Bug 1438727: [Part 1] Implement a subset of JSOP_ADD in CacheIR r=jandem
authorMatthew Gaudet <mgaudet@mozilla.com>
Thu, 29 Mar 2018 09:09:58 -0400
changeset 483236 50d88eac66ca91857d88457c33540ba46c949730
parent 483235 1e3e3e15772c489a5f37739315c79d96f9b16726
child 483237 cc58162b8a3a7ba5c153c01bb211afd24b4323f9
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1438727
milestone63.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 1438727: [Part 1] Implement a subset of JSOP_ADD in CacheIR r=jandem This patch adds both Ion and Baseline support for ADD when the arguments are doubles or int32. This is implmented as a strangler via the SharedIC, this falls back to the shared IC if there's no attachment in CacheIR. This should allow preservation of performance throughout. To provide clobber safety to the float registers, this patch uses fixed temporaries on LBinaryCache.
js/src/jit/BaselineCacheIRCompiler.cpp
js/src/jit/BaselineIC.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/CacheIRCompiler.cpp
js/src/jit/CacheIRCompiler.h
js/src/jit/CacheIRSpewer.cpp
js/src/jit/CacheIRSpewer.h
js/src/jit/CodeGenerator.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonCacheIRCompiler.cpp
js/src/jit/IonIC.cpp
js/src/jit/IonIC.h
js/src/jit/JitOptions.cpp
js/src/jit/JitOptions.h
js/src/jit/Lowering.cpp
js/src/jit/MIR.h
js/src/jit/SharedIC.cpp
js/src/jit/shared/LIR-shared.h
js/src/jit/shared/Lowering-shared-inl.h
js/src/jit/shared/Lowering-shared.h
js/src/shell/js.cpp
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -2108,16 +2108,17 @@ BaselineCacheIRCompiler::init(CacheKind 
         break;
       case CacheKind::Compare:
       case CacheKind::GetElem:
       case CacheKind::GetPropSuper:
       case CacheKind::SetProp:
       case CacheKind::In:
       case CacheKind::HasOwn:
       case CacheKind::InstanceOf:
+      case CacheKind::BinaryArith:
         MOZ_ASSERT(numInputs == 2);
         allocator.initInputLocation(0, R0);
         allocator.initInputLocation(1, R1);
         break;
       case CacheKind::GetElemSuper:
         MOZ_ASSERT(numInputs == 3);
         allocator.initInputLocation(0, BaselineFrameSlot(0));
         allocator.initInputLocation(1, R0);
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -4928,10 +4928,66 @@ ICUnaryArith_Fallback::Compiler::generat
     // Push arguments.
     masm.pushValue(R0);
     masm.push(ICStubReg);
     pushStubPayload(masm, R0.scratchReg());
 
     return tailCallVM(DoUnaryArithFallbackInfo, masm);
 }
 
+//
+// BinaryArith_Fallback
+//
+// This currently is fused with the SharedIC DoBinaryArithFallback, however,
+// as we deprecate the SharedIC, this will be able to eventually take over.
+//
+// At that point the stub argument will go away, and instead be generated here.
+bool
+DoCacheIRBinaryArithFallback(JSContext* cx, BaselineFrame* frame, ICBinaryArith_Fallback* stub_,
+                             HandleValue lhs, HandleValue rhs, MutableHandleValue ret,
+                             DebugModeOSRVolatileStub<ICBinaryArith_Fallback*>& stub)
+{
+    RootedScript script(cx, frame->script());
+    jsbytecode* pc = stub->icEntry()->pc(script);
+    JSOp op = JSOp(*pc);
+
+    // Ensure we're only generating for an enabled opcode.
+    switch(op) {
+      case JSOP_ADD:
+        break;
+      default:
+        return false; // Fallback to shared IC.
+    }
+
+    FallbackICSpew(cx, stub, "CacheIRBinaryArith(%s,%d,%d)", CodeName[op],
+            int(lhs.isDouble() ? JSVAL_TYPE_DOUBLE : lhs.extractNonDoubleType()),
+            int(rhs.isDouble() ? JSVAL_TYPE_DOUBLE : rhs.extractNonDoubleType()));
+
+    // Check if debug mode toggling made the stub invalid.
+    if (stub.invalid())
+        return true;
+
+    if (ret.isDouble())
+        stub->setSawDoubleResult();
+
+    if (stub->state().maybeTransition())
+        stub->discardStubs(cx);
+
+    if (stub->state().canAttachStub()) {
+        BinaryArithIRGenerator gen(cx, script, pc, stub->state().mode(),
+                                   op, lhs, rhs, ret);
+        if (gen.tryAttachStub()) {
+            bool attached = false;
+            ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
+                                                        BaselineCacheIRStubKind::Regular,
+                                                        ICStubEngine::Baseline, script, stub, &attached);
+            if (newStub)
+                JitSpew(JitSpew_BaselineIC, "  Attached BinaryArith CacheIR stub for %s", CodeName[op]);
+        } else {
+            return false; // Fallback to shared IC.
+        }
+    }
+    return true;
+}
+
+
 } // namespace jit
 } // namespace js
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -5073,8 +5073,100 @@ UnaryArithIRGenerator::tryAttachNumber()
         break;
       default:
         MOZ_CRASH("Unexpected OP");
     }
 
     writer.returnFromIC();
     return true;
 }
