Bug 1519135 - Replace JSOP_POS in ++/-- with JSOP_TONUMERIC r=terpri,jandem
authorAndy Wingo <wingo@igalia.com>
Wed, 13 Feb 2019 10:48:51 +0000
changeset 458873 d65cae66e331
parent 458872 dede03809a93
child 458874 53f624bc7c22
push id35551
push usershindli@mozilla.com
push dateWed, 13 Feb 2019 21:34:09 +0000
treeherdermozilla-central@08f794a4928e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterpri, jandem
bugs1519135
milestone67.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 1519135 - Replace JSOP_POS in ++/-- with JSOP_TONUMERIC r=terpri,jandem Differential Revision: https://phabricator.services.mozilla.com/D16200
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/ElemOpEmitter.cpp
js/src/frontend/NameOpEmitter.cpp
js/src/frontend/PropOpEmitter.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/BaselineICList.h
js/src/jit/BaselineInspector.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/IonTypes.h
js/src/jit/Lowering.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/RangeAnalysis.cpp
js/src/jit/TypePolicy.cpp
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/jit/shared/LIR-shared.h
js/src/vm/Interpreter.cpp
js/src/vm/Opcodes.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -1941,17 +1941,17 @@ bool BytecodeEmitter::emitCallIncDec(Una
              incDec->isKind(ParseNodeKind::PostDecrementExpr));
 
   ParseNode* call = incDec->kid();
   MOZ_ASSERT(call->isKind(ParseNodeKind::CallExpr));
   if (!emitTree(call)) {
     //              [stack] CALLRESULT
     return false;
   }
-  if (!emit1(JSOP_POS)) {
+  if (!emit1(JSOP_TONUMERIC)) {
     //              [stack] N
     return false;
   }
 
   // The increment/decrement has no side effects, so proceed to throw for
   // invalid assignment target.
   return emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS);
 }
