Bug 1252432 part 2 - Implement wasm i64.extend_s and i64.extend_u. r=bbouvier
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 04 Mar 2016 13:57:44 +0100
changeset 323122 3231cc3fd19e28e9718716a8da85041c262a3be8
parent 323121 fa16519da919a07c029abac60d328564ee8e73d3
child 323123 36a77fdd0533553090e6ff53809d1b31dd23bdac
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1252432
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1252432 part 2 - Implement wasm i64.extend_s and i64.extend_u. r=bbouvier
js/src/asmjs/Wasm.cpp
js/src/asmjs/WasmIonCompile.cpp
js/src/jit-test/tests/wasm/basic-conversion.js
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/shared/LIR-shared.h
js/src/jit/shared/LOpcodes-shared.h
js/src/jit/x64/Assembler-x64.h
js/src/jit/x64/CodeGenerator-x64.cpp
js/src/jit/x64/CodeGenerator-x64.h
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -602,18 +602,17 @@ DecodeExpr(FunctionDecoder& f, ExprType 
         return DecodeConversionOperator(f, expected, ExprType::I32, ExprType::F32);
       case Expr::I32ReinterpretF32:
         return f.fail("NYI: reinterpret");
       case Expr::I32TruncSF64:
       case Expr::I32TruncUF64:
         return DecodeConversionOperator(f, expected, ExprType::I32, ExprType::F64);
       case Expr::I64ExtendSI32:
       case Expr::I64ExtendUI32:
-        return f.fail("NYI: i64") &&
-               DecodeConversionOperator(f, expected, ExprType::I64, ExprType::I32);
+        return DecodeConversionOperator(f, expected, ExprType::I64, ExprType::I32);
       case Expr::I64TruncSF32:
       case Expr::I64TruncUF32:
         return f.fail("NYI: i64") &&
                DecodeConversionOperator(f, expected, ExprType::I64, ExprType::F32);
       case Expr::I64TruncSF64:
       case Expr::I64TruncUF64:
       case Expr::I64ReinterpretF64:
         return f.fail("NYI: i64") &&
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -447,16 +447,25 @@ class FunctionCompiler
     {
         if (inDeadCode())
             return nullptr;
         T* ins = T::NewAsmJS(alloc(), op);
         curBlock_->add(ins);
         return ins;
     }
 
+    MDefinition* extendI32(MDefinition* op, bool isUnsigned)
+    {
+        if (inDeadCode())
+            return nullptr;
+        MExtendInt32ToInt64* ins = MExtendInt32ToInt64::NewAsmJS(alloc(), op, isUnsigned);
+        curBlock_->add(ins);
+        return ins;
+    }
+
     MDefinition* compare(MDefinition* lhs, MDefinition* rhs, JSOp op, MCompare::CompareType type)
     {
         if (inDeadCode())
             return nullptr;
         MCompare* ins = MCompare::NewAsmJS(alloc(), lhs, rhs, op, type);
         curBlock_->add(ins);
         return ins;
     }
@@ -2281,16 +2290,26 @@ EmitBitwiseNot(FunctionCompiler& f, MDef
     MDefinition* in;
     if (!EmitExpr(f, ExprType::I32, &in))
         return false;
     *def = f.bitwise<MBitNot>(in);
     return true;
 }
 
 static bool
+EmitExtendI32(FunctionCompiler& f, bool isUnsigned, MDefinition** def)
+{
+    MDefinition* in;
+    if (!EmitExpr(f, ExprType::I32, &in))
+        return false;
+    *def = f.extendI32(in, isUnsigned);
+    return true;
+}
+
+static bool
 EmitSimdOp(FunctionCompiler& f, ExprType type, SimdOperation op, SimdSign sign, MDefinition** def)
 {
     switch (op) {
       case SimdOperation::Constructor:
         return EmitSimdCtor(f, type, def);
       case SimdOperation::Fn_extractLane:
         return EmitExtractLane(f, type, sign, def);
       case SimdOperation::Fn_replaceLane:
@@ -2725,16 +2744,19 @@ EmitExpr(FunctionCompiler& f, ExprType t
         return EmitAtomicsLoad(f, def);
       case Expr::I32AtomicsStore:
         return EmitAtomicsStore(f, def);
       case Expr::I32AtomicsBinOp:
         return EmitAtomicsBinOp(f, def);
       // I64
       case Expr::I64Const:
         return EmitLiteral(f, ExprType::I64, def);
+      case Expr::I64ExtendSI32:
+      case Expr::I64ExtendUI32:
+        return EmitExtendI32(f, IsUnsigned(op == Expr::I64ExtendUI32), def);
       case Expr::I64Or:
         return EmitBitwise<MBitOr>(f, ExprType::I64, def);
       case Expr::I64And:
         return EmitBitwise<MBitAnd>(f, ExprType::I64, def);
       case Expr::I64Xor:
         return EmitBitwise<MBitXor>(f, ExprType::I64, def);
       case Expr::I64Shl:
         return EmitBitwise<MLsh>(f, ExprType::I64, def);
@@ -2884,18 +2906,16 @@ EmitExpr(FunctionCompiler& f, ExprType t
       // Future opcodes
       case Expr::Select:
       case Expr::F32CopySign:
       case Expr::F32Trunc:
       case Expr::F32Nearest:
       case Expr::F64CopySign:
       case Expr::F64Nearest:
       case Expr::F64Trunc:
-      case Expr::I64ExtendSI32:
-      case Expr::I64ExtendUI32:
       case Expr::I64TruncSF32:
       case Expr::I64TruncSF64:
       case Expr::I64TruncUF32:
       case Expr::I64TruncUF64:
       case Expr::F32ConvertSI64:
       case Expr::F32ConvertUI64:
       case Expr::F64ConvertSI64:
       case Expr::F64ConvertUI64:
--- a/js/src/jit-test/tests/wasm/basic-conversion.js
+++ b/js/src/jit-test/tests/wasm/basic-conversion.js
@@ -9,16 +9,26 @@ function testConversion(resultType, opco
     assertEq(wasmEvalText(`(module
                             (func (param i64) (result ${resultType}) (${resultType}.${opcode}/i64 (get_local 0)))
                             (func (result ${resultType}) (call 0 (i64.const ${op})))
                             (export "" 1))`)(), expect);
     // The same, but now the input is a constant.
     assertEq(wasmEvalText(`(module
                             (func (result ${resultType}) (${resultType}.${opcode}/i64 (i64.const ${op})))
                             (export "" 0))`)(), expect);
+  } else if (resultType === 'i64') {
+    assertEq(wasmEvalText(`(module
+                            (func (param ${paramType}) (result i64) (i64.${opcode}/${paramType} (get_local 0)))
+                            (func (result i32) (i64.eq (i64.const ${expect}) (call 0 (${paramType}.const ${op}))))
+                            (export "" 1))`)(), 1);
+    // The same, but now the input is a constant.
+    assertEq(wasmEvalText(`(module
+                            (func (result i64) (i64.${opcode}/${paramType} (${paramType}.const ${op})))
+                            (func (result i32) (i64.eq (i64.const ${expect}) (call 0)))
+                            (export "" 1))`)(), 1);
   } else {
     assertEq(wasmEvalText('(module (func (param ' + paramType + ') (result ' + resultType + ') (' + resultType + '.' + opcode + '/' + paramType + ' (get_local 0))) (export "" 0))')(op), expect);
   }
 
   // TODO: i64 NYI
   for (var bad of ['i32', 'f32', 'f64']) {
     if (bad != resultType)
       assertErrorMessage(() => wasmEvalText('(module (func (param ' + paramType + ') (result ' + bad + ') (' + resultType + '.' + opcode + '/' + paramType + ' (get_local 0))))'),
@@ -36,34 +46,45 @@ function testConversion(resultType, opco
 if (getBuildConfiguration().x64) {
     testConversion('i32', 'wrap', 'i64', 4294967336, 40);
     testConversion('i32', 'wrap', 'i64', -10, -10);
     testConversion('i32', 'wrap', 'i64', "0xffffffff7fffffff", 0x7fffffff);
     testConversion('i32', 'wrap', 'i64', "0xffffffff00000000", 0);
     testConversion('i32', 'wrap', 'i64', "0xfffffffeffffffff", -1);
     testConversion('i32', 'wrap', 'i64', "0x1234567801abcdef", 0x01abcdef);
     testConversion('i32', 'wrap', 'i64', "0x8000000000000002", 2);
+
+    testConversion('i64', 'extend_s', 'i32', 0, 0);
+    testConversion('i64', 'extend_s', 'i32', 1234, 1234);
+    testConversion('i64', 'extend_s', 'i32', -567, -567);
+    testConversion('i64', 'extend_s', 'i32', 0x7fffffff, "0x000000007fffffff");
+    testConversion('i64', 'extend_s', 'i32', 0x80000000, "0xffffffff80000000");
+
+    testConversion('i64', 'extend_u', 'i32', 0, 0);
+    testConversion('i64', 'extend_u', 'i32', 1234, 1234);
+    testConversion('i64', 'extend_u', 'i32', -567, "0x00000000fffffdc9");
+    testConversion('i64', 'extend_u', 'i32', -1, "0x00000000ffffffff");
+    testConversion('i64', 'extend_u', 'i32', 0x7fffffff, "0x000000007fffffff");
+    testConversion('i64', 'extend_u', 'i32', 0x80000000, "0x0000000080000000");
 } else {
     // Sleeper test: once i64 works on more platforms, remove this if-else.
     try {
         testConversion('i32', 'wrap', 'i64', 4294967336, 40);
         assertEq(0, 1);
     } catch(e) {
         assertEq(e.toString().indexOf("NYI on this platform") >= 0, true);
     }
 }
 
 testConversion('i32', 'trunc_s', 'f32', 40.1, 40);
 testConversion('i32', 'trunc_u', 'f32', 40.1, 40);
 testConversion('i32', 'trunc_s', 'f64', 40.1, 40);
 testConversion('i32', 'trunc_u', 'f64', 40.1, 40);
 //testConversion('i32', 'reinterpret', 'f32', 40.1, 1109419622); // TODO: NYI
 
-//testConversion('i64', 'extend_s', 'i32', -2, -2); / TODO: NYI
-//testConversion('i64', 'extend_u', 'i32', -2, 0xfffffffffffffffc); / TODO: NYI
 //testConversion('i64', 'trunc_s', 'f32', 40.1, 40); // TODO: NYI
 //testConversion('i64', 'trunc_u', 'f32', 40.1, 40); // TODO: NYI
 //testConversion('i64', 'trunc_s', 'f64', 40.1, 40); // TODO: NYI
 //testConversion('i64', 'trunc_u', 'f64', 40.1, 40); // TODO: NYI
 //testConversion('i64', 'reinterpret', 'f64', 40.1, 1109419622); // TODO: NYI
 
 testConversion('f32', 'convert_s', 'i32', 40, 40);
 testConversion('f32', 'convert_u', 'i32', 40, 40);
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2096,16 +2096,22 @@ LIRGenerator::visitTruncateToInt32(MTrun
 
 void
 LIRGenerator::visitWrapInt64ToInt32(MWrapInt64ToInt32* ins)
 {
     define(new(alloc()) LWrapInt64ToInt32(useInt64AtStart(ins->input())), ins);
 }
 
 void
+LIRGenerator::visitExtendInt32ToInt64(MExtendInt32ToInt64* ins)
+{
+    defineInt64(new(alloc()) LExtendInt32ToInt64(useAtStart(ins->input())), ins);
+}
+
+void
 LIRGenerator::visitToString(MToString* ins)
 {
     MDefinition* opd = ins->input();
 
     switch (opd->type()) {
       case MIRType_Null: {
         const JSAtomState& names = GetJitContext()->runtime->names();
         LPointer* lir = new(alloc()) LPointer(names.null);
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -154,16 +154,17 @@ class LIRGenerator : public LIRGenerator
     void visitOsrScopeChain(MOsrScopeChain* object);
     void visitOsrReturnValue(MOsrReturnValue* value);
     void visitOsrArgumentsObject(MOsrArgumentsObject* object);
     void visitToDouble(MToDouble* convert);
     void visitToFloat32(MToFloat32* convert);
     void visitToInt32(MToInt32* convert);
     void visitTruncateToInt32(MTruncateToInt32* truncate);
     void visitWrapInt64ToInt32(MWrapInt64ToInt32* ins);
+    void visitExtendInt32ToInt64(MExtendInt32ToInt64* ins);
     void visitToString(MToString* convert);
     void visitToObjectOrNull(MToObjectOrNull* convert);
     void visitRegExp(MRegExp* ins);
     void visitRegExpMatcher(MRegExpMatcher* ins);
     void visitRegExpTester(MRegExpTester* ins);
     void visitRegExpReplace(MRegExpReplace* ins);
     void visitStringReplace(MStringReplace* ins);
     void visitBinarySharedStub(MBinarySharedStub* ins);
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -3733,16 +3733,29 @@ MWrapInt64ToInt32::foldsTo(TempAllocator
         int64_t c = input->toConstant()->toInt64();
         return MConstant::New(alloc, Int32Value(int32_t(c)));
     }
 
     return this;
 }
 
 MDefinition*
+MExtendInt32ToInt64::foldsTo(TempAllocator& alloc)
+{
+    MDefinition* input = this->input();
+    if (input->isConstant()) {
+        int32_t c = input->toConstant()->toInt32();
+        int64_t res = isUnsigned() ? int64_t(uint32_t(c)) : int64_t(c);
+        return MConstant::NewInt64(alloc, res);
+    }
+
+    return this;
+}
+
+MDefinition*
 MToDouble::foldsTo(TempAllocator& alloc)
 {
     MDefinition* input = getOperand(0);
     if (input->isBox())
         input = input->getOperand(0);
 
     if (input->type() == MIRType_Double)
         return input;
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -5268,16 +5268,51 @@ class MWrapInt64ToInt32
     bool congruentTo(const MDefinition* ins) const override {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 };
 
+class MExtendInt32ToInt64
+  : public MUnaryInstruction,
+    public NoTypePolicy::Data
+{
+    bool isUnsigned_;
+
+    MExtendInt32ToInt64(MDefinition* def, bool isUnsigned)
+      : MUnaryInstruction(def),
+        isUnsigned_(isUnsigned)
+    {
+        setResultType(MIRType_Int64);
+        setMovable();
+    }
+
+  public:
+    INSTRUCTION_HEADER(ExtendInt32ToInt64)
+    static MExtendInt32ToInt64* NewAsmJS(TempAllocator& alloc, MDefinition* def, bool isUnsigned) {
+        return new(alloc) MExtendInt32ToInt64(def, isUnsigned);
+    }
+
+    bool isUnsigned() const { return isUnsigned_; }
+
+    MDefinition* foldsTo(TempAllocator& alloc) override;
+    bool congruentTo(const MDefinition* ins) const override {
+        if (!ins->isExtendInt32ToInt64())
+            return false;
+        if (ins->toExtendInt32ToInt64()->isUnsigned_ != isUnsigned_)
+            return false;
+        return congruentIfOperandsEqual(ins);
+    }
+    AliasSet getAliasSet() const override {
+        return AliasSet::None();
+    }
+};
+
 // Converts a primitive (either typed or untyped) to an int32. If the input is
 // not primitive at runtime, a bailout occurs. If the input cannot be converted
 // to an int32 without loss (i.e. "5.5" or undefined) then a bailout occurs.
 class MToInt32
   : public MUnaryInstruction,
     public ToInt32Policy::Data
 {
     bool canBeNegativeZero_;
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -114,16 +114,17 @@ namespace jit {
     _(GuardString)                                                          \
     _(PolyInlineGuard)                                                      \
     _(AssertRange)                                                          \
     _(ToDouble)                                                             \
     _(ToFloat32)                                                            \
     _(ToInt32)                                                              \
     _(TruncateToInt32)                                                      \
     _(WrapInt64ToInt32)                                                     \
+    _(ExtendInt32ToInt64)                                                   \
     _(ToString)                                                             \
     _(ToObjectOrNull)                                                       \
     _(NewArray)                                                             \
     _(NewArrayCopyOnWrite)                                                  \
     _(NewArrayDynamicLength)                                                \
     _(NewObject)                                                            \
     _(NewTypedObject)                                                       \
     _(NewDeclEnvObject)                                                     \
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -3970,16 +3970,30 @@ class LWrapInt64ToInt32 : public LInstru
 
     static const size_t Input = 0;
 
     explicit LWrapInt64ToInt32(const LInt64Allocation& input) {
         setInt64Operand(Input, input);
     }
 };
 
+class LExtendInt32ToInt64 : public LInstructionHelper<INT64_PIECES, 1, 0>
+{
+  public:
+    LIR_HEADER(ExtendInt32ToInt64)
+
+    explicit LExtendInt32ToInt64(const LAllocation& input) {
+        setOperand(0, input);
+    }
+
+    const MExtendInt32ToInt64* mir() {
+        return mir_->toExtendInt32ToInt64();
+    }
+};
+
 // Convert a boolean value to a string.
 class LBooleanToString : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(BooleanToString)
 
     explicit LBooleanToString(const LAllocation& input) {
         setOperand(0, input);
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -185,16 +185,17 @@
     _(ValueToDouble)                \
     _(ValueToInt32)                 \
     _(ValueToFloat32)               \
     _(DoubleToInt32)                \
     _(Float32ToInt32)               \
     _(TruncateDToInt32)             \
     _(TruncateFToInt32)             \
     _(WrapInt64ToInt32)             \
+    _(ExtendInt32ToInt64)           \
     _(BooleanToString)              \
     _(IntToString)                  \
     _(DoubleToString)               \
     _(ValueToString)                \
     _(ValueToObjectOrNull)          \
     _(Int32x4ToFloat32x4)           \
     _(Float32x4ToInt32x4)           \
     _(Float32x4ToUint32x4)          \
--- a/js/src/jit/x64/Assembler-x64.h
+++ b/js/src/jit/x64/Assembler-x64.h
@@ -442,16 +442,19 @@ class Assembler : public AssemblerX86Sha
         masm.xchgq_rr(src.encoding(), dest.encoding());
     }
 
     void movslq(Register src, Register dest) {
         masm.movslq_rr(src.encoding(), dest.encoding());
     }
     void movslq(const Operand& src, Register dest) {
         switch (src.kind()) {
+          case Operand::REG:
+            masm.movslq_rr(src.reg(), dest.encoding());
+            break;
           case Operand::MEM_REG_DISP:
             masm.movslq_mr(src.disp(), src.base(), dest.encoding());
             break;
           case Operand::MEM_SCALE:
             masm.movslq_mr(src.disp(), src.base(), src.index(), src.scale(), dest.encoding());
             break;
           default:
             MOZ_CRASH("unexpected operand kind");
--- a/js/src/jit/x64/CodeGenerator-x64.cpp
+++ b/js/src/jit/x64/CodeGenerator-x64.cpp
@@ -1095,8 +1095,20 @@ CodeGeneratorX64::visitTruncateFToInt32(
 void
 CodeGeneratorX64::visitWrapInt64ToInt32(LWrapInt64ToInt32* lir)
 {
     const LAllocation* input = lir->getOperand(0);
     Register output = ToRegister(lir->output());
 
     masm.movl(ToOperand(input), output);
 }
+
+void
+CodeGeneratorX64::visitExtendInt32ToInt64(LExtendInt32ToInt64* lir)
+{
+    const LAllocation* input = lir->getOperand(0);
+    Register output = ToRegister(lir->output());
+
+    if (lir->mir()->isUnsigned())
+        masm.movl(ToOperand(input), output);
+    else
+        masm.movslq(ToOperand(input), output);
+}
--- a/js/src/jit/x64/CodeGenerator-x64.h
+++ b/js/src/jit/x64/CodeGenerator-x64.h
@@ -49,16 +49,17 @@ class CodeGeneratorX64 : public CodeGene
     void visitAddI64(LAddI64* lir);
     void visitSubI64(LSubI64* lir);
     void visitMulI64(LMulI64* lir);
     void visitDivOrModI64(LDivOrModI64* lir);
     void visitUDivOrMod64(LUDivOrMod64* lir);
     void visitTruncateDToInt32(LTruncateDToInt32* ins);
     void visitTruncateFToInt32(LTruncateFToInt32* ins);
     void visitWrapInt64ToInt32(LWrapInt64ToInt32* lir);
+    void visitExtendInt32ToInt64(LExtendInt32ToInt64* lir);
     void visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic* ins);
     void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins);
     void visitAsmJSCall(LAsmJSCall* ins);
     void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins);
     void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins);
     void visitAsmJSCompareExchangeHeap(LAsmJSCompareExchangeHeap* ins);
     void visitAsmJSAtomicExchangeHeap(LAsmJSAtomicExchangeHeap* ins);
     void visitAsmJSAtomicBinopHeap(LAsmJSAtomicBinopHeap* ins);