+
+BinaryArithIRGenerator::BinaryArithIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode,
+                                               JSOp op, HandleValue lhs, HandleValue rhs, HandleValue res)
+  : IRGenerator(cx, script, pc, CacheKind::BinaryArith, mode),
+    op_(op),
+    lhs_(lhs),
+    rhs_(rhs),
+    res_(res)
+{ }
+
+void
+BinaryArithIRGenerator::trackAttached(const char* name)
+{
+#ifdef JS_CACHEIR_SPEW
+    if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+        sp.opcodeProperty("op", op_);
+        sp.valueProperty("rhs", rhs_);
+        sp.valueProperty("lhs", lhs_);
+    }
+#endif
+}
+
+bool
+BinaryArithIRGenerator::tryAttachStub()
+{
+
+    if (tryAttachInt32())
+        return true;
+    if (tryAttachDouble())
+        return true;
+
+    trackAttached(IRGenerator::NotAttached);
+    return false;
+}
+
+bool
+BinaryArithIRGenerator::tryAttachDouble()
+{
+    if (op_ != JSOP_ADD)
+        return false;
+
+    if (!lhs_.isDouble() || !rhs_.isDouble() || !res_.isDouble())
+        return false;
+
+    if (!cx_->runtime()->jitSupportsFloatingPoint)
+        return false;
+
+    ValOperandId lhsId(writer.setInputOperandId(0));
+    ValOperandId rhsId(writer.setInputOperandId(1));
+
+    writer.guardIsNumber(lhsId);
+    writer.guardIsNumber(rhsId);
+
+    switch (op_) {
+       case JSOP_ADD:
+        writer.doubleAddResult(lhsId, rhsId);
+        trackAttached("BinaryArith.Double.Add");
+        break;
+      default:
+        MOZ_CRASH("Unhandled Op");
+    }
+    writer.returnFromIC();
+    return true;
+}
+
+bool
+BinaryArithIRGenerator::tryAttachInt32()
+{
+    if (op_ != JSOP_ADD)
+        return false;
+
+    if (!lhs_.isInt32() || !rhs_.isInt32())
+        return false;
+
+    ValOperandId lhsId(writer.setInputOperandId(0));
+    ValOperandId rhsId(writer.setInputOperandId(1));
+
+    Int32OperandId lhsIntId = writer.guardIsInt32(lhsId);
+    Int32OperandId rhsIntId = writer.guardIsInt32(rhsId);
+
+    switch (op_) {
+      case JSOP_ADD:
+        writer.int32AddResult(lhsIntId, rhsIntId);
+        trackAttached("BinaryArith.Int32.Add");
+        break;
+      default:
+        MOZ_CRASH("Unhandled op in tryAttachInt32");
+    }
+
+    writer.returnFromIC();
+    return true;
+}
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -161,17 +161,18 @@ class TypedOperandId : public OperandId
     _(In)                   \
     _(HasOwn)               \
     _(TypeOf)               \
     _(InstanceOf)           \
     _(GetIterator)          \
     _(Compare)              \
     _(ToBool)               \
     _(Call)                 \
-    _(UnaryArith)
+    _(UnaryArith)           \
+    _(BinaryArith)
 
 enum class CacheKind : uint8_t
 {
 #define DEFINE_KIND(kind) kind,
     CACHE_IR_KINDS(DEFINE_KIND)
 #undef DEFINE_KIND
 };
 
@@ -285,16 +286,18 @@ extern const char* CacheKindNames[];
     _(CallProxyGetByValueResult)          \
     _(CallProxyHasPropResult)             \
     _(CallObjectHasSparseElementResult)   \
     _(LoadUndefinedResult)                \
     _(LoadBooleanResult)                  \
     _(LoadStringResult)                   \
     _(LoadInstanceOfObjectResult)         \
     _(LoadTypeOfObjectResult)             \
+    _(DoubleAddResult)                    \
+    _(Int32AddResult)                     \
     _(Int32NotResult)                     \
     _(Int32NegationResult)                \
     _(DoubleNegationResult)               \
     _(LoadInt32TruthyResult)              \
     _(LoadDoubleTruthyResult)             \
     _(LoadStringTruthyResult)             \
     _(LoadObjectTruthyResult)             \
     _(LoadValueResult)                    \
@@ -984,16 +987,24 @@ class MOZ_RAII CacheIRWriter : public JS
         buffer_.writeByte(uint32_t(strict));
     }
     void megamorphicHasPropResult(ObjOperandId obj, ValOperandId id, bool hasOwn) {
         writeOpWithOperandId(CacheOp::MegamorphicHasPropResult, obj);
         writeOperandId(id);
         buffer_.writeByte(uint32_t(hasOwn));
     }
 
+    void doubleAddResult(ValOperandId lhsId, ValOperandId rhsId) {
+        writeOpWithOperandId(CacheOp::DoubleAddResult, lhsId);
+        writeOperandId(rhsId);
+    }
+    void int32AddResult(Int32OperandId lhs, Int32OperandId rhs) {
+        writeOpWithOperandId(CacheOp::Int32AddResult, lhs);
+        writeOperandId(rhs);
+    }
     void int32NotResult(Int32OperandId id) {
         writeOpWithOperandId(CacheOp::Int32NotResult, id);
     }
     void int32NegationResult(Int32OperandId id) {
         writeOpWithOperandId(CacheOp::Int32NegationResult, id);
     }
     void doubleNegationResult(ValOperandId val) {
         writeOpWithOperandId(CacheOp::DoubleNegationResult, val);
@@ -1772,12 +1783,32 @@ class MOZ_RAII UnaryArithIRGenerator : p
 
   public:
     UnaryArithIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, ICState::Mode mode,
                           JSOp op, HandleValue val, HandleValue res);
 
     bool tryAttachStub();
 };
 
+class MOZ_RAII BinaryArithIRGenerator : public IRGenerator
+{
+    JSOp op_;
+    HandleValue lhs_;
+    HandleValue rhs_;
+    HandleValue res_;
+
+    void trackAttached(const char* name);
+
+    bool tryAttachInt32();
+    bool tryAttachDouble();
+
+  public:
+    BinaryArithIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, ICState::Mode,
+                           JSOp op, HandleValue lhs, HandleValue rhs, HandleValue res);
+
+    bool tryAttachStub();
+
+};
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_CacheIR_h */
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -83,16 +83,61 @@ CacheRegisterAllocator::useValueRegister
 
       case OperandLocation::Uninitialized:
         break;
     }
 
     MOZ_CRASH();
 }
 