--- a/js/src/frontend/ElemOpEmitter.cpp
+++ b/js/src/frontend/ElemOpEmitter.cpp
@@ -221,17 +221,17 @@ bool ElemOpEmitter::emitIncDec() {
   if (!emitGet()) {
     //              [stack] ... ELEM
     return false;
   }
 
   MOZ_ASSERT(state_ == State::Get);
 
   JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC;
-  if (!bce_->emit1(JSOP_POS)) {
+  if (!bce_->emit1(JSOP_TONUMERIC)) {
     //              [stack] ... N
     return false;
   }
   if (isPostIncDec()) {
     if (!bce_->emit1(JSOP_DUP)) {
       //            [stack] ... N? N
       return false;
     }
--- a/js/src/frontend/NameOpEmitter.cpp
+++ b/js/src/frontend/NameOpEmitter.cpp
@@ -352,17 +352,17 @@ bool NameOpEmitter::emitAssignment() {
 bool NameOpEmitter::emitIncDec() {
   MOZ_ASSERT(state_ == State::Start);
 
   JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC;
   if (!prepareForRhs()) {
     //              [stack] ENV? V
     return false;
   }
-  if (!bce_->emit1(JSOP_POS)) {
+  if (!bce_->emit1(JSOP_TONUMERIC)) {
     //              [stack] ENV? N
     return false;
   }
   if (isPostIncDec()) {
     if (!bce_->emit1(JSOP_DUP)) {
       //            [stack] ENV? N? N
       return false;
     }
--- a/js/src/frontend/PropOpEmitter.cpp
+++ b/js/src/frontend/PropOpEmitter.cpp
@@ -205,17 +205,17 @@ bool PropOpEmitter::emitIncDec(JSAtom* p
   if (!emitGet(prop)) {
     return false;
   }
 
   MOZ_ASSERT(state_ == State::Get);
 
   JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC;
 
-  if (!bce_->emit1(JSOP_POS)) {
+  if (!bce_->emit1(JSOP_TONUMERIC)) {
     //              [stack] ... N
     return false;
   }
   if (isPostIncDec()) {
     if (!bce_->emit1(JSOP_DUP)) {
       //            [stack] .. N N
       return false;
     }
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1659,22 +1659,43 @@ bool BaselineCodeGen<Handler>::emit_JSOP
   return true;
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_POS() {
   // Keep top stack value in R0.
   frame.popRegsAndSync(1);
 
-  // Inline path for int32 and double.
+  // Inline path for int32 and double; otherwise call VM.
   Label done;
   masm.branchTestNumber(Assembler::Equal, R0, &done);
 
-  // Call IC.
-  if (!emitNextIC()) {
+  prepareVMCall();
+  pushArg(R0);
+  if (!callVM(ToNumberInfo)) {
+    return false;
+  }
+
+  masm.bind(&done);
+  frame.push(R0);
+  return true;
+}
+
+template <typename Handler>
+bool BaselineCodeGen<Handler>::emit_JSOP_TONUMERIC() {
+  // Keep top stack value in R0.
+  frame.popRegsAndSync(1);
+
+  // Inline path for int32 and double; otherwise call VM.
+  Label done;
+  masm.branchTestNumber(Assembler::Equal, R0, &done);
+
+  prepareVMCall();
+  pushArg(R0);
+  if (!callVM(ToNumericInfo)) {
     return false;
   }
 
   masm.bind(&done);
   frame.push(R0);
   return true;
 }
 
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -33,16 +33,17 @@ namespace jit {
   _(JSOP_UNPICK)                \
   _(JSOP_GOTO)                  \
   _(JSOP_IFEQ)                  \
   _(JSOP_IFNE)                  \
   _(JSOP_AND)                   \
   _(JSOP_OR)                    \
   _(JSOP_NOT)                   \
   _(JSOP_POS)                   \
+  _(JSOP_TONUMERIC)             \
   _(JSOP_LOOPHEAD)              \
   _(JSOP_LOOPENTRY)             \
   _(JSOP_VOID)                  \
   _(JSOP_UNDEFINED)             \
   _(JSOP_HOLE)                  \
   _(JSOP_NULL)                  \
   _(JSOP_TRUE)                  \
   _(JSOP_FALSE)                 \
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -209,23 +209,16 @@ void ICEntry::trace(JSTracer* trc) {
       case JSOP_STRICTEQ:
       case JSOP_STRICTNE: {
         ICCompare_Fallback::Compiler stubCompiler(cx);
         if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
           return nullptr;
         }
         break;
       }
-      case JSOP_POS: {
-        ICToNumber_Fallback::Compiler stubCompiler(cx);
-        if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
-          return nullptr;
-        }
-        break;
-      }
       case JSOP_LOOPENTRY: {
         ICWarmUpCounter_Fallback::Compiler stubCompiler(cx);
         if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
           return nullptr;
         }
         break;
       }
       case JSOP_NEWARRAY: {
@@ -1924,50 +1917,16 @@ bool ICToBool_Fallback::Compiler::genera
   // Push arguments.
   masm.pushValue(R0);
   masm.push(ICStubReg);
   pushStubPayload(masm, R0.scratchReg());
 
   return tailCallVM(fun, masm);
 }
 
-//
-// ToNumber_Fallback
-//
-
-static bool DoToNumberFallback(JSContext* cx, ICToNumber_Fallback* stub,
-                               HandleValue arg, MutableHandleValue ret) {
-  stub->incrementEnteredCount();
-  FallbackICSpew(cx, stub, "ToNumber");
-  ret.set(arg);
-  return ToNumber(cx, ret);
-}
-
-typedef bool (*DoToNumberFallbackFn)(JSContext*, ICToNumber_Fallback*,
-                                     HandleValue, MutableHandleValue);
-static const VMFunction DoToNumberFallbackInfo =
-    FunctionInfo<DoToNumberFallbackFn>(DoToNumberFallback, "DoToNumberFallback",
-                                       TailCall, PopValues(1));
-
-bool ICToNumber_Fallback::Compiler::generateStubCode(MacroAssembler& masm) {
-  MOZ_ASSERT(R0 == JSReturnOperand);
-
-  // Restore the tail call register.
-  EmitRestoreTailCallReg(masm);
-
-  // Ensure stack is fully synced for the expression decompiler.
-  masm.pushValue(R0);
-
-  // Push arguments.
-  masm.pushValue(R0);
-  masm.push(ICStubReg);
-
-  return tailCallVM(DoToNumberFallbackInfo, masm);
-}
-
 static void StripPreliminaryObjectStubs(JSContext* cx, ICFallbackStub* stub) {
   // Before the new script properties analysis has been performed on a type,
   // all instances of that type have the maximum number of fixed slots.
   // Afterwards, the objects (even the preliminary ones) might be changed
   // to reduce the number of fixed slots they have. If we generate stubs for
   // both the old and new number of fixed slots, the stub will look
   // polymorphic to IonBuilder when it is actually monomorphic. To avoid
   // this, strip out any stubs for preliminary objects before attaching a new
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -1645,41 +1645,16 @@ class ICToBool_Fallback : public ICFallb
         : ICStubCompiler(cx, ICStub::ToBool_Fallback) {}
 
     ICStub* getStub(ICStubSpace* space) override {
       return newStub<ICToBool_Fallback>(space, getStubCode());
     }
   };
 };
 
-// ToNumber
-//     JSOP_POS
-
-class ICToNumber_Fallback : public ICFallbackStub {
-  friend class ICStubSpace;
-
-  explicit ICToNumber_Fallback(JitCode* stubCode)
-      : ICFallbackStub(ICStub::ToNumber_Fallback, stubCode) {}
-
- public:
-  // Compiler for this stub kind.
-  class Compiler : public ICStubCompiler {
-   protected:
-    MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm) override;
-
-   public:
-    explicit Compiler(JSContext* cx)
-        : ICStubCompiler(cx, ICStub::ToNumber_Fallback) {}
-
-    ICStub* getStub(ICStubSpace* space) override {
-      return newStub<ICToNumber_Fallback>(space, getStubCode());
-    }
-  };
-};
-
 // GetElem
 //      JSOP_GETELEM
 //      JSOP_GETELEM_SUPER
 
 class ICGetElem_Fallback : public ICMonitoredFallbackStub {
   friend class ICStubSpace;
 
   explicit ICGetElem_Fallback(JitCode* stubCode)
--- a/js/src/jit/BaselineICList.h
+++ b/js/src/jit/BaselineICList.h
@@ -27,18 +27,16 @@ namespace jit {
   _(TypeUpdate_AnyValue)              \
                                       \
   _(NewArray_Fallback)                \
   _(NewObject_Fallback)               \
   _(NewObject_WithTemplate)           \
                                       \
   _(ToBool_Fallback)                  \
                                       \
-  _(ToNumber_Fallback)                \
-                                      \
   _(UnaryArith_Fallback)              \
                                       \
   _(Call_Fallback)                    \
   _(Call_Scripted)                    \
   _(Call_AnyScripted)                 \
   _(Call_Native)                      \
   _(Call_ClassHook)                   \
   _(Call_ScriptedApplyArray)          \
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -691,16 +691,21 @@ static bool TryToSpecializeBinaryArithOp
 MIRType BaselineInspector::expectedBinaryArithSpecialization(jsbytecode* pc) {
   if (!hasICScript()) {
     return MIRType::None;
   }
 
   MIRType result;
   ICStub* stubs[2];
 
+  if (JSOp(*pc) == JSOP_POS) {
+    // +x expanding to x*1, but no corresponding IC.
+    return MIRType::None;
+  }
+
   const ICEntry& entry = icEntryFromPC(pc);
   ICFallbackStub* stub = entry.fallbackStub();
   if (stub->state().hasFailures()) {
     return MIRType::None;
   }
 
   stubs[0] = monomorphicStub(pc);
   if (stubs[0]) {
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -3998,16 +3998,61 @@ void CodeGenerator::visitGuardUnboxedExp
 
 void CodeGenerator::visitLoadUnboxedExpando(LLoadUnboxedExpando* lir) {
   Register obj = ToRegister(lir->object());
   Register result = ToRegister(lir->getDef(0));
 
   masm.loadPtr(Address(obj, UnboxedPlainObject::offsetOfExpando()), result);
 }
 
+void CodeGenerator::visitToNumeric(LToNumeric* lir) {
+  ValueOperand operand = ToValue(lir, LToNumeric::Input);
+  ValueOperand output = ToOutValue(lir);
+  bool maybeInt32 = lir->mir()->mightBeType(MIRType::Int32);
+  bool maybeDouble = lir->mir()->mightBeType(MIRType::Double);
+  bool maybeNumber = maybeInt32 || maybeDouble;
+#ifdef ENABLE_BIGINT
+  bool maybeBigInt = lir->mir()->mightBeType(MIRType::BigInt);
+#endif
+  int checks = int(maybeNumber) + IF_BIGINT(int(maybeBigInt), 0);
+
+  OutOfLineCode* ool =
+      oolCallVM(ToNumericInfo, lir, ArgList(operand), StoreValueTo(output));
+
+  if (checks == 0) {
+    masm.jump(ool->entry());
+  } else {
+    Label done;
+    using Condition = Assembler::Condition;
+    constexpr Condition Equal = Assembler::Equal;
+    constexpr Condition NotEqual = Assembler::NotEqual;
+
+    if (maybeNumber) {
+      checks--;
+      Condition cond = checks ? Equal : NotEqual;
+      Label* target = checks ? &done : ool->entry();
+      masm.branchTestNumber(cond, operand, target);
+    }
+#ifdef ENABLE_BIGINT
+    if (maybeBigInt) {
+      checks--;
+      Condition cond = checks ? Equal : NotEqual;
+      Label* target = checks ? &done : ool->entry();
+      masm.branchTestBigInt(cond, operand, target);
+    }
+#endif
+
+    MOZ_ASSERT(checks == 0);
+    masm.bind(&done);
+    masm.moveValue(operand, output);
+  }
+
+  masm.bind(ool->rejoin());
+}
+
 void CodeGenerator::visitTypeBarrierV(LTypeBarrierV* lir) {
   ValueOperand operand = ToValue(lir, LTypeBarrierV::Input);
   Register unboxScratch = ToTempRegisterOrInvalid(lir->unboxTemp());
   Register objScratch = ToTempRegisterOrInvalid(lir->objTemp());
 
   // guardObjectType may zero the payload/Value register on speculative paths
   // (we should have a defineReuseInput allocation in this case).
   Register spectreRegToZero = operand.payloadOrValueReg();
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -617,17 +617,17 @@ AbortReasonOr<Ok> IonBuilder::analyzeNew
       continue;
     }
     if (!last) {
       continue;
     }
 
     MPhi* phi = entry->getSlot(slot)->toPhi();
 
-    if (*last == JSOP_POS) {
+    if (*last == JSOP_POS || *last == JSOP_TONUMERIC) {
       last = earlier;
     }
 
     if (CodeSpec[*last].format & JOF_TYPESET) {
       TemporaryTypeSet* typeSet = bytecodeTypes(last);
       if (!typeSet->empty()) {
         MIRType type = typeSet->getKnownMIRType();
         if (!phi->addBackedgeType(alloc(), type, typeSet)) {
@@ -1654,16 +1654,17 @@ AbortReasonOr<Ok> IonBuilder::visitBlock
         case JSOP_SETLOCAL:
         case JSOP_INITLEXICAL:
         case JSOP_SETRVAL:
         case JSOP_VOID:
           // Don't require SSA uses for values popped by these ops.
           break;
 
         case JSOP_POS:
+        case JSOP_TONUMERIC:
         case JSOP_TOID:
         case JSOP_TOSTRING:
           // These ops may leave their input on the stack without setting
           // the ImplicitlyUsed flag. If this value will be popped immediately,
           // we may replace it with |undefined|, but the difference is
           // not observable.
           MOZ_ASSERT(i == 0);
           if (current->peek(-1) == popped[0]) {
@@ -1896,16 +1897,19 @@ AbortReasonOr<Ok> IonBuilder::inspectOpc
       return jsop_binary_arith(op);
 
     case JSOP_POW:
       return jsop_pow();
 
     case JSOP_POS:
       return jsop_pos();
 
+    case JSOP_TONUMERIC:
+      return jsop_tonumeric();
+
     case JSOP_NEG:
       return jsop_neg();
 
     case JSOP_INC:
     case JSOP_DEC:
       return jsop_inc_or_dec(op);
 
     case JSOP_TOSTRING:
@@ -3799,16 +3803,54 @@ AbortReasonOr<Ok> IonBuilder::jsop_neg()
   MConstant* negator = MConstant::New(alloc(), Int32Value(-1));
   current->add(negator);
 
   MDefinition* right = current->pop();
 
   return jsop_binary_arith(JSOP_MUL, negator, right);
 }
 
+AbortReasonOr<Ok> IonBuilder::jsop_tonumeric() {
+  MDefinition* peeked = current->peek(-1);
+
+  if (IsNumericType(peeked->type())) {
+    // Elide the ToNumeric as we already unboxed the value.
+    peeked->setImplicitlyUsedUnchecked();
+    return Ok();
+  }
+
+  LifoAlloc* lifoAlloc = alloc().lifoAlloc();
+  TemporaryTypeSet* types = lifoAlloc->new_<TemporaryTypeSet>();
+  if (!types) {
+    return abort(AbortReason::Alloc);
+  }
+
+  types->addType(TypeSet::Int32Type(), lifoAlloc);
+  types->addType(TypeSet::DoubleType(), lifoAlloc);
+#ifdef ENABLE_BIGINT
+  types->addType(TypeSet::BigIntType(), lifoAlloc);
+#endif
+
+  if (peeked->type() == MIRType::Value && peeked->resultTypeSet() &&
+      peeked->resultTypeSet()->isSubset(types)) {
+    // Elide the ToNumeric because the arg is already a boxed numeric.
+    peeked->setImplicitlyUsedUnchecked();
+    return Ok();
+  }
+
+  // Otherwise, pop the value and add an MToNumeric.
+  MDefinition* popped = current->pop();
+  MToNumeric* ins = MToNumeric::New(alloc(), popped, types);
+  current->add(ins);
+  current->push(ins);
+
+  // toValue() is effectful, so add a resume point.
+  return resumeAfter(ins);
+}
+
 AbortReasonOr<Ok> IonBuilder::jsop_inc_or_dec(JSOp op) {
   // As above, pass constant without slot traffic.
   MConstant* one = MConstant::New(alloc(), Int32Value(1));
   current->add(one);
 
   MDefinition* value = current->pop();
 
   switch (op) {
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -537,16 +537,17 @@ class IonBuilder : public MIRGenerator,
   AbortReasonOr<Ok> jsop_bitnot();
   AbortReasonOr<Ok> jsop_bitop(JSOp op);
   AbortReasonOr<Ok> jsop_binary_arith(JSOp op);
   AbortReasonOr<Ok> jsop_binary_arith(JSOp op, MDefinition* left,
                                       MDefinition* right);
   AbortReasonOr<Ok> jsop_pow();
   AbortReasonOr<Ok> jsop_pos();
   AbortReasonOr<Ok> jsop_neg();
+  AbortReasonOr<Ok> jsop_tonumeric();
   AbortReasonOr<Ok> jsop_inc_or_dec(JSOp op);
   AbortReasonOr<Ok> jsop_tostring();
   AbortReasonOr<Ok> jsop_setarg(uint32_t arg);
   AbortReasonOr<Ok> jsop_defvar();
   AbortReasonOr<Ok> jsop_deflexical();
   AbortReasonOr<Ok> jsop_deffun();
   AbortReasonOr<Ok> jsop_notearg();
   AbortReasonOr<Ok> jsop_throwsetconst();
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -651,16 +651,20 @@ static inline bool IsIntType(MIRType typ
   return type == MIRType::Int32 || type == MIRType::Int64;
 }
 
 static inline bool IsNumberType(MIRType type) {
   return type == MIRType::Int32 || type == MIRType::Double ||
          type == MIRType::Float32 || type == MIRType::Int64;
 }
 
+static inline bool IsNumericType(MIRType type) {
+  return IsNumberType(type) || IF_BIGINT(type == MIRType::BigInt, false);
+}
+
 static inline bool IsTypeRepresentableAsDouble(MIRType type) {
   return type == MIRType::Int32 || type == MIRType::Double ||
          type == MIRType::Float32;
 }
 
 static inline bool IsFloatType(MIRType type) {
   return type == MIRType::Int32 || type == MIRType::Float32;
 }
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2121,16 +2121,23 @@ void LIRGenerator::visitToNumberInt32(MT
       // coerces to NaN, not int32.
       MOZ_CRASH("ToInt32 invalid input type");
 
     default:
       MOZ_CRASH("unexpected type");
   }
 }
 
+void LIRGenerator::visitToNumeric(MToNumeric* ins) {
+  MOZ_ASSERT(ins->input()->type() == MIRType::Value);
+  LToNumeric* lir = new (alloc()) LToNumeric(useBoxAtStart(ins->input()));
+  defineBox(lir, ins);
+  assignSafepoint(lir, ins);
+}
+
 void LIRGenerator::visitTruncateToInt32(MTruncateToInt32* truncate) {
   MDefinition* opd = truncate->input();
 
   switch (opd->type()) {
     case MIRType::Value: {
       LValueToInt32* lir = new (alloc()) LValueToInt32(
           useBox(opd), tempDouble(), temp(), LValueToInt32::TRUNCATE);
       assignSnapshot(lir, Bailout_NonPrimitiveInput);
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -3864,16 +3864,31 @@ bool MResumePoint::isObservableOperand(M
 bool MResumePoint::isObservableOperand(size_t index) const {
   return block()->info().isObservableSlot(index);
 }
 
 bool MResumePoint::isRecoverableOperand(MUse* u) const {
   return block()->info().isRecoverableOperand(indexOf(u));
 }
 
+MDefinition* MToNumeric::foldsTo(TempAllocator& alloc) {
+  MDefinition* input = getOperand(0);
+
+  if (input->isBox()) {
+    MDefinition* unboxed = input->getOperand(0);
+    if (IsNumericType(unboxed->type())) {
+      // If the argument is an MBox and we can see that it boxes a numeric
+      // value, ToNumeric can be elided.
+      return input;
+    }
+  }
+
+  return this;
+}
+
 MDefinition* MToNumberInt32::foldsTo(TempAllocator& alloc) {
   MDefinition* input = getOperand(0);
 
   // Fold this operation if the input operand is constant.
   if (input->isConstant()) {
     DebugOnly<IntConversionInputKind> convert = conversion();
     switch (input->type()) {
       case MIRType::Null:
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -4019,16 +4019,49 @@ class MInt64ToFloatingPoint : public MUn
     if (ins->toInt64ToFloatingPoint()->isUnsigned_ != isUnsigned_) {
       return false;
     }
     return congruentIfOperandsEqual(ins);
   }
   AliasSet getAliasSet() const override { return AliasSet::None(); }
 };
 
+// Takes a boxed Value and returns a Value containing either a Number or a
+// BigInt.  Usually this will be the value itself, but it may be an object that
+// has a @@toPrimitive, valueOf, or toString method.
+class MToNumeric : public MUnaryInstruction, public BoxInputsPolicy::Data {
+  MToNumeric(MDefinition* arg, TemporaryTypeSet* types)
+      : MUnaryInstruction(classOpcode, arg) {
+    MOZ_ASSERT(!IsNumericType(arg->type()),
+               "Unboxable definitions don't need ToNumeric");
+    setResultType(MIRType::Value);
+    // Although `types' is always Int32|Double|BigInt, we have to compute it in
+    // IonBuilder to know whether emitting an MToNumeric is needed, so we just
+    // pass it through as an argument instead of recomputing it here.
+    setResultTypeSet(types);
+    setGuard();
+    setMovable();
+  }
+
+ public:
+  INSTRUCTION_HEADER(ToNumeric)
+  static MToNumeric* New(TempAllocator& alloc, MDefinition* arg,
+                         TemporaryTypeSet* types) {
+    return new (alloc) MToNumeric(arg, types);
+  }
+
+  void computeRange(TempAllocator& alloc) override;
+  bool congruentTo(const MDefinition* ins) const override {
+    return congruentIfOperandsEqual(ins);
+  }
+  MDefinition* foldsTo(TempAllocator& alloc) override;
+
+  ALLOW_CLONE(MToNumeric)
+};
+
 // Applies ECMA's ToNumber on a primitive (either typed or untyped) and expects
 // the result to be precisely representable as an Int32, otherwise bails.
 //
 // 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 MToNumberInt32 : public MUnaryInstruction, public ToInt32Policy::Data {
   bool canBeNegativeZero_;
--- a/js/src/jit/RangeAnalysis.cpp
+++ b/js/src/jit/RangeAnalysis.cpp
@@ -1689,16 +1689,20 @@ void MToDouble::computeRange(TempAllocat
 void MToFloat32::computeRange(TempAllocator& alloc) {}
 
 void MTruncateToInt32::computeRange(TempAllocator& alloc) {
   Range* output = new (alloc) Range(getOperand(0));
   output->wrapAroundToInt32();
   setRange(output);
 }
 
+void MToNumeric::computeRange(TempAllocator& alloc) {
+  setRange(new (alloc) Range(getOperand(0)));
+}
+
 void MToNumberInt32::computeRange(TempAllocator& alloc) {
   // No clamping since this computes the range *before* bailouts.
   setRange(new (alloc) Range(getOperand(0)));
 }
 
 void MLimitedTruncate::computeRange(TempAllocator& alloc) {
   Range* output = new (alloc) Range(input());
   setRange(output);
--- a/js/src/jit/TypePolicy.cpp
+++ b/js/src/jit/TypePolicy.cpp
@@ -1219,16 +1219,17 @@ bool FilterTypeSetPolicy::adjustInputs(T
   // Carry over the dependency the MFilterTypeSet had.
   replace->setDependency(ins->dependency());
 
   return true;
 }
 
 // Lists of all TypePolicy specializations which are used by MIR Instructions.
 #define TYPE_POLICY_LIST(_)         \
+  _(AllDoublePolicy)                \
   _(ArithPolicy)                    \
   _(BitwisePolicy)                  \
   _(BoxInputsPolicy)                \
   _(CallPolicy)                     \
   _(CallSetElementPolicy)           \
   _(ClampPolicy)                    \
   _(ComparePolicy)                  \
   _(FilterTypeSetPolicy)            \
@@ -1236,17 +1237,16 @@ bool FilterTypeSetPolicy::adjustInputs(T
   _(PowPolicy)                      \
   _(SameValuePolicy)                \
   _(SignPolicy)                     \
   _(StoreTypedArrayHolePolicy)      \
   _(StoreUnboxedScalarPolicy)       \
   _(StoreUnboxedObjectOrNullPolicy) \
   _(StoreUnboxedStringPolicy)       \
   _(TestPolicy)                     \
-  _(AllDoublePolicy)                \
   _(ToDoublePolicy)                 \
   _(ToInt32Policy)                  \
   _(ToStringPolicy)                 \
   _(TypeBarrierPolicy)
 
 #define TEMPLATE_TYPE_POLICY_LIST(_)                                          \
   _(BoxExceptPolicy<0, MIRType::Object>)                                      \
   _(BoxPolicy<0>)                                                             \
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -1868,10 +1868,32 @@ const VMFunction AddOrUpdateSparseElemen
 
 typedef bool (*GetSparseElementHelperFn)(JSContext* cx, HandleArrayObject obj,
                                          int32_t int_id,
                                          MutableHandleValue result);
 const VMFunction GetSparseElementHelperInfo =
     FunctionInfo<GetSparseElementHelperFn>(GetSparseElementHelper,
                                            "getSparseElementHelper");
 
+#ifdef ENABLE_BIGINT
+template <bool allowBigInt = false>
+#endif
+static bool DoToNumeric(JSContext* cx, HandleValue arg,
+                        MutableHandleValue ret) {
+  ret.set(arg);
+#ifdef ENABLE_BIGINT
+  if (allowBigInt) {
+    return ToNumeric(cx, ret);
+  }
+#endif
+  return ToNumber(cx, ret);
+}
+
+typedef bool (*ToNumericFn)(JSContext*, HandleValue, MutableHandleValue);
+const VMFunction ToNumberInfo =
+    FunctionInfo<ToNumericFn>(DoToNumeric, "ToNumber");
+#ifdef ENABLE_BIGINT
+const VMFunction ToNumericInfo =
+    FunctionInfo<ToNumericFn>(DoToNumeric<true>, "ToNumeric");
+#endif
+
 }  // namespace jit
 }  // namespace js
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -1201,15 +1201,20 @@ extern const VMFunction ProxySetProperty
 extern const VMFunction ProxyHasInfo;
 extern const VMFunction ProxyHasOwnInfo;
 
 extern const VMFunction NativeGetElementInfo;
 
 extern const VMFunction AddOrUpdateSparseElementHelperInfo;
 extern const VMFunction GetSparseElementHelperInfo;
 
+extern const VMFunction ToNumberInfo;
+#ifdef ENABLE_BIGINT
+extern const VMFunction ToNumericInfo;
+#endif
+
 // TailCall VMFunctions
 extern const VMFunction DoConcatStringObjectInfo;
 
 }  // namespace jit
 }  // namespace js
 
 #endif /* jit_VMFunctions_h */
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -5739,16 +5739,32 @@ class LLoadUnboxedExpando : public LInst
     setOperand(0, in);
   }
   const LAllocation* object() { return getOperand(0); }
   const MLoadUnboxedExpando* mir() const {
     return mir_->toLoadUnboxedExpando();
   }
 };
 
+// Ensure that a value is numeric, possibly via a VM call-out that invokes
+// valueOf().
+class LToNumeric : public LInstructionHelper<BOX_PIECES, BOX_PIECES, 0> {
+ public:
+  LIR_HEADER(ToNumeric)
+
+  explicit LToNumeric(const LBoxAllocation& input)
+      : LInstructionHelper(classOpcode) {
+    setBoxOperand(Input, input);
+  }
+
+  static const size_t Input = 0;
+
+  const MToNumeric* mir() const { return mir_->toToNumeric(); }
+};
+
 // Guard that a value is in a TypeSet.
 class LTypeBarrierV : public LInstructionHelper<BOX_PIECES, BOX_PIECES, 2> {
  public:
   LIR_HEADER(TypeBarrierV)
 
   LTypeBarrierV(const LBoxAllocation& input, const LDefinition& unboxTemp,
                 const LDefinition& objTemp)
       : LInstructionHelper(classOpcode) {
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -4314,16 +4314,23 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
       ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]);
       MutableHandleValue res = REGS.stackHandleAt(-1);
       if (!DecOperation(cx, &val, res)) {
         goto error;
       }
     }
     END_CASE(JSOP_DEC)
 
+    CASE(JSOP_TONUMERIC) {
+      if (!ToNumeric(cx, REGS.stackHandleAt(-1))) {
+        goto error;
+      }
+    }
+    END_CASE(JSOP_TONUMERIC)
+
 #ifdef ENABLE_BIGINT
     CASE(JSOP_BIGINT) {
       PUSH_COPY(script->getConst(GET_UINT32_INDEX(REGS.pc)));
       MOZ_ASSERT(REGS.sp[-1].isBigInt());
     }
     END_CASE(JSOP_BIGINT)
 #endif
 
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -345,17 +345,17 @@
      * Pops the value 'val' from the stack, then pushes '+val'.
      * ('+val' is the value converted to a number.)
      *
      *   Category: Operators
      *   Type: Arithmetic Operators
      *   Operands:
      *   Stack: val => (+val)
      */ \
-    MACRO(JSOP_POS, 35, "pos", "+ ", 1, 1, 1, JOF_BYTE|JOF_IC) \
+    MACRO(JSOP_POS, 35, "pos", "+ ", 1, 1, 1, JOF_BYTE) \
     /*
      * Looks up name on the environment chain and deletes it, pushes 'true'
      * onto the stack if succeeded (if the property was present and deleted or
      * if the property wasn't present in the first place), 'false' if not.
      *
      * Strict mode code should never contain this opcode.
      *
      *   Category: Variables and Scopes
@@ -2518,32 +2518,39 @@
      *
      *   Category: Operators
      *   Type: Arithmetic Operators
      *   Operands:
      *   Stack: val => (val - 1)
      */ \
     MACRO(JSOP_DEC, 235, "dec", NULL, 1, 1, 1, JOF_BYTE|JOF_IC) \
     /*
+     * Pop 'val' from the stack, then push the result of 'ToNumeric(val)'.
+     *   Category: Operators
+     *   Type: Arithmetic Operators
+     *   Operands:
+     *   Stack: val => ToNumeric(val)
+     */ \
+    MACRO(JSOP_TONUMERIC, 236, "tonumeric", NULL, 1, 1, 1, JOF_BYTE) \
+    /*
      * Pushes a BigInt constant onto the stack.
      *   Category: Literals
      *   Type: Constants
      *   Operands: uint32_t constIndex
      *   Stack: => val
      */ \
-    IF_BIGINT(MACRO(JSOP_BIGINT, 236, "bigint", NULL, 5, 0, 1, JOF_BIGINT),)
+    IF_BIGINT(MACRO(JSOP_BIGINT, 237, "bigint", NULL, 5, 0, 1, JOF_BIGINT),)
 // clang-format on
 
 /*
  * In certain circumstances it may be useful to "pad out" the opcode space to
  * a power of two.  Use this macro to do so.
  */
 #define FOR_EACH_TRAILING_UNUSED_OPCODE(MACRO) \
-  IF_BIGINT(, MACRO(236))                      \
-  MACRO(237)                                   \
+  IF_BIGINT(, MACRO(237))                      \
   MACRO(238)                                   \
   MACRO(239)                                   \
   MACRO(240)                                   \
   MACRO(241)                                   \
   MACRO(242)                                   \
   MACRO(243)                                   \
   MACRO(244)                                   \
   MACRO(245)                                   \