Parcourir la source

[MERGE #3741 @Cellule] WASM: Implement new Sign Extend operators

Merge pull request #3741 from Cellule:wasm/signextend2

Implement new operators as describe in
https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md#new-sign-extending-operators
Michael Ferris il y a 8 ans
Parent
commit
18c55b732e
40 fichiers modifiés avec 618 ajouts et 120 suppressions
  1. 1 1
      lib/Backend/BackwardPass.cpp
  2. 25 0
      lib/Backend/IRBuilderAsmJs.cpp
  3. 3 1
      lib/Backend/IRBuilderAsmJs.h
  4. 8 0
      lib/Backend/Lower.cpp
  5. 63 0
      lib/Backend/LowerMDShared.cpp
  6. 1 0
      lib/Backend/LowerMDShared.h
  7. 7 9
      lib/Backend/Opnd.cpp
  8. 1 0
      lib/Backend/Opnd.h
  9. 1 0
      lib/Backend/arm/LowerMD.h
  10. 1 0
      lib/Backend/arm64/LowerMD.h
  11. 2 0
      lib/Common/ConfigFlagsList.h
  12. 7 0
      lib/Runtime/ByteCode/OpCodesAsmJs.h
  13. 3 1
      lib/Runtime/Language/AsmJsUtils.cpp
  14. 7 0
      lib/Runtime/Language/InterpreterHandlerAsmJs.inl
  15. 1 7
      lib/Runtime/Library/WabtInterface.cpp
  16. 2 0
      lib/Runtime/Math/WasmMath.h
  17. 6 0
      lib/Runtime/Math/WasmMath.inl
  18. 8 0
      lib/WasmReader/WasmBinaryOpCodes.h
  19. 24 15
      lib/wabt/chakra/wabtapi.cc
  20. 3 6
      lib/wabt/chakra/wabtapi.h
  21. 1 1
      lib/wabt/src/feature.cc
  22. 2 1
      lib/wabt/src/feature.h
  23. 1 0
      test/WasmSpec/baselines/chakra_extends_i32.baseline
  24. 1 0
      test/WasmSpec/baselines/chakra_extends_i64.baseline
  25. 1 0
      test/WasmSpec/baselines/extends_i32.baseline
  26. 1 0
      test/WasmSpec/baselines/extends_i64.baseline
  27. 37 0
      test/WasmSpec/chakra/chakra_extends_i32.wast
  28. 57 0
      test/WasmSpec/chakra/chakra_extends_i64.wast
  29. 0 0
      test/WasmSpec/chakra/chakra_i32.wast
  30. 0 0
      test/WasmSpec/chakra/chakra_i64.wast
  31. 17 0
      test/WasmSpec/convert-test-suite/config.json
  32. 2 2
      test/WasmSpec/convert-test-suite/generateTests/chakra_intConst.js
  33. 13 2
      test/WasmSpec/convert-test-suite/generateTests/index.js
  34. 99 63
      test/WasmSpec/convert-test-suite/index.js
  35. 3 2
      test/WasmSpec/convert-test-suite/package.json
  36. 22 0
      test/WasmSpec/features/extends/extends_i32.wast
  37. 34 0
      test/WasmSpec/features/extends/extends_i64.wast
  38. 69 9
      test/WasmSpec/rlexe.xml
  39. 7 0
      test/wasm/rlexe.xml
  40. 77 0
      test/wasm/signextend.js

+ 1 - 1
lib/Backend/BackwardPass.cpp

@@ -5534,7 +5534,7 @@ BackwardPass::TrackIntUsage(IR::Instr *const instr)
             case Js::OpCode::Conv_Prim:
                 Assert(dstSym);
                 Assert(instr->GetSrc1());
-                Assert(!instr->GetSrc2());
+                Assert(!instr->GetSrc2() || instr->GetDst()->GetType() == instr->GetSrc1()->GetType());
 
                 if(instr->GetDst()->IsInt32())
                 {

+ 25 - 0
lib/Backend/IRBuilderAsmJs.cpp

@@ -2408,6 +2408,13 @@ IRBuilderAsmJs::BuildInt2(Js::OpCodeAsmJs newOpcode, uint32 offset, Js::RegSlot
     case Js::OpCodeAsmJs::GrowMemory:
         instr = IR::Instr::New(Js::OpCode::GrowWasmMemory, dstOpnd, BuildSrcOpnd(AsmJsRegSlots::WasmMemoryReg, TyVar), srcOpnd, m_func);
         break;
+
+    case Js::OpCodeAsmJs::I32Extend8_s: 
+        instr = CreateSignExtendInstr(dstOpnd, srcOpnd, TyInt8);
+        break;
+    case Js::OpCodeAsmJs::I32Extend16_s:
+        instr = CreateSignExtendInstr(dstOpnd, srcOpnd, TyInt16);
+        break;
     default:
         Assume(UNREACHED);
     }
@@ -2436,6 +2443,15 @@ IR::RegOpnd* IRBuilderAsmJs::BuildTrapIfMinIntOverNegOne(IR::RegOpnd* src1Opnd,
     return newSrc;
 }
 
+IR::Instr* IRBuilderAsmJs::CreateSignExtendInstr(IR::Opnd* dst, IR::Opnd* src, IRType fromType)
+{
+    // Since CSE ignores the type of the type, the int const value carries that information to prevent
+    // cse of sign extension of different types.
+    IR::Opnd* fromTypeOpnd = IR::IntConstOpnd::New(fromType, fromType, m_func);
+    // Src2 is a dummy source, used only to carry the type to cast from
+    return IR::Instr::New(Js::OpCode::Conv_Prim, dst, src, fromTypeOpnd, m_func);
+}
+
 void
 IRBuilderAsmJs::BuildInt3(Js::OpCodeAsmJs newOpcode, uint32 offset, Js::RegSlot dstRegSlot, Js::RegSlot src1RegSlot, Js::RegSlot src2RegSlot)
 {
@@ -3054,6 +3070,15 @@ IRBuilderAsmJs::BuildLong2(Js::OpCodeAsmJs newOpcode, uint32 offset, Js::RegSlot
             AddInstr(slotInstr, offset);
         }
         break;
+    case Js::OpCodeAsmJs::I64Extend8_s:
+        instr = CreateSignExtendInstr(dstOpnd, srcOpnd, TyInt8);
+        break;
+    case Js::OpCodeAsmJs::I64Extend16_s:
+        instr = CreateSignExtendInstr(dstOpnd, srcOpnd, TyInt16);
+        break;
+    case Js::OpCodeAsmJs::I64Extend32_s:
+        instr = CreateSignExtendInstr(dstOpnd, srcOpnd, TyInt32);
+        break;
     default:
         Assume(UNREACHED);
     }

+ 3 - 1
lib/Backend/IRBuilderAsmJs.h

@@ -164,7 +164,9 @@ private:
     IR::Instr*              GenerateStSlotForReturn(IR::RegOpnd* srcOpnd, IRType type);
     IR::RegOpnd*            BuildTrapIfZero(IR::RegOpnd* srcOpnd, uint32 offset);
     IR::RegOpnd*            BuildTrapIfMinIntOverNegOne(IR::RegOpnd* src1Opnd, IR::RegOpnd* src2Opnd, uint32 offset);
-    
+
+    IR::Instr*              CreateSignExtendInstr(IR::Opnd* dst, IR::Opnd* src, IRType fromType);
+
     JitArenaAllocator *     m_tempAlloc;
     JitArenaAllocator *     m_funcAlloc;
     Func *                  m_func;

+ 8 - 0
lib/Backend/Lower.cpp

@@ -1953,6 +1953,10 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa
                 {
                     m_lowererMD.EmitUIntToLong(instr->GetDst(), instr->GetSrc1(), instr);
                 }
+                else if (instr->GetSrc1()->IsInt64() && instr->GetSrc2())
+                {
+                    m_lowererMD.EmitSignExtend(instr);
+                }
                 else
                 {
                     Assert(0);
@@ -1965,6 +1969,10 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa
                 {
                     m_lowererMD.EmitLongToInt(instr->GetDst(), instr->GetSrc1(), instr);
                 }
+                else if ((instr->GetSrc1()->IsInt32() || instr->GetSrc1()->IsUInt32()) && instr->GetSrc2())
+                {
+                    m_lowererMD.EmitSignExtend(instr);
+                }
                 else
                 {
                     Assert(instr->GetSrc1()->IsFloat());

+ 63 - 0
lib/Backend/LowerMDShared.cpp

@@ -1609,6 +1609,17 @@ LowererMD::Legalize(IR::Instr *const instr, bool fPostRegAlloc)
 
             break;
         }
+        case Js::OpCode::MOVSX:
+        case Js::OpCode::MOVSXW:
+            Assert(instr->GetDst()->GetSize() == 4 || instr->GetDst()->GetSize() == 8);
+            Assert(instr->m_opcode != Js::OpCode::MOVSX || instr->GetSrc1()->GetSize() == 1);
+            Assert(instr->m_opcode != Js::OpCode::MOVSXW || instr->GetSrc1()->GetSize() == 2);
+            LegalizeOpnds<verify>(
+                instr,
+                L_Reg,
+                L_Reg | L_Mem,
+                L_None);
+            break;
 
         case Js::OpCode::MOVUPS:
         case Js::OpCode::MOVAPS:
@@ -7620,6 +7631,58 @@ LowererMD::EmitLongToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert)
     this->lowererMDArch.EmitLongToInt(dst, src, instrInsert);
 }
 
+
+void LowererMD::EmitSignExtend(IR::Instr * instr)
+{
+    IR::Opnd* dst = instr->GetDst();
+    IR::Opnd* src1 = instr->GetSrc1();
+    IR::Opnd* src2 = instr->GetSrc2();
+    Assert(dst && src1 && src2);
+
+    // Src2 is used to determine what's the from type size
+    Assert(src2->GetSize() < dst->GetSize());
+    IRType fromType = src2->GetType();
+    Js::OpCode op = Js::OpCode::MOVSX;
+    switch (src2->GetSize())
+    {
+    case 1: break; // default
+    case 2: op = Js::OpCode::MOVSXW; break;
+    case 4:
+#if _M_X64
+        op = Js::OpCode::MOVSXD;
+#else
+        op = LowererMDArch::GetAssignOp(fromType);
+#endif
+        break;
+    default:
+        Assert(UNREACHED);
+    }
+
+#if _M_IX86
+    // Special handling of int64 on x86
+    if (dst->IsInt64())
+    {
+        Int64RegPair dstPair = m_func->FindOrCreateInt64Pair(dst);
+        Int64RegPair srcPair = m_func->FindOrCreateInt64Pair(src1);
+
+        IR::RegOpnd * eaxReg = IR::RegOpnd::New(RegEAX, TyInt32, m_func);
+        IR::RegOpnd * edxReg = IR::RegOpnd::New(RegEDX, TyInt32, m_func);
+
+        instr->InsertBefore(IR::Instr::New(op, eaxReg, srcPair.low->UseWithNewType(fromType, m_func), m_func)); 
+        Legalize(instr->m_prev);
+        instr->InsertBefore(IR::Instr::New(Js::OpCode::CDQ, edxReg, m_func));
+        Legalize(instr->m_prev);
+        m_lowerer->InsertMove(dstPair.low, eaxReg, instr);
+        m_lowerer->InsertMove(dstPair.high, edxReg, instr);
+    }
+    else
+#endif
+    {
+        instr->InsertBefore(IR::Instr::New(op, dst, src1->UseWithNewType(fromType, m_func), m_func));
+        Legalize(instr->m_prev);
+    }
+}
+
 void
 LowererMD::EmitFloat32ToFloat64(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert)
 {

+ 1 - 0
lib/Backend/LowerMDShared.h

@@ -216,6 +216,7 @@ public:
             void            EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert, IR::Instr * instrBailOut = nullptr, IR::LabelInstr * labelBailOut = nullptr);
             void            EmitInt64toFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert);
             void            EmitFloat32ToFloat64(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert);
