Bug 811314 - BaselineCompiler: Implement Bitwise ops. r=jandem
authorTom Schuster <evilpies@gmail.com>
Wed, 21 Nov 2012 22:36:41 +0100
changeset 113892 492d0e164f6afd4f33bec3aa12caef05bf7b4ad5
parent 113891 37ad58ffe32b8173ff26a975eae0d9dd6639c316
child 114110 9a4f3dcf37302f068a4813670dea683442e53b90
push id1366
push userevilpies@gmail.com
push dateWed, 21 Nov 2012 21:38:58 +0000
reviewersjandem
bugs811314
milestone20.0a1
Bug 811314 - BaselineCompiler: Implement Bitwise ops. r=jandem
js/src/ion/BaselineCompiler.cpp
js/src/ion/BaselineCompiler.h
js/src/ion/BaselineIC.cpp
js/src/ion/BaselineIC.h
js/src/ion/arm/BaselineIC-arm.cpp
js/src/ion/shared/Assembler-x86-shared.h
js/src/ion/x64/BaselineIC-x64.cpp
js/src/ion/x86/BaselineIC-x86.cpp
--- a/js/src/ion/BaselineCompiler.cpp
+++ b/js/src/ion/BaselineCompiler.cpp
@@ -475,26 +475,66 @@ BaselineCompiler::storeValue(const Stack
         masm.storeValue(scratch, dest);
         break;
       default:
         JS_NOT_REACHED("Invalid kind");
     }
 }
 
 bool