+// Load a value operand directly into a float register. Caller must have
+// guarded isNumber on the provided val.
+void
+CacheRegisterAllocator::loadDouble(MacroAssembler& masm, ValOperandId op, FloatRegister dest)
+{
+    OperandLocation& loc = operandLocations_[op.id()];
+
+    Label failure, done;
+    switch (loc.kind()) {
+      case OperandLocation::ValueReg: {
+        masm.ensureDouble(loc.valueReg(), dest, &failure);
+        break;
+      }
+
+      case OperandLocation::ValueStack: {
+        masm.ensureDouble(valueAddress(masm, &loc), dest, &failure);
+        break;
+      }
+
+      case OperandLocation::BaselineFrame: {
+        Address addr = addressOf(masm, loc.baselineFrameSlot());
+        masm.ensureDouble(addr, dest, &failure);
+        break;
+      }
+
+      case OperandLocation::DoubleReg: {
+        masm.moveDouble(loc.doubleReg(), dest);
+        loc.setDoubleReg(dest);
+        return;
+      }
+
+      case OperandLocation::Constant:
+      case OperandLocation::PayloadStack:
+      case OperandLocation::PayloadReg:
+      case OperandLocation::Uninitialized:
+        MOZ_CRASH("Unhandled operand type in loadDouble");
+        return;
+    }
+    masm.jump(&done);
+    masm.bind(&failure);
+    masm.assumeUnreachable("Missing guard allowed non-number to hit loadDouble");
+    masm.bind(&done);
+}
+
+
 ValueOperand
 CacheRegisterAllocator::useFixedValueRegister(MacroAssembler& masm, ValOperandId valId,
                                               ValueOperand reg)
 {
     allocateFixedValueRegister(masm, reg);
 
     OperandLocation& loc = operandLocations_[valId.id()];
     switch (loc.kind()) {
@@ -663,16 +708,23 @@ CacheRegisterAllocator::popPayload(Macro
         MOZ_ASSERT(loc->payloadStack() < stackPushed_);
         masm.loadPtr(Address(masm.getStackPointer(), stackPushed_ - loc->payloadStack()), dest);
         masm.propagateOOM(freePayloadSlots_.append(loc->payloadStack()));
     }
 
     loc->setPayloadReg(dest, loc->payloadType());
 }
 
+Address
+CacheRegisterAllocator::valueAddress(MacroAssembler& masm, OperandLocation* loc)
+{
+    MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end());
+    return Address(masm.getStackPointer(), stackPushed_ - loc->valueStack());
+}
+
 void
 CacheRegisterAllocator::popValue(MacroAssembler& masm, OperandLocation* loc, ValueOperand dest)
 {
     MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end());
     MOZ_ASSERT(stackPushed_ >= sizeof(js::Value));
 
     // The Value is on the stack. If it's on top of the stack we can just
     // pop it, else we emit a load.
@@ -772,19 +824,21 @@ CacheRegisterAllocator::restoreInputStat
                 Register scratch = dest.valueReg().scratchReg();
                 popPayload(masm, &cur, scratch);
                 masm.tagValue(cur.payloadType(), scratch, dest.valueReg());
                 continue;
               }
               case OperandLocation::ValueStack:
                 popValue(masm, &cur, dest.valueReg());
                 continue;
+              case OperandLocation::DoubleReg:
+                masm.boxDouble(cur.doubleReg(), dest.valueReg(), cur.doubleReg());
+                continue;
               case OperandLocation::Constant:
               case OperandLocation::BaselineFrame:
-              case OperandLocation::DoubleReg:
               case OperandLocation::Uninitialized:
                 break;
             }
         } else if (dest.kind() == OperandLocation::PayloadReg) {
             // We have to restore a payload register.
             switch (cur.kind()) {
               case OperandLocation::ValueReg:
                 MOZ_ASSERT(dest.payloadType() != JSVAL_TYPE_DOUBLE);
@@ -1862,16 +1916,49 @@ CacheIRCompiler::emitLoadInt32ArrayLengt
     masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
     masm.load32(Address(scratch, ObjectElements::offsetOfLength()), scratch);
 
     // Guard length fits in an int32.
     masm.branchTest32(Assembler::Signed, scratch, scratch, failure->label());
     EmitStoreResult(masm, scratch, JSVAL_TYPE_INT32, output);
     return true;
 }
+bool
+CacheIRCompiler::emitDoubleAddResult()
+{
+    AutoOutputRegister output(*this);
+
+    // Float register must be preserved. The BinaryArith ICs use
+    // the fact that baseline has them available, as well as fixed temps on
+    // LBinaryCache.
+    allocator.loadDouble(masm, reader.valOperandId(), FloatReg0);
+    allocator.loadDouble(masm, reader.valOperandId(), FloatReg1);
+
+    masm.addDouble(FloatReg1, FloatReg0);
+    masm.boxDouble(FloatReg0, output.valueReg(), FloatReg0);
+
+    return true;
+}
+
+bool
+CacheIRCompiler::emitInt32AddResult()
+{
+    AutoOutputRegister output(*this);
+    Register lhs = allocator.useRegister(masm, reader.int32OperandId());
+    Register rhs = allocator.useRegister(masm, reader.int32OperandId());
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    masm.branchAdd32(Assembler::Overflow, lhs, rhs, failure->label());
+    EmitStoreResult(masm, rhs, JSVAL_TYPE_INT32, output);
+
+    return true;
+}
 
 bool
 CacheIRCompiler::emitInt32NegationResult()
 {
     AutoOutputRegister output(*this);
     Register val = allocator.useRegister(masm, reader.int32OperandId());
 
     FailurePath* failure;
--- a/js/src/jit/CacheIRCompiler.h
+++ b/js/src/jit/CacheIRCompiler.h
@@ -46,16 +46,18 @@ namespace jit {
     _(LoadEnclosingEnvironment)           \
     _(LoadWrapperTarget)                  \
     _(LoadValueTag)                       \
     _(LoadDOMExpandoValue)                \
     _(LoadDOMExpandoValueIgnoreGeneration)\
     _(LoadUndefinedResult)                \
     _(LoadBooleanResult)                  \
     _(LoadInt32ArrayLengthResult)         \
+    _(Int32AddResult)                     \
+    _(DoubleAddResult)                    \
     _(Int32NegationResult)                \
     _(Int32NotResult)                     \
     _(DoubleNegationResult)               \
     _(TruncateDoubleToUInt32)             \
     _(LoadArgumentsObjectLengthResult)    \
     _(LoadFunctionLengthResult)           \
     _(LoadStringLengthResult)             \
     _(LoadStringCharResult)               \
@@ -324,16 +326,17 @@ class MOZ_RAII CacheRegisterAllocator
 
     void freeDeadOperandLocations(MacroAssembler& masm);
 
     void spillOperandToStack(MacroAssembler& masm, OperandLocation* loc);
     void spillOperandToStackOrRegister(MacroAssembler& masm, OperandLocation* loc);
 
     void popPayload(MacroAssembler& masm, OperandLocation* loc, Register dest);
     void popValue(MacroAssembler& masm, OperandLocation* loc, ValueOperand dest);
+    Address valueAddress(MacroAssembler& masm, OperandLocation* loc);
 
 #ifdef DEBUG
     void assertValidState() const;
 #endif
 
   public:
     friend class AutoScratchRegister;
     friend class AutoScratchRegisterExcluding;
@@ -464,16 +467,19 @@ class MOZ_RAII CacheRegisterAllocator
     Register useRegister(MacroAssembler& masm, TypedOperandId typedId);
 
     ConstantOrRegister useConstantOrRegister(MacroAssembler& masm, ValOperandId val);
 
     // Allocates an output register for the given operand.
     Register defineRegister(MacroAssembler& masm, TypedOperandId typedId);
     ValueOperand defineValueRegister(MacroAssembler& masm, ValOperandId val);
 
+    // Loads (and unboxes) a value into a float register (caller guarded)
+    void loadDouble(MacroAssembler&, ValOperandId, FloatRegister);
+
     // Returns |val|'s JSValueType or JSVAL_TYPE_UNKNOWN.
     JSValueType knownType(ValOperandId val) const;
 
     // Emits code to restore registers and stack to the state at the start of
     // the stub.
     void restoreInputState(MacroAssembler& masm, bool discardStack = true);
 
     // Returns the set of registers storing the IC input operands.
--- a/js/src/jit/CacheIRSpewer.cpp
+++ b/js/src/jit/CacheIRSpewer.cpp
@@ -152,16 +152,27 @@ CacheIRSpewer::valueProperty(const char*
         j.formatProperty("value", "%p (shape: %p)", &v.toObject(),
                          v.toObject().maybeShape());
     }
 
     j.endObject();
 }
 
 void
+CacheIRSpewer::opcodeProperty(const char* name, const JSOp op)
+{
+    MOZ_ASSERT(enabled());
+    JSONPrinter& j = json.ref();
+
+    j.beginStringProperty(name);
+    output.put(CodeName[op]);
+    j.endStringProperty();
+}
+
+void
 CacheIRSpewer::attached(const char* name)
 {
     MOZ_ASSERT(enabled());
     json.ref().property("attached", name);
 }
 
 void
 CacheIRSpewer::endCache()
--- a/js/src/jit/CacheIRSpewer.h
+++ b/js/src/jit/CacheIRSpewer.h
@@ -32,16 +32,17 @@ class CacheIRSpewer
 
     bool enabled() { return json.isSome(); }
 
     // These methods can only be called when enabled() is true.
     Mutex& lock() { MOZ_ASSERT(enabled()); return outputLock; }
 
     void beginCache(const IRGenerator& generator);
     void valueProperty(const char* name, const Value& v);
+    void opcodeProperty(const char* name, const JSOp op);
     void attached(const char* name);
     void endCache();
 
   public:
     static CacheIRSpewer& singleton() { return cacheIRspewer; }
     bool init(const char* name);
 
     class MOZ_RAII Guard {
@@ -69,16 +70,20 @@ class CacheIRSpewer
             sp_.lock().unlock();
           }
         }
 
         void valueProperty(const char* name, const Value& v) const {
           sp_.valueProperty(name, v);
         }
 
+        void opcodeProperty(const char* name, const JSOp op) const {
+          sp_.opcodeProperty(name, op);
+        }
+
         explicit operator bool() const {
           return sp_.enabled();
         }
     };
 };
 
 } // namespace jit
 } // namespace js
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -183,16 +183,21 @@ typedef bool (*IonInstanceOfICFn)(JSCont
 static const VMFunction IonInstanceOfInfo =
     FunctionInfo<IonInstanceOfICFn>(IonInstanceOfIC::update, "IonInstanceOfIC::update");
 
 typedef bool (*IonUnaryArithICFn)(JSContext* cx, HandleScript outerScript, IonUnaryArithIC* stub,
                                     HandleValue val, MutableHandleValue res);
 static const VMFunction IonUnaryArithICInfo =
     FunctionInfo<IonUnaryArithICFn>(IonUnaryArithIC::update, "IonUnaryArithIC::update");
 
+typedef bool (*IonBinaryArithICFn)(JSContext* cx, HandleScript outerScript, IonBinaryArithIC* stub,
+                                    HandleValue lhs, HandleValue rhs, MutableHandleValue res);
+static const VMFunction IonBinaryArithICInfo =
+    FunctionInfo<IonBinaryArithICFn>(IonBinaryArithIC::update, "IonBinaryArithIC::update");
+
 void
 CodeGenerator::visitOutOfLineICFallback(OutOfLineICFallback* ool)
 {
     LInstruction* lir = ool->lir();
     size_t cacheIndex = ool->cacheIndex();
     size_t cacheInfoIndex = ool->cacheInfoIndex();
 
     DataPtr<IonIC> ic(this, cacheIndex);
@@ -375,16 +380,33 @@ CodeGenerator::visitOutOfLineICFallback(
         callVM(IonUnaryArithICInfo, lir);
 
         StoreValueTo(unaryArithIC->output()).generate(this);
         restoreLiveIgnore(lir, StoreValueTo(unaryArithIC->output()).clobbered());
 
         masm.jump(ool->rejoin());
         return;
       }
+      case CacheKind::BinaryArith: {
+        IonBinaryArithIC* binaryArithIC = ic->asBinaryArithIC();
+
+        saveLive(lir);
+
+        pushArg(binaryArithIC->rhs());
+        pushArg(binaryArithIC->lhs());
+        icInfo_[cacheInfoIndex].icOffsetForPush = pushArgWithPatch(ImmWord(-1));
+        pushArg(ImmGCPtr(gen->info().script()));
+        callVM(IonBinaryArithICInfo, lir);
+
+        StoreValueTo(binaryArithIC->output()).generate(this);
+        restoreLiveIgnore(lir, StoreValueTo(binaryArithIC->output()).clobbered());
+
+        masm.jump(ool->rejoin());
+        return;
+      }
       case CacheKind::Call:
       case CacheKind::Compare:
       case CacheKind::TypeOf:
       case CacheKind::ToBool:
       case CacheKind::GetIntrinsic:
         MOZ_CRASH("Unsupported IC");
     }
     MOZ_CRASH();
@@ -2756,16 +2778,28 @@ CodeGenerator::emitSharedStub(ICStub::Ki
     masm.freeStack(sizeof(intptr_t) * 2);
 #else
     masm.freeStack(sizeof(intptr_t));
 #endif
     markSafepointAt(callOffset, lir);
 }
 
 void
+CodeGenerator::visitBinaryCache(LBinaryCache* lir)
+{
+    LiveRegisterSet liveRegs = lir->safepoint()->liveRegs();
+    TypedOrValueRegister lhs = TypedOrValueRegister(ToValue(lir, LBinaryCache::LhsInput));
+    TypedOrValueRegister rhs = TypedOrValueRegister(ToValue(lir, LBinaryCache::RhsInput));
+    ValueOperand output = ToOutValue(lir);
+
+    IonBinaryArithIC ic(liveRegs, lhs, rhs, output);
+    addIC(lir, allocateIC(ic));
+}
+
+void
 CodeGenerator::visitBinarySharedStub(LBinarySharedStub* lir)
 {
     JSOp jsop = JSOp(*lir->mirRaw()->toInstruction()->resumePoint()->pc());
     switch (jsop) {
       case JSOP_ADD:
       case JSOP_SUB:
       case JSOP_MUL:
       case JSOP_DIV:
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -3565,16 +3565,22 @@ IonBuilder::arithTrySharedStub(bool* emi
       case JSOP_NEG:
       case JSOP_BITNOT:
         MOZ_ASSERT_IF(op == JSOP_MUL,
                       left->maybeConstantValue() && left->maybeConstantValue()->toInt32() == -1);
         MOZ_ASSERT_IF(op != JSOP_MUL, !left);
         stub = MUnaryCache::New(alloc(), right);
         break;
       case JSOP_ADD:
+        // If not disabled, prefer the cache IR stub.
+        if (!JitOptions.disableCacheIRBinaryArith) {
+            stub = MBinaryCache::New(alloc(), left, right);
+            break;
+        }
+        MOZ_FALLTHROUGH;
       case JSOP_SUB:
       case JSOP_MUL:
       case JSOP_DIV:
       case JSOP_MOD:
       case JSOP_POW:
         stub = MBinarySharedStub::New(alloc(), left, right);
         break;
       default:
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -518,16 +518,30 @@ IonCacheIRCompiler::init()
 
         liveRegs_.emplace(ic->liveRegs());
         outputUnchecked_.emplace(TypedOrValueRegister(output));
 
         MOZ_ASSERT(numInputs == 1);
         allocator.initInputLocation(0, ic->input());
         break;
       }
+      case CacheKind::BinaryArith: {
+        IonBinaryArithIC* ic = ic_->asBinaryArithIC();
+        ValueOperand output = ic->output();
+
+        available.add(output);
+
+        liveRegs_.emplace(ic->liveRegs());
+        outputUnchecked_.emplace(TypedOrValueRegister(output));
+
+        MOZ_ASSERT(numInputs == 2);
+        allocator.initInputLocation(0, ic->lhs());
+        allocator.initInputLocation(1, ic->rhs());
+        break;
+      }
       case CacheKind::Call:
       case CacheKind::Compare:
       case CacheKind::TypeOf:
       case CacheKind::ToBool:
       case CacheKind::GetIntrinsic:
         MOZ_CRASH("Unsupported IC");
     }
 
--- a/js/src/jit/IonIC.cpp
+++ b/js/src/jit/IonIC.cpp
@@ -53,16 +53,18 @@ IonIC::scratchRegisterForEntryJump()
       case CacheKind::HasOwn:
         return asHasOwnIC()->output();
       case CacheKind::GetIterator:
         return asGetIteratorIC()->temp1();
       case CacheKind::InstanceOf:
         return asInstanceOfIC()->output();
       case CacheKind::UnaryArith:
         return asUnaryArithIC()->output().scratchReg();
+      case CacheKind::BinaryArith:
+        return asBinaryArithIC()->output().scratchReg();
       case CacheKind::Call:
       case CacheKind::Compare:
       case CacheKind::TypeOf:
       case CacheKind::ToBool:
       case CacheKind::GetIntrinsic:
         MOZ_CRASH("Unsupported IC");
     }
 
@@ -543,16 +545,58 @@ IonUnaryArithIC::update(JSContext* cx, H
 
         if (!attached)
             ic->state().trackNotAttached();
     }
 
     return true;
 }
 