+            void            EmitSignExtend(IR::Instr * instr);
      static IR::Instr *     InsertConvertFloat64ToInt32(const RoundMode roundMode, IR::Opnd *const dst, IR::Opnd *const src, IR::Instr *const insertBeforeInstr);
             void            ConvertFloatToInt32(IR::Opnd* intOpnd, IR::Opnd* floatOpnd, IR::LabelInstr * labelHelper, IR::LabelInstr * labelDone, IR::Instr * instInsert);
             void            EmitReinterpretPrimitive(IR::Opnd* dst, IR::Opnd* src, IR::Instr* insertBeforeInstr);

+ 7 - 9
lib/Backend/Opnd.cpp

@@ -1093,25 +1093,23 @@ void RegOpnd::Initialize(StackSym *sym, RegNum reg, IRType type)
 ///----------------------------------------------------------------------------
 
 RegOpnd *
-    RegOpnd::New(IRType type, Func *func)
+RegOpnd::New(IRType type, Func *func)
 {
     return RegOpnd::New(StackSym::New(type, func), RegNOREG, type, func);
 }
 
+IR::RegOpnd *
+RegOpnd::New(RegNum reg, IRType type, Func *func)
+{
+    return RegOpnd::New(StackSym::New(type, func), reg, type, func);
+}
+
 RegOpnd *
 RegOpnd::New(StackSym *sym, IRType type, Func *func)
 {
     return RegOpnd::New(sym, RegNOREG, type, func);
 }
 
-///----------------------------------------------------------------------------
-///
-/// RegOpnd::New
-///
-///     Creates a new RegOpnd.
-///
-///----------------------------------------------------------------------------
-
 RegOpnd *
 RegOpnd::New(StackSym *sym, RegNum reg, IRType type, Func *func)
 {

+ 1 - 0
lib/Backend/Opnd.h

@@ -1275,6 +1275,7 @@ private:
 
 public:
     static RegOpnd *        New(IRType type, Func *func);
+    static RegOpnd *        New(RegNum reg, IRType type, Func *func);
     static RegOpnd *        New(StackSym *sym, IRType type, Func *func);
     static RegOpnd *        New(StackSym *sym, RegNum reg, IRType type, Func *func);
 

+ 1 - 0
lib/Backend/arm/LowerMD.h

@@ -207,6 +207,7 @@ public:
             void                EmitIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert);
             void                EmitUIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert);
             void                EmitLongToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert);
+            void                EmitSignExtend(IR::Instr * instr) { Assert(UNREACHED); }
             void                EmitReinterpretPrimitive(IR::Opnd* dst, IR::Opnd* src, IR::Instr* insertBeforeInstr) { Assert(UNREACHED); }
             void                EmitLoadFloatFromNumber(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr);
             IR::LabelInstr*     EmitLoadFloatCommon(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, bool needHelperLabel);

+ 1 - 0
lib/Backend/arm64/LowerMD.h

@@ -202,6 +202,7 @@ public:
               void                EmitIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) { __debugbreak(); }
               void                EmitUIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) { __debugbreak(); }
               void                EmitLongToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) { __debugbreak(); }
+              void                EmitSignExtend(IR::Instr * instr) { __debugbreak(); }
               void                EmitReinterpretPrimitive(IR::Opnd* dst, IR::Opnd* src, IR::Instr* insertBeforeInstr) { __debugbreak(); }
               void                GenerateTruncWithCheck(IR::Instr * instr) { __debugbreak(); }
               static IR::Instr *  InsertConvertFloat64ToInt32(const RoundMode roundMode, IR::Opnd *const dst, IR::Opnd *const src, IR::Instr *const insertBeforeInstr) { __debugbreak(); return 0; }

+ 2 - 0
lib/Common/ConfigFlagsList.h

@@ -405,6 +405,7 @@ PHASE(All)
 #define DEFAULT_CONFIG_WasmMathExFilter     (false)
 #define DEFAULT_CONFIG_WasmIgnoreResponse   (false)
 #define DEFAULT_CONFIG_WasmMaxTableSize     (10000000)