+BaselineCompiler::emit_JSOP_BITOR()
+{
+    return emitBinaryArith();
+}
+
+bool
+BaselineCompiler::emit_JSOP_BITXOR()
+{
+    return emitBinaryArith();
+}
+
+bool
+BaselineCompiler::emit_JSOP_BITAND()
+{
+    return emitBinaryArith();
+}
+
+bool
+BaselineCompiler::emit_JSOP_LSH()
+{
+    return emitBinaryArith();
+}
+
+bool
+BaselineCompiler::emit_JSOP_RSH()
+{
+    return emitBinaryArith();
+}
+
+bool
+BaselineCompiler::emit_JSOP_URSH()
+{
+    return emitBinaryArith();
+}
+
+bool
 BaselineCompiler::emit_JSOP_ADD()
 {
+    return emitBinaryArith();
+}
+
+bool
+BaselineCompiler::emitBinaryArith()
+{
     // Allocate IC entry and stub.
     ICBinaryArith_Fallback::Compiler stubCompiler(cx);
     ICEntry *entry = allocateICEntry(stubCompiler.getStub());
     if (!entry)
         return false;
 
-    // CODEGEN
-    
     // Keep top JSStack value in R0 and R2
     frame.popRegsAndSync(2);
 
     // Call IC
     CodeOffsetLabel patchOffset;
     EmitCallIC(&patchOffset, masm);
     entry->setReturnOffset(masm.currentOffset());
     if (!addICLoadLabel(patchOffset))
--- a/js/src/ion/BaselineCompiler.h
+++ b/js/src/ion/BaselineCompiler.h
@@ -52,16 +52,22 @@ namespace ion {
     _(JSOP_ZERO)               \
     _(JSOP_ONE)                \
     _(JSOP_INT8)               \
     _(JSOP_INT32)              \
     _(JSOP_UINT16)             \
     _(JSOP_UINT24)             \
     _(JSOP_DOUBLE)             \
     _(JSOP_STRING)             \
+    _(JSOP_BITOR)              \
+    _(JSOP_BITXOR)             \
+    _(JSOP_BITAND)             \
+    _(JSOP_LSH)                \
+    _(JSOP_RSH)                \
+    _(JSOP_URSH)               \
     _(JSOP_ADD)                \
     _(JSOP_LT)                 \
     _(JSOP_GT)                 \
     _(JSOP_GETELEM)            \
     _(JSOP_SETELEM)            \
     _(JSOP_GETLOCAL)           \
     _(JSOP_CALLLOCAL)          \
     _(JSOP_SETLOCAL)           \
@@ -98,16 +104,18 @@ class BaselineCompiler : public Baseline
 
     void storeValue(const StackValue *source, const Address &dest,
                     const ValueOperand &scratch);
 
 #define EMIT_OP(op) bool emit_##op();
     OPCODE_LIST(EMIT_OP)
 #undef EMIT_OP
 
+    // JSOP_BITXOR, JSOP_LSH, JSOP_ADD etc.
+    bool emitBinaryArith();
     // Handles JSOP_LT, JSOP_GT, and friends
     bool emitCompare();
     bool emitToBoolean();
     bool emitTest(bool branchIfTrue);
     bool emitAndOr(bool branchIfTrue);
     bool emitCall();
 };
 
--- a/js/src/ion/BaselineIC.cpp
+++ b/js/src/ion/BaselineIC.cpp
@@ -9,16 +9,18 @@
 #include "BaselineCompiler.h"
 #include "BaselineHelpers.h"
 #include "BaselineIC.h"
 #include "IonLinker.h"
 #include "IonSpewer.h"
 #include "VMFunctions.h"
 #include "IonFrames-inl.h"
 
+#include "jsinterpinlines.h"
+
 namespace js {
 namespace ion {
 
 IonCode *
 ICStubCompiler::getStubCode()
 {
     IonCompartment *ion = cx->compartment->ionCompartment();
 
@@ -250,40 +252,92 @@ DoBinaryArithFallback(JSContext *cx, ICB
     JSOp op = JSOp(*stub->icEntry()->pc(script));
     switch(op) {
       case JSOP_ADD: {
         // Do an add.
         if (!AddValues(cx, script, stub->icEntry()->pc(script), lhs, rhs, ret.address()))
             return false;
         break;
       }
+      case JSOP_BITOR: {
+        int32_t result;
+        if (!BitOr(cx, lhs, rhs, &result))
+            return false;
+        ret.setInt32(result);
+        break;
+      }
+      case JSOP_BITXOR: {
+        int32_t result;
+        if (!BitXor(cx, lhs, rhs, &result))
+            return false;
+        ret.setInt32(result);
+        break;
+      }
+      case JSOP_BITAND: {
+        int32_t result;
+        if (!BitAnd(cx, lhs, rhs, &result))
+            return false;
+        ret.setInt32(result);
+        break;
+      }
+      case JSOP_LSH: {
+        int32_t result;
+        if (!BitLsh(cx, lhs, rhs, &result))
+            return false;
+        ret.setInt32(result);
+        break;
+      }
+      case JSOP_RSH: {
+        int32_t result;
+        if (!BitRsh(cx, lhs, rhs, &result))
+            return false;
+        ret.setInt32(result);
+        break;
+      }
+      case JSOP_URSH: {
+        if (!UrshOperation(cx, script, stub->icEntry()->pc(script), lhs, rhs, ret.address()))
+            return false;
+        break;
+      }
       default:
-        JS_ASSERT(!"Unhandled baseline compare op");
+        JS_NOT_REACHED("Unhandled baseline arith op");
         return false;
     }
 
     // Check to see if a new stub should be generated.
     if (stub->numOptimizedStubs() >= ICBinaryArith_Fallback::MAX_OPTIMIZED_STUBS) {
         // TODO: Discard all stubs in this IC and replace with inert megamorphic stub.
         // But for now we just bail.
         return true;
     }
 
+    if (!lhs.isInt32() || !rhs.isInt32())
+        return true;
+
     // Try to generate new stubs.
-    if (lhs.isInt32()) {
-        if (rhs.isInt32()) {
-            ICBinaryArith_Int32::Compiler compilerInt32(cx, op);
-            ICStub *int32Stub = compilerInt32.getStub();
-            if (!int32Stub)
-                return false;
-
-            stub->addNewStub(int32Stub);
-        }
+    switch(op) {
+      case JSOP_BITOR:
+      case JSOP_BITXOR:
+      case JSOP_BITAND:
+      case JSOP_LSH:
+      case JSOP_RSH:
+      case JSOP_URSH:
+      case JSOP_ADD: {
+        bool allowDouble = ret.isDouble();
+        ICBinaryArith_Int32::Compiler compilerInt32(cx, op, allowDouble);
+        ICStub *int32Stub = compilerInt32.getStub();
+        if (!int32Stub)
+            return false;
+        stub->addNewStub(int32Stub);
+        break;
+      }
+      default:
+        JS_NOT_REACHED("Unhandled baseline arith op");
+        return false;
     }
-
     return true;
 }
 
 typedef bool (*DoBinaryArithFallbackFn)(JSContext *, ICBinaryArith_Fallback *, HandleValue, HandleValue,
                                         MutableHandleValue);
 static const VMFunction DoBinaryArithFallbackInfo =
     FunctionInfo<DoBinaryArithFallbackFn>(DoBinaryArithFallback);
 
--- a/js/src/ion/BaselineIC.h
+++ b/js/src/ion/BaselineIC.h
@@ -560,16 +560,18 @@ class ICToNumber_Fallback : public ICFal
         ICStub *getStub() {
             return ICToNumber_Fallback::New(getStubCode());
         }
     };
 };
 
 // BinaryArith
 //      JSOP_ADD
+//      JSOP_BITAND, JSOP_BITXOR, JSOP_BITOR
+//      JSOP_LSH, JSOP_RSH, JSOP_URSH
 
 class ICBinaryArith_Fallback : public ICFallbackStub
 {
     ICBinaryArith_Fallback(IonCode *stubCode)
       : ICFallbackStub(BinaryArith_Fallback, stubCode) {}
 
   public:
     static const uint32_t MAX_OPTIMIZED_STUBS = 8;
@@ -599,23 +601,33 @@ class ICBinaryArith_Int32 : public ICStu
       : ICStub(BinaryArith_Int32, stubCode) {}
 
   public:
     static inline ICBinaryArith_Int32 *New(IonCode *code) {
         return new ICBinaryArith_Int32(code);
     }
 
     // Compiler for this stub kind.
-    class Compiler : public ICMultiStubCompiler {
+    class Compiler : public ICStubCompiler {
       protected:
+        JSOp op_;
+        bool allowDouble_;
+
         bool generateStubCode(MacroAssembler &masm);
 
+        // Stub keys shift-stubs need to encode the kind, the JSOp and if we allow doubles.
+        virtual int32_t getKey() const {
+            return (static_cast<int32_t>(kind) | (static_cast<int32_t>(op_) << 16) |
+                    (static_cast<int32_t>(allowDouble_) << 24));
+        }
+
       public:
-        Compiler(JSContext *cx, JSOp op)
-          : ICMultiStubCompiler(cx, ICStub::BinaryArith_Int32, op) {}
+        Compiler(JSContext *cx, JSOp op, bool allowDouble)
+          : ICStubCompiler(cx, ICStub::BinaryArith_Int32),
+            op_(op), allowDouble_(allowDouble) {}
 
         ICStub *getStub() {
             return ICBinaryArith_Int32::New(getStubCode());
         }
     };
 };
 
 // GetElem
--- a/js/src/ion/arm/BaselineIC-arm.cpp
+++ b/js/src/ion/arm/BaselineIC-arm.cpp
@@ -28,17 +28,17 @@ ICCompare_Int32::Compiler::generateStubC
     switch(op) {
       case JSOP_LT:
         cond = Assembler::LessThan;
         break;
       case JSOP_GT:
         cond = Assembler::GreaterThan;
         break;
       default:
-        JS_ASSERT(!"Unhandled op for ICCompare_Int32!");
+        JS_NOT_REACHED("Unhandled op for ICCompare_Int32.");
         return false;
     }
 
     // Guard that R0 is an integer and R1 is an integer.
     Label failure;
     masm.branchTestInt32(Assembler::NotEqual, R0, &failure);
     masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
 
@@ -66,32 +66,57 @@ ICBinaryArith_Int32::Compiler::generateS
     // Guard that R0 is an integer and R1 is an integer.
     Label failure;
     masm.branchTestInt32(Assembler::NotEqual, R0, &failure);
     masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
 
     // Add R0 and R1.  Don't need to explicitly unbox, just use R2's payloadReg.
     Register scratchReg = R2.payloadReg();
 
-    switch(op) {
+    switch(op_) {
       case JSOP_ADD:
         masm.ma_add(R0.payloadReg(), R1.payloadReg(), scratchReg);
+
+        // Just jump to failure on overflow.  R0 and R1 are preserved, so we can just jump to
+        // the next stub.
+        masm.j(Assembler::Overflow, &failure);
+
+        // Box the result and return.  We know R0.typeReg() already contains the integer
+        // tag, so we just need to move the result value into place.
+        masm.movePtr(scratchReg, R0.payloadReg());
         break;
+      case JSOP_BITOR:
+        masm.ma_orr(R1.payloadReg(), R0.payloadReg(), R0.payloadReg());
+        break;
+      case JSOP_BITXOR:
+        masm.ma_eor(R1.payloadReg(), R0.payloadReg(), R0.payloadReg());
+        break;
+      case JSOP_BITAND:
+        masm.ma_and(R1.payloadReg(), R0.payloadReg(), R0.payloadReg());
+        break;
+      case JSOP_LSH:
+        // ARM will happily try to shift by more than 0x1f.
+        masm.ma_and(Imm32(0x1F), R1.payloadReg(), R1.payloadReg());
+        masm.ma_lsl(R1.payloadReg(), R0.payloadReg(), R0.payloadReg());
+        break;
+      case JSOP_RSH:
+        masm.ma_and(Imm32(0x1F), R1.payloadReg(), R1.payloadReg());
+        masm.ma_asr(R1.payloadReg(), R0.payloadReg(), R0.payloadReg());
+      case JSOP_URSH:
+        masm.ma_and(Imm32(0x1F), R1.payloadReg(), scratchReg);
+        masm.ma_lsr(scratchReg, R0.payloadReg(), scratchReg);
+        masm.ma_cmp(scratchReg, Imm32(0));
+        masm.j(Assembler::LessThan, &failure);
+        // Move result for return.
+        masm.movePtr(scratchReg, R0.payloadReg());
       default:
-        JS_ASSERT(!"Unhandled op for BinaryArith_Int32!");
+        JS_NOT_REACHED("Unhandled op for BinaryArith_Int32.");
         return false;
     }
 
-    // Just jump to failure on overflow.  R0 and R1 are preserved, so we can just jump to
-    // the next stub.
-    masm.j(Assembler::Overflow, &failure);
-
-    // Box the result and return.  We know R0.typeReg() already contains the integer
-    // tag, so we just need to move the result value into place.
-    masm.movePtr(scratchReg, R0.payloadReg());
     EmitReturnFromIC(masm);
 
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
 
     return true;
 }
--- a/js/src/ion/shared/Assembler-x86-shared.h
+++ b/js/src/ion/shared/Assembler-x86-shared.h
@@ -792,16 +792,19 @@ class AssemblerX86Shared
             break;
           case Operand::REG_DISP:
             masm.xorl_im(imm.value, op.disp(), op.base());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
+    void andl(const Register &src, const Register &dest) {
+        masm.andl_rr(src.code(), dest.code());
+    }
     void andl(Imm32 imm, const Register &dest) {
         masm.andl_ir(imm.value, dest.code());
     }
     void andl(Imm32 imm, const Operand &op) {
         switch (op.kind()) {
           case Operand::REG:
             masm.andl_ir(imm.value, op.reg());
             break;
--- a/js/src/ion/x64/BaselineIC-x64.cpp
+++ b/js/src/ion/x64/BaselineIC-x64.cpp
@@ -58,38 +58,92 @@ ICCompare_Int32::Compiler::generateStubC
 bool
 ICBinaryArith_Int32::Compiler::generateStubCode(MacroAssembler &masm)
 {
     // Guard that R0 is an integer and R1 is an integer.
     Label failure;
     masm.branchTestInt32(Assembler::NotEqual, R0, &failure);
     masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
 
-    // Add R0 and R1.  Don't need to explicitly unbox, just use the TailCallReg which
-    // should be available.
-    masm.unboxNonDouble(R0, rdx);
-    masm.unboxNonDouble(R1, ScratchReg);
+    Label revertRegister;
+    switch(op_) {
+      case JSOP_ADD:
+        masm.unboxInt32(R1, ExtractTemp0);
+        // Just jump to failure on overflow. R0 and R1 are preserved, so we can just jump to
+        // the next stub.
+        masm.addl(R0.valueReg(), ExtractTemp0);
+        masm.j(Assembler::Overflow, &failure);
 
-    switch(op) {
-      case JSOP_ADD:
-        masm.addl(rdx, ScratchReg);
+        // Box the result
+        masm.boxValue(JSVAL_TYPE_INT32, ExtractTemp0, R0.valueReg());
+        break;
+      case JSOP_BITOR:
+        // We can overide R0, because the instruction is unfailable.
+        // Because the tag bits are the same, we don't need to retag.
+        masm.orq(R1.valueReg(), R0.valueReg());
+        break;
+      case JSOP_BITXOR:
+        masm.xorl(R1.valueReg(), R0.valueReg());
+        masm.boxValue(JSVAL_TYPE_INT32, R0.valueReg(), R0.valueReg());
+        break;
+      case JSOP_BITAND:
+        masm.andq(R1.valueReg(), R0.valueReg());
+        break;
+      case JSOP_LSH:
+        masm.unboxInt32(R0, ExtractTemp0);
+        masm.unboxInt32(R1, ecx); // Unboxing R1 to ecx, clobbers R0.
+        masm.shll_cl(ExtractTemp0);
+        masm.boxValue(JSVAL_TYPE_INT32, ExtractTemp0, R0.valueReg());
+        break;
+      case JSOP_RSH:
+        masm.unboxInt32(R0, ExtractTemp0);
+        masm.unboxInt32(R1, ecx);
+        masm.sarl_cl(ExtractTemp0);
+        masm.boxValue(JSVAL_TYPE_INT32, ExtractTemp0, R0.valueReg());
+        break;
+      case JSOP_URSH:
+        if (!allowDouble_)
+            masm.movq(R0.valueReg(), ScratchReg);
+
+        masm.unboxInt32(R0, ExtractTemp0);
+        masm.unboxInt32(R1, ecx); // This clobbers R0
+
+        masm.shrl_cl(ExtractTemp0);
+        masm.testl(ExtractTemp0, ExtractTemp0);
+        if (allowDouble_) {
+            Label toUint;
+            masm.j(Assembler::Signed, &toUint);
+
+            // Box and return.
+            masm.boxValue(JSVAL_TYPE_INT32, ExtractTemp0, R0.valueReg());
+            EmitReturnFromIC(masm);
+
+            masm.bind(&toUint);
+            masm.convertUInt32ToDouble(ExtractTemp0, ScratchFloatReg);
+            masm.boxDouble(ScratchFloatReg, R0);
+        } else {
+            masm.j(Assembler::Signed, &revertRegister);
+            masm.boxValue(JSVAL_TYPE_INT32, ExtractTemp0, R0.valueReg());
+        }
         break;
       default:
-        JS_ASSERT(!"Unhandled op for BinaryArith_Int32!");
-        return false;
+        JS_NOT_REACHED("Unhandled op in BinaryArith_Int32");
+        return NULL;
     }
 
-    // Just jump to failure on overflow.  R0 and R1 are preserved, so we can just jump to
-    // the next stub.
-    masm.j(Assembler::Overflow, &failure);
-
-    // Box the result and return
-    masm.boxValue(JSVAL_TYPE_INT32, ScratchReg, R0.valueReg());
+    // Return from stub.
     EmitReturnFromIC(masm);
 
+    // Revert the content of R0 in the fallible >>> case.
+    if (op_ == JSOP_URSH && !allowDouble_) {
+        masm.bind(&revertRegister);
+        // Restore tag and payload.
+        masm.movq(ScratchReg, R0.valueReg());
+        // Fall through to failure.
+    }
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
 
     return true;
 }
 
 
--- a/js/src/ion/x86/BaselineIC-x86.cpp
+++ b/js/src/ion/x86/BaselineIC-x86.cpp
@@ -62,34 +62,91 @@ ICBinaryArith_Int32::Compiler::generateS
     Label failure;
     masm.branchTestInt32(Assembler::NotEqual, R0, &failure);
     masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
 
     // Add R0 and R1.  Don't need to explicitly unbox, just use the TailCallReg which
     // should be available.
     Register scratchReg = BaselineTailCallReg;
 
-    switch(op) {
+    Label revertRegister;
+    switch(op_) {
       case JSOP_ADD:
+        // Add R0 and R1.  Don't need to explicitly unbox.
         masm.movl(R1.payloadReg(), scratchReg);
         masm.addl(R0.payloadReg(), scratchReg);
+
+        // Just jump to failure on overflow.  R0 and R1 are preserved, so we can just jump to
+        // the next stub.
+        masm.j(Assembler::Overflow, &failure);
+
+        // Just overwrite the payload, the tag is still fine.
+        masm.movl(scratchReg, R0.payloadReg());
+        break;
+      case JSOP_BITOR:
+        // We can overide R0, because the instruction is unfailable.
+        // The R0.typeReg() is also still intact.
+        masm.orl(R1.payloadReg(), R0.payloadReg());
+        break;
+      case JSOP_BITXOR:
+        masm.xorl(R1.payloadReg(), R0.payloadReg());
+        break;
+      case JSOP_BITAND:
+        masm.andl(R1.payloadReg(), R0.payloadReg());
+        break;
+      case JSOP_LSH:
+        // RHS needs to be in ecx for shift operations.
+        JS_ASSERT(R0.typeReg() == ecx);
+        masm.movl(R1.payloadReg(), ecx);
+        masm.shll_cl(R0.payloadReg());
+        // We need to tag again, because we overwrote it.
+        masm.tagValue(JSVAL_TYPE_INT32, R0.payloadReg(), R0);
+        break;
+      case JSOP_RSH:
+        masm.movl(R1.payloadReg(), ecx);
+        masm.sarl_cl(R0.payloadReg());
+        masm.tagValue(JSVAL_TYPE_INT32, R0.payloadReg(), R0);
+        break;
+      case JSOP_URSH:
+        if (!allowDouble_)
+            masm.movl(R0.payloadReg(), scratchReg);
+
+        masm.movl(R1.payloadReg(), ecx);
+        masm.shrl_cl(R0.payloadReg());
+        masm.testl(R0.payloadReg(), R0.payloadReg());
+        if (allowDouble_) {
+            Label toUint;
+            masm.j(Assembler::Signed, &toUint);
+
+            // Box and return.
+            masm.tagValue(JSVAL_TYPE_INT32, R0.payloadReg(), R0);
+            EmitReturnFromIC(masm);
+
+            masm.bind(&toUint);
+            masm.convertUInt32ToDouble(R0.payloadReg(), ScratchFloatReg);
+            masm.boxDouble(ScratchFloatReg, R0);
+        } else {
+            masm.j(Assembler::Signed, &revertRegister);
+            masm.tagValue(JSVAL_TYPE_INT32, R0.payloadReg(), R0);
+        }
         break;
       default:
-        JS_ASSERT(!"Unhandled op for BinaryArith_Int32!");
-        return false;
+       JS_NOT_REACHED("Unhandled op for BinaryArith_Int32.  ");
+       return NULL;
     }
 
-    // Just jump to failure on overflow.  R0 and R1 are preserved, so we can just jump to
-    // the next stub.
-    masm.j(Assembler::Overflow, &failure);
-
-    // Box the result and return
-    masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0);
+    // Return.
     EmitReturnFromIC(masm);
 
+    // Revert the content of R0 in the fallible >>> case.
+    if (op_ == JSOP_URSH && !allowDouble_) {
+        masm.bind(&revertRegister);
+        masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0);
+        // Fall through to failure.
+    }
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
 
     return true;
 }