+/* static */ bool
+IonBinaryArithIC::update(JSContext* cx, HandleScript outerScript, IonBinaryArithIC* ic,
+                         HandleValue lhs, HandleValue rhs, MutableHandleValue ret)
+{
+    IonScript* ionScript = outerScript->ionScript();
+    RootedScript script(cx, ic->script());
+    jsbytecode* pc = ic->pc();
+    JSOp op = JSOp(*pc);
+
+    // Don't pass lhs/rhs directly, we need the original values when
+    // generating stubs.
+    RootedValue lhsCopy(cx, lhs);
+    RootedValue rhsCopy(cx, rhs);
+
+    // Perform the compare operation.
+    switch(op) {
+      case JSOP_ADD:
+        // Do an add.
+        if (!AddValues(cx, &lhsCopy, &rhsCopy, ret))
+            return false;
+        break;
+      default:
+        MOZ_CRASH("Unhandled binary arith op");
+    }
+
+    if (ic->state().maybeTransition())
+        ic->discardStubs(cx->zone());
+
+    if (ic->state().canAttachStub()) {
+        bool attached = false;
+        BinaryArithIRGenerator gen(cx, script, pc, ic->state().mode(),
+                                   op, lhs, rhs, ret);
+        if (gen.tryAttachStub()) {
+            ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached);
+
+            if (!attached)
+                ic->state().trackNotAttached();
+        }
+    }
+    return true;
+}
+
 uint8_t*
 IonICStub::stubDataStart()
 {
     return reinterpret_cast<uint8_t*>(this) + stubInfo_->stubDataOffset();
 }
 
 void
 IonIC::attachStub(IonICStub* newStub, JitCode* code)