+#define DEFAULT_CONFIG_WasmSignExtends      (false)
 #define DEFAULT_CONFIG_BgJitDelayFgBuffer   (0)
 #define DEFAULT_CONFIG_BgJitPendingFuncCap  (31)
 #define DEFAULT_CONFIG_CurrentSourceInfo    (true)
@@ -873,6 +874,7 @@ FLAGNR(Boolean, WasmCheckVersion      , "Check the binary version for WebAssembl
 FLAGNR(Boolean, WasmFold              , "Enable i32/i64 const folding", DEFAULT_CONFIG_WasmFold)
 FLAGNR(Boolean, WasmIgnoreResponse    , "Ignore the type of the Response object", DEFAULT_CONFIG_WasmIgnoreResponse)
 FLAGNR(Number,  WasmMaxTableSize      , "Maximum size allowed to the WebAssembly.Table", DEFAULT_CONFIG_WasmMaxTableSize)
+FLAGNR(Boolean, WasmSignExtends       , "Use new WebAssembly sign extension operators", DEFAULT_CONFIG_WasmSignExtends)
 
 #ifdef ENABLE_SIMDJS
 #ifndef COMPILE_DISABLE_Simdjs

+ 7 - 0
lib/Runtime/ByteCode/OpCodesAsmJs.h

@@ -117,6 +117,13 @@ MACRO_WMS       ( Return_Db                  , Double2         , None
 MACRO_WMS       ( Return_Flt                 , Float2          , None            ) // convert float to var
 MACRO_WMS       ( Return_Int                 , Int2            , None            ) // convert int to var
 
+// Wasm Sign Extension operators
+MACRO_WMS       ( I32Extend8_s               , Int2            , None            )
+MACRO_WMS       ( I32Extend16_s              , Int2            , None            )
+MACRO_WMS       ( I64Extend8_s               , Long2           , None            )
+MACRO_WMS       ( I64Extend16_s              , Long2           , None            )
+MACRO_WMS       ( I64Extend32_s              , Long2           , None            )
+
 // Module memory manipulation
 MACRO_WMS       ( LdSlotArr                  , ElementSlot     , None            ) // Loads an array of Var from an array of Var
 MACRO_WMS       ( LdSlot                     , ElementSlot     , None            ) // Loads a Var from an array of Var

+ 3 - 1
lib/Runtime/Language/AsmJsUtils.cpp

@@ -631,7 +631,9 @@ namespace Js
 #if ENABLE_DEBUG_CONFIG_OPTIONS
             if (CONFIG_FLAG(WasmI64))
             {
-                returnValue = CreateI64ReturnObject((int64)iLow | ((int64)iHigh << 32), func->GetScriptContext());
+                uint64 lHigh = ((uint64)iHigh) << 32;
+                uint64 lLow = (uint64)(uint32)iLow;
+                returnValue = CreateI64ReturnObject((int64)(lHigh | lLow), func->GetScriptContext());
                 break;
             }
 #endif

+ 7 - 0
lib/Runtime/Language/InterpreterHandlerAsmJs.inl

@@ -91,6 +91,13 @@ EXDEF2    (NOPASMJS          , InvalidOpCode, Empty
   DEF2_WMS( I1toI1Mem        , Return_Int   , (int)                                              )
   DEF2_WMS( L1toL1Mem        , Return_Long  , (int64)                                            )
 
+// Wasm Sign Extension operators
+  DEF2_WMS( I1toI1Mem        , I32Extend8_s , (Wasm::WasmMath::SignExtend<int32, int8> )         )
+  DEF2_WMS( I1toI1Mem        , I32Extend16_s, (Wasm::WasmMath::SignExtend<int32, int16>)         )
+  DEF2_WMS( L1toL1Mem        , I64Extend8_s , (Wasm::WasmMath::SignExtend<int64, int8> )         )
+  DEF2_WMS( L1toL1Mem        , I64Extend16_s, (Wasm::WasmMath::SignExtend<int64, int16>)         )
+  DEF2_WMS( L1toL1Mem        , I64Extend32_s, (Wasm::WasmMath::SignExtend<int64, int32>)         )
+
   DEF2_WMS( I1toI1Mem        , BeginSwitch_Int, (int)                                            )
   DEF2    ( BR_ASM           , EndSwitch_Int, OP_Br                                              )
   DEF2_WMS( BR_ASM_Mem       , Case_Int     , AsmJsMath::CmpEq<int>                              )

+ 1 - 7
lib/Runtime/Library/WabtInterface.cpp

@@ -101,12 +101,6 @@ Js::Var CreateBuffer(const uint8* buf, uint size, void* user_data)
     return arrayBuffer;
 }
 
-void* Allocate(uint size, void* user_data)
-{
-    Context* ctx = (Context*)user_data;
-    return (void*)AnewArrayZ(ctx->allocator, byte, size);
-};
-
 Js::Var WabtInterface::EntryConvertWast2Wasm(RecyclableObject* function, CallInfo callInfo, ...)
 {
     ScriptContext* scriptContext = function->GetScriptContext();
@@ -152,8 +146,8 @@ Js::Var WabtInterface::EntryConvertWast2Wasm(RecyclableObject* function, CallInf
         ChakraWabt::SpecContext spec;
         ChakraWabt::Context wabtCtx;
         wabtCtx.user_data = &context;
-        wabtCtx.allocator = Allocate;
         wabtCtx.createBuffer = CreateBuffer;
+        wabtCtx.features.sign_extends = CONFIG_FLAG(WasmSignExtends);
         if (isSpecText)
         {
             wabtCtx.spec = &spec;

+ 2 - 0
lib/Runtime/Math/WasmMath.h

@@ -27,6 +27,8 @@ public:
     template <typename T> using CmpPtr = bool(*)(T a, T b);
     template <typename STYPE, typename UTYPE, UTYPE MAX, UTYPE NEG_ZERO, UTYPE NEG_ONE, CmpPtr<UTYPE> CMP1, CmpPtr<UTYPE> CMP2> static bool isInRange(STYPE srcVal);
     template <typename STYPE> static bool isNaN(STYPE src);
+
+    template<typename To, typename From> static To SignExtend(To value);
 };
 
 } //namespace Wasm

+ 6 - 0
lib/Runtime/Math/WasmMath.inl

@@ -223,4 +223,10 @@ inline int64 WasmMath::Ror(int64 aLeft, int64 aRight)
     return _rotr64(aLeft, (int)aRight);
 }
 
+template<typename To, typename From>
+To WasmMath::SignExtend(To value)
+{
+    return static_cast<To>(static_cast<From>(value));
+}
+
 }

+ 8 - 0
lib/WasmReader/WasmBinaryOpCodes.h

@@ -296,6 +296,14 @@ WASM_UNARY__OPCODE(I64ReinterpretF64, 0xbd, L_D , Reinterpret_DTL, false)
 WASM_UNARY__OPCODE(F32ReinterpretI32, 0xbe, F_I , Reinterpret_ITF, false)
 WASM_UNARY__OPCODE(F64ReinterpretI64, 0xbf, D_L , Reinterpret_LTD, false)
 
+// New sign extend operators
+WASM_UNARY__OPCODE(I32Extend8_s , 0xc0, I_I, I32Extend8_s , !CONFIG_FLAG(WasmSignExtends))
+WASM_UNARY__OPCODE(I32Extend16_s, 0xc1, I_I, I32Extend16_s, !CONFIG_FLAG(WasmSignExtends))
+WASM_UNARY__OPCODE(I64Extend8_s , 0xc2, L_L, I64Extend8_s , !CONFIG_FLAG(WasmSignExtends))
+WASM_UNARY__OPCODE(I64Extend16_s, 0xc3, L_L, I64Extend16_s, !CONFIG_FLAG(WasmSignExtends))
+WASM_UNARY__OPCODE(I64Extend32_s, 0xc4, L_L, I64Extend32_s, !CONFIG_FLAG(WasmSignExtends))
+
+
 #if ENABLE_DEBUG_CONFIG_OPTIONS
 WASM_UNARY__OPCODE(PrintFuncName    , 0xf0, V_I , PrintFuncName    , false)
 WASM_EMPTY__OPCODE(PrintArgSeparator, 0xf1,       PrintArgSeparator, false)

+ 24 - 15
lib/wabt/chakra/wabtapi.cc

@@ -425,20 +425,19 @@ Js::Var write_commands(Context* ctx, Script* script)
     return resultObj;
 }
 