--- a/js/src/jit/IonIC.h
+++ b/js/src/jit/IonIC.h
@@ -61,16 +61,17 @@ class IonSetPropertyIC;
 class IonGetPropSuperIC;
 class IonGetNameIC;
 class IonBindNameIC;
 class IonGetIteratorIC;
 class IonHasOwnIC;
 class IonInIC;
 class IonInstanceOfIC;
 class IonUnaryArithIC;
+class IonBinaryArithIC;
 
 class IonIC
 {
     // This either points at the OOL path for the fallback path, or the code for
     // the first stub.
     uint8_t* codeRaw_;
 
     // The first optimized stub, or nullptr.
@@ -172,16 +173,20 @@ class IonIC
     IonInstanceOfIC* asInstanceOfIC() {
         MOZ_ASSERT(kind_ == CacheKind::InstanceOf);
         return (IonInstanceOfIC*)this;
     }
     IonUnaryArithIC* asUnaryArithIC() {
         MOZ_ASSERT(kind_ == CacheKind::UnaryArith);
         return (IonUnaryArithIC*)this;
     }
+    IonBinaryArithIC* asBinaryArithIC() {
+        MOZ_ASSERT(kind_ == CacheKind::BinaryArith);
+        return (IonBinaryArithIC*)this;
+    }
 
     void updateBaseAddress(JitCode* code);
 
     // Returns the Register to use as scratch when entering IC stubs. This
     // should either be an output register or a temp.
     Register scratchRegisterForEntryJump();
 
     void trace(JSTracer* trc);
@@ -499,12 +504,40 @@ class IonUnaryArithIC : public IonIC
     LiveRegisterSet liveRegs() const { return liveRegs_; }
     TypedOrValueRegister input() const { return input_; }
     ValueOperand output() const { return output_; }
 
     static MOZ_MUST_USE bool update(JSContext* cx, HandleScript outerScript, IonUnaryArithIC* stub,
                                     HandleValue val, MutableHandleValue res);
 };
 
+class IonBinaryArithIC : public IonIC
+{
+    LiveRegisterSet liveRegs_;
+
+    TypedOrValueRegister lhs_;
+    TypedOrValueRegister rhs_;
+    ValueOperand output_;
+
+    public:
+
+    IonBinaryArithIC(LiveRegisterSet liveRegs, TypedOrValueRegister lhs, TypedOrValueRegister rhs,  ValueOperand output)
+      : IonIC(CacheKind::BinaryArith),
+        liveRegs_(liveRegs),
+        lhs_(lhs),
+        rhs_(rhs),
+        output_(output)
+    { }
+
+    LiveRegisterSet liveRegs() const { return liveRegs_; }
+    TypedOrValueRegister lhs() const { return lhs_; }
+    TypedOrValueRegister rhs() const { return rhs_; }
+    ValueOperand output() const { return output_; }
+
+    static MOZ_MUST_USE bool update(JSContext* cx, HandleScript outerScript, IonBinaryArithIC* stub,
+                                    HandleValue lhs, HandleValue rhs, MutableHandleValue res);
+};
+
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_IonIC_h */
--- a/js/src/jit/JitOptions.cpp
+++ b/js/src/jit/JitOptions.cpp
@@ -120,16 +120,19 @@ DefaultJitOptions::DefaultJitOptions()
     SET_DEFAULT(disableRecoverIns, false);
 
     // Toggle whether eager scalar replacement is globally disabled.
     SET_DEFAULT(disableScalarReplacement, false);
 
     // Toggles whether CacheIR stubs are used.
     SET_DEFAULT(disableCacheIR, false);
 
+    // Toggles whether CacheIR stubs for binary arith operations are used
+    SET_DEFAULT(disableCacheIRBinaryArith, false);
+
     // Toggles whether shared stubs are used in Ionmonkey.
     SET_DEFAULT(disableSharedStubs, false);
 
     // Toggles whether sincos optimization is globally disabled.
     // See bug984018: The MacOS is the only one that has the sincos fast.
     #if defined(XP_MACOSX)
         SET_DEFAULT(disableSincos, false);
     #else
--- a/js/src/jit/JitOptions.h
+++ b/js/src/jit/JitOptions.h
@@ -58,16 +58,17 @@ struct DefaultJitOptions
     bool disableLoopUnrolling;
     bool disableOptimizationTracking;
     bool disablePgo;
     bool disableInstructionReordering;
     bool disableRangeAnalysis;
     bool disableRecoverIns;
     bool disableScalarReplacement;
     bool disableCacheIR;
+    bool disableCacheIRBinaryArith;
     bool disableSharedStubs;
     bool disableSincos;
     bool disableSink;
     bool eagerCompilation;
     bool forceInlineCaches;
     bool fullDebugChecks;
     bool limitScriptSize;
     bool osr;
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2508,16 +2508,34 @@ LIRGenerator::visitStringReplace(MString
     LStringReplace* lir = new(alloc()) LStringReplace(useRegisterOrConstantAtStart(ins->string()),
                                                       useRegisterAtStart(ins->pattern()),
                                                       useRegisterOrConstantAtStart(ins->replacement()));
     defineReturn(lir, ins);
     assignSafepoint(lir, ins);
 }
 
 void
+LIRGenerator::visitBinaryCache(MBinaryCache* ins)
+{
+    MDefinition* lhs = ins->getOperand(0);
+    MDefinition* rhs = ins->getOperand(1);
+
+    MOZ_ASSERT(ins->type() == MIRType::Value);
+    MOZ_ASSERT(ins->type() == MIRType::Value);
+
+    LBinaryCache* lir = new(alloc()) LBinaryCache(useBox(lhs),
+                                                  useBox(rhs),
+                                                  tempFixed(FloatReg0),
+                                                  tempFixed(FloatReg1));
+    defineBox(lir, ins);
+    assignSafepoint(lir, ins);
+}
+
+
+void
 LIRGenerator::visitBinarySharedStub(MBinarySharedStub* ins)
 {
     MDefinition* lhs = ins->getOperand(0);
     MDefinition* rhs = ins->getOperand(1);
 
     MOZ_ASSERT(ins->type() == MIRType::Value);
     MOZ_ASSERT(ins->type() == MIRType::Value);
 
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -8333,16 +8333,32 @@ class MBinarySharedStub
         setResultType(MIRType::Value);
     }
 
   public:
     INSTRUCTION_HEADER(BinarySharedStub)
     TRIVIAL_NEW_WRAPPERS
 };
 