-void Context::Validate(bool isSpec) const
+void Validate(const Context& ctx, bool isSpec)
 {
-    if (!allocator) throw Error("Missing allocator");
-    if (!createBuffer) throw Error("Missing createBuffer");
+    if (!ctx.createBuffer) throw Error("Missing createBuffer");
     if (isSpec)
     {
-        if (!spec) throw Error("Missing Spec context");
-        if (!spec->setProperty) throw Error("Missing spec->setProperty");
-        if (!spec->int32ToVar) throw Error("Missing spec->int32ToVar");
-        if (!spec->int64ToVar) throw Error("Missing spec->int64ToVar");
-        if (!spec->stringToVar) throw Error("Missing spec->stringToVar");
-        if (!spec->createObject) throw Error("Missing spec->createObject");
-        if (!spec->createArray) throw Error("Missing spec->createArray");
-        if (!spec->push) throw Error("Missing spec->push");
+        if (!ctx.spec) throw Error("Missing Spec context");
+        if (!ctx.spec->setProperty) throw Error("Missing spec->setProperty");
+        if (!ctx.spec->int32ToVar) throw Error("Missing spec->int32ToVar");
+        if (!ctx.spec->int64ToVar) throw Error("Missing spec->int64ToVar");
+        if (!ctx.spec->stringToVar) throw Error("Missing spec->stringToVar");
+        if (!ctx.spec->createObject) throw Error("Missing spec->createObject");
+        if (!ctx.spec->createArray) throw Error("Missing spec->createArray");
+        if (!ctx.spec->push) throw Error("Missing spec->push");
     }
 }
 
@@ -450,18 +449,28 @@ void CheckResult(Result result, const char* errorMessage)
     }
 }
 
+Features GetWabtFeatures(Context& ctx)
+{
+    Features features;
+    if (ctx.features.sign_extends)
+    {
+        features.enable_threads();
+    }
+    return features;
+}
+
 Js::Var ChakraWabt::ConvertWast2Wasm(Context& ctx, char* buffer, uint bufferSize, bool isSpecText)
 {
-    ctx.Validate(isSpecText);
+    Validate(ctx, isSpecText);
 
     std::unique_ptr<WastLexer> lexer = WastLexer::CreateBufferLexer("", buffer, (size_t)bufferSize);
 
     MyErrorHandler s_error_handler;
-
+    WastParseOptions options(GetWabtFeatures(ctx));
     if (isSpecText)
     {
         std::unique_ptr<Script> script;
-        Result result = ParseWastScript(lexer.get(), &script, &s_error_handler);
+        Result result = ParseWastScript(lexer.get(), &script, &s_error_handler, &options);
         CheckResult(result, "Invalid wast script");
 
         result = ResolveNamesScript(lexer.get(), script.get(), &s_error_handler);
@@ -472,7 +481,7 @@ Js::Var ChakraWabt::ConvertWast2Wasm(Context& ctx, char* buffer, uint bufferSize
     else
     {
         std::unique_ptr<Module> module;
-        Result result = ParseWatModule(lexer.get(), &module, &s_error_handler);
+        Result result = ParseWatModule(lexer.get(), &module, &s_error_handler, &options);
         CheckResult(result, "Invalid wat script");
 
         result = ResolveNamesModule(lexer.get(), module.get(), &s_error_handler);

+ 3 - 6
lib/wabt/chakra/wabtapi.h

@@ -61,19 +61,16 @@ namespace ChakraWabt
     };
 
     typedef Js::Var(*CreateBufferFn)(const unsigned char* start, uint size, void* user_data);
-    typedef void*(*AllocatorFn)(uint size, void* user_data);
     struct Context
     {
-        AllocatorFn allocator;
         CreateBufferFn createBuffer;
         SpecContext* spec;
         void* user_data;
 
-        void* Allocate(uint size)
+        struct
         {
-            return allocator(size, user_data);
-        }
-        void Validate(bool isSpec) const;
+            bool sign_extends : 1;
+        } features;
     };
 
     Js::Var ConvertWast2Wasm(Context& ctx, char* buffer, uint bufferSize, bool isSpecText);

+ 1 - 1
lib/wabt/src/feature.cc

@@ -23,7 +23,7 @@ namespace wabt {
 void Features::AddOptions(OptionParser* parser) {
 #define WABT_FEATURE(variable, flag, help) \
   parser->AddOption("enable-" flag, help,  \
-                    [this]() { variable##_enabled_ = true; });
+                    [this]() { enable_##variable(); });
 
 #include "src/feature.def"
 #undef WABT_FEATURE

+ 2 - 1
lib/wabt/src/feature.h

@@ -28,7 +28,8 @@ class Features {
   void AddOptions(OptionParser*);
 
 #define WABT_FEATURE(variable, flag, help) \
-  bool variable##_enabled() const { return variable##_enabled_; }
+  bool variable##_enabled() const { return variable##_enabled_; } \
+  void enable_##variable() { variable##_enabled_ = true; }
 #include "src/feature.def"
 #undef WABT_FEATURE
 

+ 1 - 0
test/WasmSpec/baselines/chakra_extends_i32.baseline

@@ -0,0 +1 @@
+15/15 tests passed.

+ 1 - 0
test/WasmSpec/baselines/chakra_extends_i64.baseline

@@ -0,0 +1 @@
+25/25 tests passed.

+ 1 - 0
test/WasmSpec/baselines/extends_i32.baseline

@@ -0,0 +1 @@
+15/15 tests passed.

+ 1 - 0
test/WasmSpec/baselines/extends_i64.baseline

@@ -0,0 +1 @@
+25/25 tests passed.

+ 37 - 0
test/WasmSpec/chakra/chakra_extends_i32.wast

@@ -0,0 +1,37 @@
+;;-------------------------------------------------------------------------------------------------------
+;; Copyright (C) Microsoft. All rights reserved.
+;; Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+;;-------------------------------------------------------------------------------------------------------
+;;AUTO-GENERATED do not modify
+
+(module
+  (func (export "extend8_s0") (result i32) (i32.extend8_s (i32.const 0)))
+  (func (export "extend8_s1") (result i32) (i32.extend8_s (i32.const 0x7f)))
+  (func (export "extend8_s2") (result i32) (i32.extend8_s (i32.const 0x80)))
+  (func (export "extend8_s3") (result i32) (i32.extend8_s (i32.const 0xff)))
+  (func (export "extend8_s4") (result i32) (i32.extend8_s (i32.const 0x012345_00)))
+  (func (export "extend8_s5") (result i32) (i32.extend8_s (i32.const 0xfedcba_80)))
+  (func (export "extend8_s6") (result i32) (i32.extend8_s (i32.const -1)))
+  (func (export "extend16_s7") (result i32) (i32.extend16_s (i32.const 0)))
+  (func (export "extend16_s8") (result i32) (i32.extend16_s (i32.const 0x7fff)))
+  (func (export "extend16_s9") (result i32) (i32.extend16_s (i32.const 0x8000)))
+  (func (export "extend16_s10") (result i32) (i32.extend16_s (i32.const 0xffff)))
+  (func (export "extend16_s11") (result i32) (i32.extend16_s (i32.const 0x0123_0000)))
+  (func (export "extend16_s12") (result i32) (i32.extend16_s (i32.const 0xfedc_8000)))
+  (func (export "extend16_s13") (result i32) (i32.extend16_s (i32.const -1)))
+)
+
+(assert_return (invoke "extend8_s0") (i32.const 0))
+(assert_return (invoke "extend8_s1") (i32.const 127))
+(assert_return (invoke "extend8_s2") (i32.const -128))
+(assert_return (invoke "extend8_s3") (i32.const -1))
+(assert_return (invoke "extend8_s4") (i32.const 0))
+(assert_return (invoke "extend8_s5") (i32.const -0x80))
+(assert_return (invoke "extend8_s6") (i32.const -1))
+(assert_return (invoke "extend16_s7") (i32.const 0))
+(assert_return (invoke "extend16_s8") (i32.const 32767))
+(assert_return (invoke "extend16_s9") (i32.const -32768))
+(assert_return (invoke "extend16_s10") (i32.const -1))
+(assert_return (invoke "extend16_s11") (i32.const 0))
+(assert_return (invoke "extend16_s12") (i32.const -0x8000))
+(assert_return (invoke "extend16_s13") (i32.const -1))

+ 57 - 0
test/WasmSpec/chakra/chakra_extends_i64.wast

@@ -0,0 +1,57 @@
+;;-------------------------------------------------------------------------------------------------------
+;; Copyright (C) Microsoft. All rights reserved.
+;; Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+;;-------------------------------------------------------------------------------------------------------
+;;AUTO-GENERATED do not modify
+
+(module
+  (func (export "extend8_s0") (result i64) (i64.extend8_s (i64.const 0)))
+  (func (export "extend8_s1") (result i64) (i64.extend8_s (i64.const 0x7f)))
+  (func (export "extend8_s2") (result i64) (i64.extend8_s (i64.const 0x80)))
+  (func (export "extend8_s3") (result i64) (i64.extend8_s (i64.const 0xff)))
+  (func (export "extend8_s4") (result i64) (i64.extend8_s (i64.const 0x01234567_89abcd_00)))
+  (func (export "extend8_s5") (result i64) (i64.extend8_s (i64.const 0xfedcba98_765432_80)))
+  (func (export "extend8_s6") (result i64) (i64.extend8_s (i64.const -1)))
+  (func (export "extend16_s7") (result i64) (i64.extend16_s (i64.const 0)))
+  (func (export "extend16_s8") (result i64) (i64.extend16_s (i64.const 0x7fff)))
+  (func (export "extend16_s9") (result i64) (i64.extend16_s (i64.const 0x8000)))
+  (func (export "extend16_s10") (result i64) (i64.extend16_s (i64.const 0xffff)))
+  (func (export "extend16_s11") (result i64) (i64.extend16_s (i64.const 0x12345678_9abc_0000)))
+  (func (export "extend16_s12") (result i64) (i64.extend16_s (i64.const 0xfedcba98_7654_8000)))
+  (func (export "extend16_s13") (result i64) (i64.extend16_s (i64.const -1)))
+  (func (export "extend32_s14") (result i64) (i64.extend32_s (i64.const 0)))
+  (func (export "extend32_s15") (result i64) (i64.extend32_s (i64.const 0x7fff)))
+  (func (export "extend32_s16") (result i64) (i64.extend32_s (i64.const 0x8000)))
+  (func (export "extend32_s17") (result i64) (i64.extend32_s (i64.const 0xffff)))
+  (func (export "extend32_s18") (result i64) (i64.extend32_s (i64.const 0x7fffffff)))
+  (func (export "extend32_s19") (result i64) (i64.extend32_s (i64.const 0x80000000)))
+  (func (export "extend32_s20") (result i64) (i64.extend32_s (i64.const 0xffffffff)))
+  (func (export "extend32_s21") (result i64) (i64.extend32_s (i64.const 0x01234567_00000000)))
+  (func (export "extend32_s22") (result i64) (i64.extend32_s (i64.const 0xfedcba98_80000000)))
+  (func (export "extend32_s23") (result i64) (i64.extend32_s (i64.const -1)))
+)
+
+(assert_return (invoke "extend8_s0") (i64.const 0))
+(assert_return (invoke "extend8_s1") (i64.const 127))
+(assert_return (invoke "extend8_s2") (i64.const -128))
+(assert_return (invoke "extend8_s3") (i64.const -1))
+(assert_return (invoke "extend8_s4") (i64.const 0))
+(assert_return (invoke "extend8_s5") (i64.const -0x80))
+(assert_return (invoke "extend8_s6") (i64.const -1))
+(assert_return (invoke "extend16_s7") (i64.const 0))
+(assert_return (invoke "extend16_s8") (i64.const 32767))
+(assert_return (invoke "extend16_s9") (i64.const -32768))
+(assert_return (invoke "extend16_s10") (i64.const -1))
+(assert_return (invoke "extend16_s11") (i64.const 0))
+(assert_return (invoke "extend16_s12") (i64.const -0x8000))
+(assert_return (invoke "extend16_s13") (i64.const -1))
+(assert_return (invoke "extend32_s14") (i64.const 0))
+(assert_return (invoke "extend32_s15") (i64.const 32767))
+(assert_return (invoke "extend32_s16") (i64.const 32768))
+(assert_return (invoke "extend32_s17") (i64.const 65535))
+(assert_return (invoke "extend32_s18") (i64.const 0x7fffffff))
+(assert_return (invoke "extend32_s19") (i64.const -0x80000000))
+(assert_return (invoke "extend32_s20") (i64.const -1))
+(assert_return (invoke "extend32_s21") (i64.const 0))
+(assert_return (invoke "extend32_s22") (i64.const -0x80000000))
+(assert_return (invoke "extend32_s23") (i64.const -1))

+ 0 - 0
test/WasmSpec/testsuite/chakra/chakra_i32.wast → test/WasmSpec/chakra/chakra_i32.wast


+ 0 - 0
test/WasmSpec/testsuite/chakra/chakra_i64.wast → test/WasmSpec/chakra/chakra_i64.wast


+ 17 - 0
test/WasmSpec/convert-test-suite/config.json

@@ -1,4 +1,11 @@
 {
+  "folders": [
+    "chakra",
+    "testsuite/core",
+    "testsuite/js-api",
+
+    "features/extends"
+  ],
   "features": [{
     "flags": ["-wasmfastarray-"],
     "rltags": ["exclude_x86"],
@@ -20,6 +27,16 @@
       "chakra_i64",
       "traps"
     ]
+  }, {
+    "required": true,
+    "flags": ["-WasmSignExtends"],
+    "folders": [
+      "features/extends"
+    ],
+    "files": [
+      "chakra_extends_i32",
+      "chakra_extends_i64"
+    ]
   }],
   "excludes": [
     "names"

+ 2 - 2
test/WasmSpec/convert-test-suite/generateTests/chakra_intConst.js

@@ -76,8 +76,8 @@ function extractModule(moduleNode) {
 }
 
 module.exports = function(src, suite) {
-  const originalI64Wast = path.resolve(suite, "core", `${src}.wast`);
-  const originalContent = fs.readFileSync(originalI64Wast).toString();
+  const originalWast = path.resolve(suite, `${src}.wast`);
+  const originalContent = fs.readFileSync(originalWast).toString();
   return new Promise((resolve, reject) => {
     let id = 0;
     const newModuleContent = [];

+ 13 - 2
test/WasmSpec/convert-test-suite/generateTests/index.js

@@ -3,10 +3,21 @@
 // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 //-------------------------------------------------------------------------------------------------------
 const chakraIntConst = require("./chakra_intConst");
+const path = require("path");
 
 const genTests = [
-  {name: "chakra_i64", getContent: chakraIntConst.bind(null, "i64")},
-  {name: "chakra_i32", getContent: chakraIntConst.bind(null, "i32")},
+  {name: "chakra_i64", getContent(rlRoot) {
+    return chakraIntConst("i64", path.join(rlRoot, "testsuite", "core"));
+  }},
+  {name: "chakra_i32", getContent(rlRoot) {
+    return chakraIntConst("i32", path.join(rlRoot, "testsuite", "core"));
+  }},
+  {name: "chakra_extends_i32", getContent(rlRoot) {
+    return chakraIntConst("extends_i32", path.join(rlRoot, "features", "extends"));
+  }},
+  {name: "chakra_extends_i64", getContent(rlRoot) {
+    return chakraIntConst("extends_i64", path.join(rlRoot, "features", "extends"));
+  }},
 ];
 
 module.exports = genTests;

+ 99 - 63
test/WasmSpec/convert-test-suite/index.js

@@ -5,24 +5,20 @@
 /* eslint-env node */
 const path = require("path");
 const fs = require("fs-extra");
+const Bluebird = require("bluebird");
 const {spawn} = require("child_process");
 const slash = require("slash");
 
+Bluebird.promisifyAll(fs);
 const config = require("./config.json");
-const rlRoot = path.join(__dirname, "..");
+const rlRoot = path.resolve(__dirname, "..");
+const folders = config.folders.map(folder => path.resolve(rlRoot, folder));
 const baselineDir = path.join(rlRoot, "baselines");
 
 const argv = require("yargs")
   .help()
   .alias("help", "h")
   .options({
-    suite: {
-      string: true,
-      alias: "s",
-      description: "Path to the test suite",
-      default: path.join(rlRoot, "testsuite"),
-      demand: true,
-    },
     rebase: {
       string: true,
       description: "Path to host to run the test create/update the baselines"
@@ -31,14 +27,16 @@ const argv = require("yargs")
   .argv;
 
 // Make sure all arguments are valid
-fs.statSync(argv.suite).isDirectory();
+for (const folder of folders) {
+  fs.statSync(folder).isDirectory();
+}
 
 function hostFlags(specFile) {
-  return `-wasm -args ${slash(path.relative(rlRoot, specFile))} -endargs`;
+  return `-wasm -args ${slash(specFile.relative)} -endargs`;
 }
 
 function getBaselinePath(specFile) {
-  return `${slash(path.relative(rlRoot, path.join(baselineDir, path.basename(specFile, path.extname(specFile)))))}.baseline`;
+  return `${slash(path.relative(rlRoot, path.join(baselineDir, specFile.basename)))}.baseline`;
 }
 
 function removePossiblyEmptyFolder(folder) {
@@ -53,18 +51,16 @@ function removePossiblyEmptyFolder(folder) {
   });
 }
 
-function main() {
-  const chakraTestsDestination = path.join(argv.suite, "chakra");
-  const chakraTests = require("./generateTests");
+function generateChakraTests() {
+  const chakraTestsDestination = path.join(rlRoot, "chakra");
 
-  return Promise.all([
-    removePossiblyEmptyFolder(chakraTestsDestination),
-  ]).then(() => {
-    fs.ensureDirSync(chakraTestsDestination);
-    return Promise.all(chakraTests.map(test => test.getContent(argv.suite)
-      .then(content => new Promise((resolve, reject) => {
+  const chakraTests = require("./generateTests");
+  return removePossiblyEmptyFolder(chakraTestsDestination)
+    .then(() => fs.ensureDirAsync(chakraTestsDestination))
+    .then(() => Promise.all(chakraTests.map(test => test.getContent(rlRoot)
+      .then(content => {
         if (!content) {
-          return resolve();
+          return;
         }
         const testPath = path.join(chakraTestsDestination, `${test.name}.wast`);
         const copyrightHeader =
@@ -73,63 +69,89 @@ function main() {
 ;; Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 ;;-------------------------------------------------------------------------------------------------------
 `;
-        fs.writeFile(testPath, `${copyrightHeader};;AUTO-GENERATED do not modify\n${content}`, err => {
-          if (err) {
-            return reject(err);
-          }
-          console.log(`Generated ${testPath}`);
-          return resolve(testPath);
-        });
-      }))));
-  }).then(chakraTests => new Promise((resolve) => {
-    const specFiles = [...chakraTests];
-    fs.walk(path.join(argv.suite, "core"))
-      .on("data", item => {
-        if (
-          path.extname(item.path) === ".wast" &&
-          item.path.indexOf(".fail") === -1 &&
-          !config.excludes.includes(path.basename(item.path, ".wast"))
-        ) {
-          specFiles.push(item.path);
-        }
+        return fs.writeFileAsync(testPath, `${copyrightHeader};;AUTO-GENERATED do not modify\n${content}`);
       })
-      .on("end", () => {
-        resolve(specFiles);
-      });
-  })).then(specFiles => new Promise((resolve) => {
-    fs.walk(path.join(argv.suite, "js-api"))
+    )));
+}
+
+function main() {
+  let runs;
+  return generateChakraTests(
+    // Walk all the folders to find test files
+  ).then(() => Bluebird.reduce(folders, (specFiles, folder) => new Promise((resolve) => {
+    fs.walk(folder)
       .on("data", item => {
-        if (path.extname(item.path) === ".js") {
-          specFiles.push(item.path);
+        const ext = path.extname(item.path);
+        const basename = path.basename(item.path, ext);
+        if ((
+            (ext === ".wast" && item.path.indexOf(".fail") === -1) ||
+            (ext === ".js")
+          ) &&
+          !config.excludes.includes(basename)
+        ) {
+          specFiles.push({
+            path: item.path,
+            basename,
+            ext,
+            dirname: path.dirname(item.path),
+            relative: path.relative(rlRoot, item.path)
+          });
         }
       })
       .on("end", () => {
         resolve(specFiles);
       });
-  })).then(specFiles => {
+  }), [])
+  ).then(specFiles => {
+    // Verify that no 2 file have the same name. We use the name as key even if they're in different folders
+    const map = {};
+    for (const file of specFiles) {
+      if (map[file.basename]) {
+        throw new Error(`Duplicate filename entry
+original : ${map[file.basename].path}
+duplicate: ${file.path}`);
+      }
+      map[file.basename] = file;
+    }
+    return specFiles;
+  }).then(specFiles => {
     const runners = {
       ".wast": "spec.js",
       ".js": "jsapi.js",
     };
-    const runs = specFiles.map(specFile => {
-      const ext = path.extname(specFile);
-      const basename = path.basename(specFile, ext);
-      const isXplatExcluded = config["xplat-excludes"].indexOf(path.basename(specFile, ext)) !== -1;
+    runs = specFiles.map(specFile => {
+      const ext = specFile.ext;
+      const basename = specFile.basename;
+      const dirname = path.dirname(specFile.path);
+
+      const useFeature = (allowRequired, feature) => !(allowRequired ^ feature.required) && (
+        (feature.files || []).includes(basename) ||
+        (feature.folders || []).map(folder => path.join(rlRoot, folder)).includes(dirname)
+      );
+
+      const isXplatExcluded = config["xplat-excludes"].indexOf(basename) !== -1;
       const baseline = getBaselinePath(specFile);
       const flags = hostFlags(specFile);
       const runner = runners[ext];
+
+      const requiredFeature = config.features
+        // Use first required feature found
+        .filter(useFeature.bind(null, true))[0] ||
+        // Or use default
+        {rltags: [], flags: []};
+
       const tests = [{
         runner,
-        tags: [],
+        tags: [].concat(requiredFeature.rltags || []),
         baseline,
-        flags: [flags]
+        flags: [flags].concat(requiredFeature.flags || [])
       }, {
         runner,
-        tags: ["exclude_dynapogo"],
+        tags: ["exclude_dynapogo"].concat(requiredFeature.rltags || []),
         baseline,
-        flags: [flags, "-nonative"]
+        flags: [flags, "-nonative"].concat(requiredFeature.flags || [])
       }].concat(config.features
-        .filter(feature => feature.files.includes(basename))
+        .filter(useFeature.bind(null, false))
         .map(feature => ({
           runner,
           baseline,
@@ -158,16 +180,14 @@ function main() {
 }
 </regress-exe>
 `);
-    return new Promise((resolve, reject) => {
-      fs.writeFile(path.join(__dirname, "..", "rlexe.xml"), rlexe, err => err ? reject(err) : resolve(runs));
-    });
-  }).then(runs => {
+    return fs.writeFileAsync(path.join(rlRoot, "rlexe.xml"), rlexe);
+  }).then(() => {
     if (!argv.rebase) {
       return;
     }
     fs.removeSync(baselineDir);
     fs.ensureDirSync(baselineDir);
-    return Promise.all(runs.map(run => new Promise((resolve, reject) => {
+    const startRuns = runs.map(run => () => new Bluebird((resolve, reject) => {
       const test = run[0];
       const baseline = fs.createWriteStream(test.baseline);
       baseline.on("open", () => {
@@ -186,7 +206,23 @@ function main() {
         engine.on("close", resolve);
       });
       baseline.on("error", reject);
-    })));
+    }));
+
+    const cucurrentRuns = 8;
+    let running = startRuns.splice(0, cucurrentRuns).map(start => start());
+    return new Promise((resolve, reject) => {
+      const checkIfContinue = () => {
+        // Keep only runs that are not done
+        running = running.filter(run => !run.isFulfilled());
+        // If we have nothing left to run, terminate
+        if (running.length === 0 && startRuns.length === 0) {
+          return resolve();
+        }
+        running.push(...(startRuns.splice(0, cucurrentRuns - running.length).map(start => start())));
+        Bluebird.any(running).then(checkIfContinue).catch(reject);
+      };
+      checkIfContinue();
+    });
   });
 }
 

+ 3 - 2
test/WasmSpec/convert-test-suite/package.json

@@ -9,9 +9,10 @@
   "author": "Michael Ferris",
   "license": "MIT",
   "dependencies": {
+    "bluebird": "^3.5.0",
     "fs-extra": "^0.30.0",
     "sexpr-plus": "^7.0.0",
-    "yargs": "^4.7.1",
-    "slash": "^1.0.0"
+    "slash": "^1.0.0",
+    "yargs": "^4.7.1"
   }
 }

+ 22 - 0
test/WasmSpec/features/extends/extends_i32.wast

@@ -0,0 +1,22 @@
+;; i32 operations
+
+(module
+  (func (export "extend8_s") (param $x i32) (result i32) (i32.extend8_s (get_local $x)))
+  (func (export "extend16_s") (param $x i32) (result i32) (i32.extend16_s (get_local $x)))
+)
+
+(assert_return (invoke "extend8_s" (i32.const 0)) (i32.const 0))
+(assert_return (invoke "extend8_s" (i32.const 0x7f)) (i32.const 127))
+(assert_return (invoke "extend8_s" (i32.const 0x80)) (i32.const -128))
+(assert_return (invoke "extend8_s" (i32.const 0xff)) (i32.const -1))
+(assert_return (invoke "extend8_s" (i32.const 0x012345_00)) (i32.const 0))
+(assert_return (invoke "extend8_s" (i32.const 0xfedcba_80)) (i32.const -0x80))
+(assert_return (invoke "extend8_s" (i32.const -1)) (i32.const -1))
+
+(assert_return (invoke "extend16_s" (i32.const 0)) (i32.const 0))
+(assert_return (invoke "extend16_s" (i32.const 0x7fff)) (i32.const 32767))
+(assert_return (invoke "extend16_s" (i32.const 0x8000)) (i32.const -32768))
+(assert_return (invoke "extend16_s" (i32.const 0xffff)) (i32.const -1))
+(assert_return (invoke "extend16_s" (i32.const 0x0123_0000)) (i32.const 0))
+(assert_return (invoke "extend16_s" (i32.const 0xfedc_8000)) (i32.const -0x8000))
+(assert_return (invoke "extend16_s" (i32.const -1)) (i32.const -1))

+ 34 - 0
test/WasmSpec/features/extends/extends_i64.wast

@@ -0,0 +1,34 @@
+;; i64 operations
+
+(module
+  (func (export "extend8_s") (param $x i64) (result i64) (i64.extend8_s (get_local $x)))
+  (func (export "extend16_s") (param $x i64) (result i64) (i64.extend16_s (get_local $x)))
+  (func (export "extend32_s") (param $x i64) (result i64) (i64.extend32_s (get_local $x)))
+)
+
+(assert_return (invoke "extend8_s" (i64.const 0)) (i64.const 0))
+(assert_return (invoke "extend8_s" (i64.const 0x7f)) (i64.const 127))
+(assert_return (invoke "extend8_s" (i64.const 0x80)) (i64.const -128))
+(assert_return (invoke "extend8_s" (i64.const 0xff)) (i64.const -1))
+(assert_return (invoke "extend8_s" (i64.const 0x01234567_89abcd_00)) (i64.const 0))
+(assert_return (invoke "extend8_s" (i64.const 0xfedcba98_765432_80)) (i64.const -0x80))
+(assert_return (invoke "extend8_s" (i64.const -1)) (i64.const -1))
+
+(assert_return (invoke "extend16_s" (i64.const 0)) (i64.const 0))
+(assert_return (invoke "extend16_s" (i64.const 0x7fff)) (i64.const 32767))
+(assert_return (invoke "extend16_s" (i64.const 0x8000)) (i64.const -32768))
+(assert_return (invoke "extend16_s" (i64.const 0xffff)) (i64.const -1))
+(assert_return (invoke "extend16_s" (i64.const 0x12345678_9abc_0000)) (i64.const 0))
+(assert_return (invoke "extend16_s" (i64.const 0xfedcba98_7654_8000)) (i64.const -0x8000))
+(assert_return (invoke "extend16_s" (i64.const -1)) (i64.const -1))
+
+(assert_return (invoke "extend32_s" (i64.const 0)) (i64.const 0))
+(assert_return (invoke "extend32_s" (i64.const 0x7fff)) (i64.const 32767))
+(assert_return (invoke "extend32_s" (i64.const 0x8000)) (i64.const 32768))
+(assert_return (invoke "extend32_s" (i64.const 0xffff)) (i64.const 65535))
+(assert_return (invoke "extend32_s" (i64.const 0x7fffffff)) (i64.const 0x7fffffff))
+(assert_return (invoke "extend32_s" (i64.const 0x80000000)) (i64.const -0x80000000))
+(assert_return (invoke "extend32_s" (i64.const 0xffffffff)) (i64.const -1))
+(assert_return (invoke "extend32_s" (i64.const 0x01234567_00000000)) (i64.const 0))
+(assert_return (invoke "extend32_s" (i64.const 0xfedcba98_80000000)) (i64.const -0x80000000))
+(assert_return (invoke "extend32_s" (i64.const -1)) (i64.const -1))

+ 69 - 9
test/WasmSpec/rlexe.xml

@@ -4,37 +4,45 @@
   <test>
     <default>
       <files>spec.js</files>
-      <baseline>baselines/chakra_i64.baseline</baseline>
-      <compile-flags>-wasm -args testsuite/chakra/chakra_i64.wast -endargs</compile-flags>
+      <baseline>baselines/chakra_extends_i32.baseline</baseline>
+      <compile-flags>-wasm -args chakra/chakra_extends_i32.wast -endargs -wasmSignExtends</compile-flags>
     </default>
   </test>
   <test>
     <default>
       <files>spec.js</files>
-      <baseline>baselines/chakra_i64.baseline</baseline>
-      <compile-flags>-wasm -args testsuite/chakra/chakra_i64.wast -endargs -nonative</compile-flags>
+      <baseline>baselines/chakra_extends_i32.baseline</baseline>
+      <compile-flags>-wasm -args chakra/chakra_extends_i32.wast -endargs -nonative -wasmSignExtends</compile-flags>
       <tags>exclude_dynapogo</tags>
     </default>
   </test>
   <test>
     <default>
       <files>spec.js</files>
-      <baseline>baselines/chakra_i64.baseline</baseline>
-      <compile-flags>-wasm -args testsuite/chakra/chakra_i64.wast -endargs -wasmMathExFilter</compile-flags>
+      <baseline>baselines/chakra_extends_i64.baseline</baseline>
+      <compile-flags>-wasm -args chakra/chakra_extends_i64.wast -endargs -wasmSignExtends</compile-flags>
+    </default>
+  </test>
+  <test>
+    <default>
+      <files>spec.js</files>
+      <baseline>baselines/chakra_extends_i64.baseline</baseline>
+      <compile-flags>-wasm -args chakra/chakra_extends_i64.wast -endargs -nonative -wasmSignExtends</compile-flags>
+      <tags>exclude_dynapogo</tags>
     </default>
   </test>
   <test>
     <default>
       <files>spec.js</files>
       <baseline>baselines/chakra_i32.baseline</baseline>
-      <compile-flags>-wasm -args testsuite/chakra/chakra_i32.wast -endargs</compile-flags>
+      <compile-flags>-wasm -args chakra/chakra_i32.wast -endargs</compile-flags>
     </default>
   </test>
   <test>
     <default>
       <files>spec.js</files>
       <baseline>baselines/chakra_i32.baseline</baseline>
-      <compile-flags>-wasm -args testsuite/chakra/chakra_i32.wast -endargs -nonative</compile-flags>
+      <compile-flags>-wasm -args chakra/chakra_i32.wast -endargs -nonative</compile-flags>
       <tags>exclude_dynapogo</tags>
     </default>
   </test>
@@ -42,7 +50,29 @@
     <default>
       <files>spec.js</files>
       <baseline>baselines/chakra_i32.baseline</baseline>
-      <compile-flags>-wasm -args testsuite/chakra/chakra_i32.wast -endargs -wasmMathExFilter</compile-flags>
+      <compile-flags>-wasm -args chakra/chakra_i32.wast -endargs -wasmMathExFilter</compile-flags>
+    </default>
+  </test>
+  <test>
+    <default>
+      <files>spec.js</files>
+      <baseline>baselines/chakra_i64.baseline</baseline>
+      <compile-flags>-wasm -args chakra/chakra_i64.wast -endargs</compile-flags>
+    </default>
+  </test>
+  <test>
+    <default>
+      <files>spec.js</files>
+      <baseline>baselines/chakra_i64.baseline</baseline>
+      <compile-flags>-wasm -args chakra/chakra_i64.wast -endargs -nonative</compile-flags>
+      <tags>exclude_dynapogo</tags>
+    </default>
+  </test>
+  <test>
+    <default>
+      <files>spec.js</files>
+      <baseline>baselines/chakra_i64.baseline</baseline>
+      <compile-flags>-wasm -args chakra/chakra_i64.wast -endargs -wasmMathExFilter</compile-flags>
     </default>
   </test>
   <test>
@@ -1141,4 +1171,34 @@
       <tags>exclude_dynapogo</tags>
     </default>
   </test>
+  <test>
+    <default>
+      <files>spec.js</files>
+      <baseline>baselines/extends_i32.baseline</baseline>
+      <compile-flags>-wasm -args features/extends/extends_i32.wast -endargs -wasmSignExtends</compile-flags>
+    </default>
+  </test>
+  <test>
+    <default>
+      <files>spec.js</files>
+      <baseline>baselines/extends_i32.baseline</baseline>
+      <compile-flags>-wasm -args features/extends/extends_i32.wast -endargs -nonative -wasmSignExtends</compile-flags>
+      <tags>exclude_dynapogo</tags>
+    </default>
+  </test>
+  <test>
+    <default>
+      <files>spec.js</files>
+      <baseline>baselines/extends_i64.baseline</baseline>
+      <compile-flags>-wasm -args features/extends/extends_i64.wast -endargs -wasmSignExtends</compile-flags>
+    </default>
+  </test>
+  <test>
+    <default>
+      <files>spec.js</files>
+      <baseline>baselines/extends_i64.baseline</baseline>
+      <compile-flags>-wasm -args features/extends/extends_i64.wast -endargs -nonative -wasmSignExtends</compile-flags>
+      <tags>exclude_dynapogo</tags>
+    </default>
+  </test>
 </regress-exe>

+ 7 - 0
test/wasm/rlexe.xml

@@ -266,6 +266,13 @@
     <tags>exclude_jshost,exclude_win7,exclude_interpreted</tags>
   </default>
 </test>
+<test>
+  <default>
+    <files>signextend.js</files>
+    <compile-flags>-wasm -args --no-verbose -endargs</compile-flags>
+    <tags>exclude_jshost,exclude_win7,exclude_xplat</tags>
+  </default>
+</test>
 <test>
   <default>
     <files>unsigned.js</files>

+ 77 - 0
test/wasm/signextend.js

@@ -0,0 +1,77 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft Corporation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+/* global assert,testRunner */ // eslint rule
+WScript.Flag("-WasmSignExtends");
+WScript.Flag("-WasmI64");
+WScript.LoadScriptFile("../UnitTestFrameWork/UnitTestFrameWork.js");
+
+function makeCSETest(type, op1, op2, tests) {
+  return {
+    name: op2 ? `${type}.${op1}/${type}.${op2} no cse` : `${type}.${op1} cse`,
+    body() {
+      const buf = WebAssembly.wabt.convertWast2Wasm(`
+(module
+  (func (export "func") (param $x ${type}) (result ${type})
+    (${type}.sub
+      (${type}.${op1} (get_local $x))
+      (${type}.${op2 || op1} (get_local $x))
+    )
+  )
+)`);
+      const mod = new WebAssembly.Module(buf);
+      const {exports: {func}} = new WebAssembly.Instance(mod);
+      const name = op2 ? `${op1}/${op2}` : op1;
+      const assertion = op2 ? assert.areNotEqual : assert.areEqual;
+      let j = 0;
+      for (let i = 0; i < tests.length * 10; ++i) {
+        const input = tests[j++ % tests.length];
+        const result = func(input);
+        if (type === "i64") {
+          assertion(0, result.low|0, `${type}.${name}(0x${input.toString(16)}) = 0x${(result.low|0).toString(16)} low`);
+          if (!op2) {
+            assertion(0, result.high|0, `${type}.${name}(0x${input.toString(16)}) = 0x${(result.high|0).toString(16)} high`);
+          }
+        } else {
+          assertion(0, result|0, `${type}.${name}(0x${input.toString(16)})`);
+        }
+      }
+    }
+  };
+}
+
+const tests = [
+  makeCSETest("i32", "extend8_s" , null, [0xFF, 1, 0x1FF]),
+  makeCSETest("i32", "extend16_s", null, [0xFF, 1, 0xFFFF, 0x1FFFF]),
+  makeCSETest("i64", "extend8_s" , null, [0xFF, 1, 0x1FF]),
+  makeCSETest("i64", "extend16_s", null, [0xFF, 1, 0xFFFF, 0x1FFFF]),
+  makeCSETest("i64", "extend32_s", null, [0xFF, 1, 0xFFFF, 0xFFFFFFFF, {low: 0xFFFFFFFF, high: 1}]),
+
+  makeCSETest("i32", "extend8_s" , "extend16_s", [0xFF, 0x1FF, 0xFF4F, 0x1FF4F]),
+  makeCSETest("i32", "extend16_s", "extend8_s" , [0xFF, 0x1FF, 0xFF4F, 0x1FF4F]),
+
+  makeCSETest("i64", "extend8_s" , "extend16_s", [0xFF, 0x1FF, 0xFF4F, 0x1FF4F]),
+  makeCSETest("i64", "extend8_s" , "extend32_s", [0xFF, 0x1FF, 0xFF4F, 0x1FF4F]),
+  makeCSETest("i64", "extend16_s", "extend8_s" , [0xFF, 0xFF4F, 0x1FF4F]),
+  makeCSETest("i64", "extend16_s", "extend32_s", [0xFFFF, 0x14FFF]),
+  makeCSETest("i64", "extend32_s", "extend8_s" , [0xFF, 0xFF4F, 0xFFF4FFFF, {low: 0x4FFFFFFF, high: 1}]),
+  makeCSETest("i64", "extend32_s", "extend16_s", [0xFF4F, 0xFFF4FFFF, {low: 0x4FFFFFFF, high: 1}]),
+];
+
+WScript.LoadScriptFile("../UnitTestFrameWork/yargs.js");
+const argv = yargsParse(WScript.Arguments, {
+  boolean: ["verbose"],
+  number: ["start", "end"],
+  default: {
+    verbose: true,
+    start: 0,
+    end: tests.length
+  }
+}).argv;
+
+const todoTests = tests
+  .slice(argv.start, argv.end);
+
+testRunner.run(todoTests, {verbose: argv.verbose});