+class MBinaryCache
+  : public MBinaryInstruction,
+    public MixPolicy<BoxPolicy<0>, BoxPolicy<1> >::Data
+{
+  protected:
+    explicit MBinaryCache(MDefinition* left, MDefinition* right)
+      : MBinaryInstruction(classOpcode, left, right)
+    {
+        setResultType(MIRType::Value);
+    }
+
+  public:
+    INSTRUCTION_HEADER(BinaryCache)
+    TRIVIAL_NEW_WRAPPERS
+};
+
 class MUnaryCache
   : public MUnaryInstruction,
     public BoxPolicy<0>::Data
 {
     explicit MUnaryCache(MDefinition* input)
       : MUnaryInstruction(classOpcode, input)
     {
         setResultType(MIRType::Value);
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -668,16 +668,22 @@ SharedStubInfo::outerScript(JSContext* c
         MOZ_ASSERT(!it.ionScript()->invalidated());
     }
     return outerScript_;
 }
 
 //
 // BinaryArith_Fallback
 //
+// This will be phased out in favour of the CacheIR system.
+
+extern bool
+DoCacheIRBinaryArithFallback(JSContext* cx, BaselineFrame* frame, ICBinaryArith_Fallback* stub_,
+                             HandleValue lhs, HandleValue rhs, MutableHandleValue ret,
+                             DebugModeOSRVolatileStub<ICBinaryArith_Fallback*>& stub);
 
 static bool
 DoBinaryArithFallback(JSContext* cx, void* payload, ICBinaryArith_Fallback* stub_,
                       HandleValue lhs, HandleValue rhs, MutableHandleValue ret)
 {
     SharedStubInfo info(cx, payload, stub_->icEntry());
     ICStubCompiler::Engine engine = info.engine();
 
@@ -685,16 +691,17 @@ DoBinaryArithFallback(JSContext* cx, voi
     DebugModeOSRVolatileStub<ICBinaryArith_Fallback*> stub(engine, info.maybeFrame(), stub_);
 
     jsbytecode* pc = info.pc();
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "BinaryArith(%s,%d,%d)", CodeName[op],
             int(lhs.isDouble() ? JSVAL_TYPE_DOUBLE : lhs.extractNonDoubleType()),
             int(rhs.isDouble() ? JSVAL_TYPE_DOUBLE : rhs.extractNonDoubleType()));
 
+
     // Don't pass lhs/rhs directly, we need the original values when
     // generating stubs.
     RootedValue lhsCopy(cx, lhs);
     RootedValue rhsCopy(cx, rhs);
 
     // Perform the compare operation.
     switch(op) {
       case JSOP_ADD:
@@ -765,16 +772,23 @@ DoBinaryArithFallback(JSContext* cx, voi
       default:
         MOZ_CRASH("Unhandled baseline arith op");
     }
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
+    // Try to use a CacheIR stub first. If that succeeds, then we're done. Otherwise, we
+    // need to try to attach a shared stub.
+    if (engine == ICStubCompiler::Engine::Baseline && !JitOptions.disableCacheIRBinaryArith) {
+        if (DoCacheIRBinaryArithFallback(cx, (BaselineFrame*)payload, stub_, lhs, rhs, ret, stub))
+            return true;
+    }
+
     if (ret.isDouble())
         stub->setSawDoubleResult();
 
     // Check to see if a new stub should be generated.
     if (stub->numOptimizedStubs() >= ICBinaryArith_Fallback::MAX_OPTIMIZED_STUBS) {
         stub->noteUnoptimizableOperands();
         return true;
     }
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -5551,16 +5551,42 @@ class LBinarySharedStub : public LCallIn
     const MBinarySharedStub* mir() const {
         return mir_->toBinarySharedStub();
     }
 
     static const size_t LhsInput = 0;
     static const size_t RhsInput = BOX_PIECES;
 };
 
+class LBinaryCache : public LInstructionHelper<BOX_PIECES, 2 * BOX_PIECES, 2>
+{
+  public:
+    LIR_HEADER(BinaryCache)
+
+    // Takes two temps: these are intendend to be FloatReg0 and FloatReg1
+    // To allow the actual cache code to safely clobber those values without
+    // save and restore.
+    LBinaryCache(const LBoxAllocation& lhs, const LBoxAllocation& rhs,
+                 const LDefinition& temp0, const LDefinition& temp1)
+      : LInstructionHelper(classOpcode)
+    {
+        setBoxOperand(LhsInput, lhs);
+        setBoxOperand(RhsInput, rhs);
+        setTemp(0, temp0);
+        setTemp(1, temp1);
+    }
+
+    const MBinaryCache* mir() const {
+        return mir_->toBinaryCache();
+    }
+
+    static const size_t LhsInput = 0;
+    static const size_t RhsInput = BOX_PIECES;
+};
+
 class LUnaryCache : public LInstructionHelper<BOX_PIECES, BOX_PIECES, 0>
 {
   public:
     LIR_HEADER(UnaryCache)
 
     explicit LUnaryCache(const LBoxAllocation& input)
       : LInstructionHelper(classOpcode)
     {
--- a/js/src/jit/shared/Lowering-shared-inl.h
+++ b/js/src/jit/shared/Lowering-shared-inl.h
@@ -683,16 +683,24 @@ LDefinition
 LIRGeneratorShared::tempFixed(Register reg)
 {
     LDefinition t = temp(LDefinition::GENERAL);
     t.setOutput(LGeneralReg(reg));
     return t;
 }
 
 LDefinition
+LIRGeneratorShared::tempFixed(FloatRegister reg)
+{
+    LDefinition t = temp(LDefinition::DOUBLE);
+    t.setOutput(LFloatReg(reg));
+    return t;
+}
+
+LDefinition
 LIRGeneratorShared::tempFloat32()
 {
     return temp(LDefinition::FLOAT32);
 }
 
 LDefinition
 LIRGeneratorShared::tempDouble()
 {
--- a/js/src/jit/shared/Lowering-shared.h
+++ b/js/src/jit/shared/Lowering-shared.h
@@ -145,18 +145,21 @@ class LIRGeneratorShared
     // These create temporary register requests.
     inline LDefinition temp(LDefinition::Type type = LDefinition::GENERAL,
                             LDefinition::Policy policy = LDefinition::REGISTER);
     inline LInt64Definition tempInt64(LDefinition::Policy policy = LDefinition::REGISTER);
     inline LDefinition tempFloat32();
     inline LDefinition tempDouble();
     inline LDefinition tempCopy(MDefinition* input, uint32_t reusedInput);
 
-    // Note that the fixed register has a GENERAL type.
+    // Note that the fixed register has a GENERAL type,
+    // unless the arg is of FloatRegister type
     inline LDefinition tempFixed(Register reg);
+    inline LDefinition tempFixed(FloatRegister reg);
+
 
     template <size_t Ops, size_t Temps>
     inline void defineFixed(LInstructionHelper<1, Ops, Temps>* lir, MDefinition* mir,
                             const LAllocation& output);
 
     template <size_t Temps>
     inline void defineBox(details::LInstructionFixedDefsTempsHelper<BOX_PIECES, Temps>* lir,
                           MDefinition* mir, LDefinition::Policy policy = LDefinition::REGISTER);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -8640,16 +8640,18 @@ SetContextOptions(JSContext* cx, const O
     if (op.getBoolOption("no-unboxed-objects"))
         jit::JitOptions.disableUnboxedObjects = true;
 
     if (const char* str = op.getStringOption("cache-ir-stubs")) {
         if (strcmp(str, "on") == 0)
             jit::JitOptions.disableCacheIR = false;
         else if (strcmp(str, "off") == 0)
             jit::JitOptions.disableCacheIR = true;
+        else if (strcmp(str, "nobinary") == 0)
+            jit::JitOptions.disableCacheIRBinaryArith = true;
         else
             return OptionFailure("cache-ir-stubs", str);
     }
 
     if (const char* str = op.getStringOption("spectre-mitigations")) {
         if (strcmp(str, "on") == 0) {
             jit::JitOptions.spectreIndexMasking = true;
             jit::JitOptions.spectreObjectMitigationsBarriers = true;
@@ -9194,18 +9196,19 @@ main(int argc, char** argv, char** envp)
                                "(default: on, off to disable)"
 #  else
                                "(default: off, on to enable)"
 #  endif
             )
 #endif
         || !op.addStringOption('\0', "spectre-mitigations", "on/off",
                                "Whether Spectre mitigations are enabled (default: off, on to enable)")
-        || !op.addStringOption('\0', "cache-ir-stubs", "on/off",
-                               "Use CacheIR stubs (default: on, off to disable)")
+        || !op.addStringOption('\0', "cache-ir-stubs", "on/off/nobinary",
+                               "Use CacheIR stubs (default: on, off to disable, nobinary to"
+                               "just disable binary arith)")
         || !op.addStringOption('\0', "ion-shared-stubs", "on/off",
                                "Use shared stubs (default: on, off to disable)")
         || !op.addStringOption('\0', "ion-scalar-replacement", "on/off",
                                "Scalar Replacement (default: on, off to disable)")
         || !op.addStringOption('\0', "ion-gvn", "[mode]",
                                "Specify Ion global value numbering:\n"
                                "  off: disable GVN\n"
                                "  on:  enable GVN (default)\n")