bug 1508521 - Introduce new increment and decrement operations. r=jandem
authorRobin Templeton <robin@igalia.com>
Thu, 17 Jan 2019 08:09:57 +0000
changeset 511393 560290f263515bb7c7bb834cde8f7d64d5bdde54
parent 511392 cbb4720dc4d5c4f782943a888a4e4ed05320134c
child 511394 ed843bb514f9e7c6c52914663d62d663f5658f19
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1508521
milestone66.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 1508521 - Introduce new increment and decrement operations. r=jandem Differential Revision: https://phabricator.services.mozilla.com/D12378
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/BaselineInspector.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/CacheIRCompiler.cpp
js/src/jit/CacheIRCompiler.h
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/IonIC.cpp
js/src/vm/Interpreter-inl.h
js/src/vm/Interpreter.cpp
js/src/vm/Opcodes.h
--- a/js/src/frontend/ElemOpEmitter.cpp
+++ b/js/src/frontend/ElemOpEmitter.cpp
@@ -220,32 +220,28 @@ bool ElemOpEmitter::emitIncDec() {
 
   if (!emitGet()) {
     //              [stack] ... ELEM
     return false;
   }
 
   MOZ_ASSERT(state_ == State::Get);
 
-  JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
+  JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC;
   if (!bce_->emit1(JSOP_POS)) {
     //              [stack] ... N
     return false;
   }
   if (isPostIncDec()) {
     if (!bce_->emit1(JSOP_DUP)) {
       //            [stack] ... N? N
       return false;
     }
   }
-  if (!bce_->emit1(JSOP_ONE)) {
-    //              [stack] ... N? N 1
-    return false;
-  }
-  if (!bce_->emit1(binOp)) {
+  if (!bce_->emit1(incOp)) {
     //              [stack] ... N? N+1
     return false;
   }
   if (isPostIncDec()) {
     if (isSuper()) {
       //            [stack] THIS KEY OBJ N N+1
 
       if (!bce_->emit2(JSOP_PICK, 4)) {
--- a/js/src/frontend/NameOpEmitter.cpp
+++ b/js/src/frontend/NameOpEmitter.cpp
@@ -347,36 +347,32 @@ bool NameOpEmitter::emitAssignment() {
   state_ = State::Assignment;
 #endif
   return true;
 }
 
 bool NameOpEmitter::emitIncDec() {
   MOZ_ASSERT(state_ == State::Start);
 
-  JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
+  JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC;
   if (!prepareForRhs()) {
     //              [stack] ENV? V
     return false;
   }
   if (!bce_->emit1(JSOP_POS)) {
     //              [stack] ENV? N
     return false;
   }
   if (isPostIncDec()) {
     if (!bce_->emit1(JSOP_DUP)) {
       //            [stack] ENV? N? N
       return false;
     }
   }
-  if (!bce_->emit1(JSOP_ONE)) {
-    //              [stack] ENV? N? N 1
-    return false;
-  }
-  if (!bce_->emit1(binOp)) {
+  if (!bce_->emit1(incOp)) {
     //              [stack] ENV? N? N+1
     return false;
   }
   if (isPostIncDec() && emittedBindOp()) {
     if (!bce_->emit2(JSOP_PICK, 2)) {
       //            [stack] N? N+1 ENV?
       return false;
     }
--- a/js/src/frontend/PropOpEmitter.cpp
+++ b/js/src/frontend/PropOpEmitter.cpp
@@ -203,33 +203,29 @@ bool PropOpEmitter::emitIncDec(JSAtom* p
   MOZ_ASSERT(isIncDec());
 
   if (!emitGet(prop)) {
     return false;
   }
 
   MOZ_ASSERT(state_ == State::Get);
 
-  JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
+  JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC;
 
   if (!bce_->emit1(JSOP_POS)) {
     //              [stack] ... N
     return false;
   }
   if (isPostIncDec()) {
     if (!bce_->emit1(JSOP_DUP)) {
       //            [stack] .. N N
       return false;
     }
   }
-  if (!bce_->emit1(JSOP_ONE)) {
-    //              [stack] ... N? N 1
-    return false;
-  }
-  if (!bce_->emit1(binOp)) {
+  if (!bce_->emit1(incOp)) {
     //              [stack] ... N? N+1
     return false;
   }
   if (isPostIncDec()) {
     if (isSuper()) {
       //            [stack] THIS OBJ N N+1
       if (!bce_->emit2(JSOP_PICK, 3)) {
         //          [stack] OBJ N N+1 THIS
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -2065,16 +2065,26 @@ bool BaselineCodeGen<Handler>::emit_JSOP
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_NEG() {
   return emitUnaryArith();
 }
 
 template <typename Handler>
+bool BaselineCodeGen<Handler>::emit_JSOP_INC() {
+  return emitUnaryArith();
+}
+
+template <typename Handler>
+bool BaselineCodeGen<Handler>::emit_JSOP_DEC() {
+  return emitUnaryArith();
+}
+
+template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_LT() {
   return emitCompare();
 }
 
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_LE() {
   return emitCompare();
 }
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -243,17 +243,19 @@ namespace jit {
   _(JSOP_CHECKCLASSHERITAGE)    \
   _(JSOP_INITHOMEOBJECT)        \
   _(JSOP_BUILTINPROTO)          \
   _(JSOP_OBJWITHPROTO)          \
   _(JSOP_FUNWITHPROTO)          \
   _(JSOP_CLASSCONSTRUCTOR)      \
   _(JSOP_DERIVEDCONSTRUCTOR)    \
   _(JSOP_IMPORTMETA)            \
-  _(JSOP_DYNAMIC_IMPORT)
+  _(JSOP_DYNAMIC_IMPORT)        \
+  _(JSOP_INC)                   \
+  _(JSOP_DEC)
 
 // Base class for BaselineCompiler and BaselineInterpreterGenerator. The Handler
 // template is a class storing fields/methods that are interpreter or compiler
 // specific. This can be combined with template specialization of methods in
 // this class to specialize behavior.
 template <typename Handler>
 class BaselineCodeGen {
  protected:
@@ -356,17 +358,17 @@ class BaselineCodeGen {
   MOZ_MUST_USE bool emitWarmUpCounterIncrement();
   MOZ_MUST_USE bool emitTraceLoggerResume(Register script,
                                           AllocatableGeneralRegisterSet& regs);
 
 #define EMIT_OP(op) bool emit_##op();
   OPCODE_LIST(EMIT_OP)
 #undef EMIT_OP
 
-  // JSOP_NEG, JSOP_BITNOT
+  // JSOP_NEG, JSOP_BITNOT, JSOP_INC, JSOP_DEC
   MOZ_MUST_USE bool emitUnaryArith();
 
   // JSOP_BITXOR, JSOP_LSH, JSOP_ADD etc.
   MOZ_MUST_USE bool emitBinaryArith();
 
   // Handles JSOP_LT, JSOP_GT, and friends
   MOZ_MUST_USE bool emitCompare();
 
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -163,17 +163,19 @@ void ICEntry::trace(JSTracer* trc) {
       case JSOP_IFNE: {
         ICToBool_Fallback::Compiler stubCompiler(cx);
         if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
           return nullptr;
         }
         break;
       }
       case JSOP_BITNOT:
-      case JSOP_NEG: {
+      case JSOP_NEG:
+      case JSOP_INC:
+      case JSOP_DEC: {
         ICUnaryArith_Fallback::Compiler stubCompiler(cx);
         if (!addIC(pc, stubCompiler.getStub(&stubSpace))) {
           return nullptr;
         }
         break;
       }
       case JSOP_BITOR:
       case JSOP_BITXOR:
@@ -5742,32 +5744,44 @@ static bool DoUnaryArithFallback(JSConte
                                  MutableHandleValue res) {
   stub->incrementEnteredCount();
 
   RootedScript script(cx, frame->script());
   jsbytecode* pc = stub->icEntry()->pc(script);
   JSOp op = JSOp(*pc);
   FallbackICSpew(cx, stub, "UnaryArith(%s)", CodeName[op]);
 
+  // The unary operations take a copied val because the original value is needed
+  // below.
+  RootedValue valCopy(cx, val);
   switch (op) {
     case JSOP_BITNOT: {
-      RootedValue valCopy(cx, val);
       if (!BitNot(cx, &valCopy, res)) {
         return false;
       }
       break;
     }
     case JSOP_NEG: {
-      // We copy val here because the original value is needed below.
-      RootedValue valCopy(cx, val);
       if (!NegOperation(cx, &valCopy, res)) {
         return false;
       }
       break;
     }
+    case JSOP_INC: {
+      if (!IncOperation(cx, &valCopy, res)) {
+        return false;
+      }
+      break;
+    }
+    case JSOP_DEC: {
+      if (!DecOperation(cx, &valCopy, res)) {
+        return false;
+      }
+      break;
+    }
     default:
       MOZ_CRASH("Unexpected op");
   }
 
   if (res.isDouble()) {
     stub->setSawDoubleResult();
   }
 
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -2602,16 +2602,18 @@ class ICRest_Fallback : public ICFallbac
       return newStub<ICRest_Fallback>(space, getStubCode(), templateObject);
     }
   };
 };
 
 // UnaryArith
 //     JSOP_BITNOT
 //     JSOP_NEG
+//     JSOP_INC
+//     JSOP_DEC
 
 class ICUnaryArith_Fallback : public ICFallbackStub {
   friend class ICStubSpace;
 
   explicit ICUnaryArith_Fallback(JitCode* stubCode)
       : ICFallbackStub(UnaryArith_Fallback, stubCode) {
     extra_ = 0;
   }
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -400,29 +400,33 @@ static MIRType ParseCacheIRStub(ICStub* 
     case CacheOp::CallNumberToString:
       return MIRType::String;
     case CacheOp::DoubleAddResult:
     case CacheOp::DoubleSubResult:
     case CacheOp::DoubleMulResult:
     case CacheOp::DoubleDivResult:
     case CacheOp::DoubleModResult:
     case CacheOp::DoubleNegationResult:
+    case CacheOp::DoubleIncResult:
+    case CacheOp::DoubleDecResult:
       return MIRType::Double;
     case CacheOp::Int32AddResult:
     case CacheOp::Int32SubResult:
     case CacheOp::Int32MulResult:
     case CacheOp::Int32DivResult:
     case CacheOp::Int32ModResult:
     case CacheOp::Int32BitOrResult:
     case CacheOp::Int32BitXorResult:
     case CacheOp::Int32BitAndResult:
     case CacheOp::Int32LeftShiftResult:
     case CacheOp::Int32RightShiftResult:
     case CacheOp::Int32NotResult:
     case CacheOp::Int32NegationResult:
+    case CacheOp::Int32IncResult:
+    case CacheOp::Int32DecResult:
       return MIRType::Int32;
     // Int32URightShiftResult may return a double under some
     // circumstances.
     case CacheOp::Int32URightShiftResult:
       reader.skip();  // Skip over lhs
       reader.skip();  // Skip over rhs
       return reader.readByte() == 0 ? MIRType::Int32 : MIRType::Double;
     case CacheOp::LoadValueResult:
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -5819,16 +5819,24 @@ bool UnaryArithIRGenerator::tryAttachInt
     case JSOP_BITNOT:
       writer.int32NotResult(intId);
       trackAttached("UnaryArith.Int32Not");
       break;
     case JSOP_NEG:
       writer.int32NegationResult(intId);
       trackAttached("UnaryArith.Int32Neg");
       break;
+    case JSOP_INC:
+      writer.int32IncResult(intId);
+      trackAttached("UnaryArith.Int32Inc");
+      break;
+    case JSOP_DEC:
+      writer.int32DecResult(intId);
+      trackAttached("UnaryArith.Int32Dec");
+      break;
     default:
       MOZ_CRASH("Unexected OP");
   }
 
   writer.returnFromIC();
   return true;
 }
 
@@ -5845,16 +5853,24 @@ bool UnaryArithIRGenerator::tryAttachNum
       truncatedId = writer.truncateDoubleToUInt32(valId);
       writer.int32NotResult(truncatedId);
       trackAttached("UnaryArith.DoubleNot");
       break;
     case JSOP_NEG:
       writer.doubleNegationResult(valId);
       trackAttached("UnaryArith.DoubleNeg");
       break;
+    case JSOP_INC:
+      writer.doubleIncResult(valId);
+      trackAttached("UnaryArith.DoubleInc");
+      break;
+    case JSOP_DEC:
+      writer.doubleDecResult(valId);
+      trackAttached("UnaryArith.DoubleDec");
+      break;
     default:
       MOZ_CRASH("Unexpected OP");
   }
 
   writer.returnFromIC();
   return true;
 }
 
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -309,16 +309,20 @@ extern const char* const CacheKindNames[
   _(Int32BitXorResult)                                                 \
   _(Int32BitAndResult)                                                 \
   _(Int32LeftShiftResult)                                              \
   _(Int32RightShiftResult)                                             \
   _(Int32URightShiftResult)                                            \
   _(Int32NotResult)                                                    \
   _(Int32NegationResult)                                               \
   _(DoubleNegationResult)                                              \
+  _(Int32IncResult)                                                    \
+  _(Int32DecResult)                                                    \
+  _(DoubleIncResult)                                                   \
+  _(DoubleDecResult)                                                   \
   _(LoadInt32TruthyResult)                                             \
   _(LoadDoubleTruthyResult)                                            \
   _(LoadStringTruthyResult)                                            \
   _(LoadObjectTruthyResult)                                            \
   _(LoadValueResult)                                                   \
   _(LoadNewObjectFromTemplateResult)                                   \
                                                                        \
   _(CallStringSplitResult)                                             \
@@ -1172,19 +1176,31 @@ class MOZ_RAII CacheIRWriter : public JS
     buffer_.writeByte(uint32_t(allowDouble));
   }
   void int32NotResult(Int32OperandId id) {
     writeOpWithOperandId(CacheOp::Int32NotResult, id);
   }
   void int32NegationResult(Int32OperandId id) {
     writeOpWithOperandId(CacheOp::Int32NegationResult, id);
   }
+  void int32IncResult(Int32OperandId id) {
+    writeOpWithOperandId(CacheOp::Int32IncResult, id);
+  }
+  void int32DecResult(Int32OperandId id) {
+    writeOpWithOperandId(CacheOp::Int32DecResult, id);
+  }
   void doubleNegationResult(ValOperandId val) {
     writeOpWithOperandId(CacheOp::DoubleNegationResult, val);
   }
+  void doubleIncResult(ValOperandId val) {
+    writeOpWithOperandId(CacheOp::DoubleIncResult, val);
+  }
+  void doubleDecResult(ValOperandId val) {
+    writeOpWithOperandId(CacheOp::DoubleDecResult, val);
+  }
   void loadBooleanResult(bool val) {
     writeOp(CacheOp::LoadBooleanResult);
     buffer_.writeByte(uint32_t(val));
   }
   void loadUndefinedResult() { writeOp(CacheOp::LoadUndefinedResult); }
   void loadStringResult(JSString* str) {
     writeOp(CacheOp::LoadStringResult);
     addStubField(uintptr_t(str), StubField::Type::String);
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -2362,16 +2362,50 @@ bool CacheIRCompiler::emitInt32NegationR
   // Guard against 0 and MIN_INT by checking if low 31-bits are all zero.
   // Both of these result in a double.
   masm.branchTest32(Assembler::Zero, val, Imm32(0x7fffffff), failure->label());
   masm.neg32(val);
   masm.tagValue(JSVAL_TYPE_INT32, val, output.valueReg());
   return true;
 }
 
+bool CacheIRCompiler::emitInt32IncResult() {
+  AutoOutputRegister output(*this);
+  Register input = allocator.useRegister(masm, reader.int32OperandId());
+  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
+
+  FailurePath* failure;
+  if (!addFailurePath(&failure)) {
+    return false;
+  }
+
+  masm.mov(input, scratch);
+  masm.branchAdd32(Assembler::Overflow, Imm32(1), scratch, failure->label());
+  EmitStoreResult(masm, scratch, JSVAL_TYPE_INT32, output);
+
+  return true;
+}
+
+bool CacheIRCompiler::emitInt32DecResult() {
+  AutoOutputRegister output(*this);
+  Register input = allocator.useRegister(masm, reader.int32OperandId());
+  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
+
+  FailurePath* failure;
+  if (!addFailurePath(&failure)) {
+    return false;
+  }
+
+  masm.mov(input, scratch);
+  masm.branchSub32(Assembler::Overflow, Imm32(1), scratch, failure->label());
+  EmitStoreResult(masm, scratch, JSVAL_TYPE_INT32, output);
+
+  return true;
+}
+
 bool CacheIRCompiler::emitInt32NotResult() {
   AutoOutputRegister output(*this);
   Register val = allocator.useRegister(masm, reader.int32OperandId());
   masm.not32(val);
   masm.tagValue(JSVAL_TYPE_INT32, val, output.valueReg());
   return true;
 }
 
@@ -2404,16 +2438,63 @@ bool CacheIRCompiler::emitDoubleNegation
     masm.pop(FloatReg0);
     masm.jump(failure->label());
   }
 
   masm.bind(&done);
   return true;
 }
 
+bool CacheIRCompiler::emitDoubleIncDecResult(bool isInc) {
+  AutoOutputRegister output(*this);
+  ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
+
+  FailurePath* failure;
+  if (!addFailurePath(&failure)) {
+    return false;
+  }
+
+  // If we're compiling a Baseline IC, FloatReg0 is always available.
+  Label failurePopReg, done;
+  if (mode_ != Mode::Baseline) {
+    masm.push(FloatReg0);
+  }
+
+  masm.ensureDouble(
+      val, FloatReg0,
+      (mode_ != Mode::Baseline) ? &failurePopReg : failure->label());
+  masm.loadConstantDouble(1.0, ScratchDoubleReg);
+  if (isInc) {
+    masm.addDouble(ScratchDoubleReg, FloatReg0);
+  } else {
+    masm.subDouble(ScratchDoubleReg, FloatReg0);
+  }
+  masm.boxDouble(FloatReg0, output.valueReg(), FloatReg0);
+
+  if (mode_ != Mode::Baseline) {
+    masm.pop(FloatReg0);
+    masm.jump(&done);
+
+    masm.bind(&failurePopReg);
+    masm.pop(FloatReg0);
+    masm.jump(failure->label());
+  }
+
+  masm.bind(&done);
+  return true;
+}
+
+bool CacheIRCompiler::emitDoubleIncResult() {
+  return emitDoubleIncDecResult(true);
+}
+
+bool CacheIRCompiler::emitDoubleDecResult() {
+  return emitDoubleIncDecResult(false);
+}
+
 bool CacheIRCompiler::emitTruncateDoubleToUInt32() {
   ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
   Register res = allocator.defineRegister(masm, reader.int32OperandId());
 
   Label int32, done;
   masm.branchTestInt32(Assembler::Equal, val, &int32);
 
   Label doneTruncate, truncateABICall;
--- a/js/src/jit/CacheIRCompiler.h
+++ b/js/src/jit/CacheIRCompiler.h
@@ -78,16 +78,20 @@ namespace jit {
   _(Int32BitOrResult)                     \
   _(Int32BitXorResult)                    \
   _(Int32BitAndResult)                    \
   _(Int32LeftShiftResult)                 \
   _(Int32RightShiftResult)                \
   _(Int32URightShiftResult)               \
   _(Int32NegationResult)                  \
   _(Int32NotResult)                       \
+  _(Int32IncResult)                       \
+  _(Int32DecResult)                       \
+  _(DoubleIncResult)                      \
+  _(DoubleDecResult)                      \
   _(DoubleNegationResult)                 \
   _(TruncateDoubleToUInt32)               \
   _(LoadArgumentsObjectLengthResult)      \
   _(LoadFunctionLengthResult)             \
   _(LoadStringLengthResult)               \
   _(LoadStringCharResult)                 \
   _(LoadArgumentsObjectArgResult)         \
   _(LoadInstanceOfObjectResult)           \
@@ -783,16 +787,18 @@ class MOZ_RAII CacheIRCompiler {
   void emitPostBarrierElement(Register obj, const T& val, Register scratch,
                               Register index) {
     MOZ_ASSERT(index != InvalidReg);
     emitPostBarrierShared(obj, val, scratch, index);
   }
 
   bool emitComparePointerResultShared(bool symbol);
 
+  bool emitDoubleIncDecResult(bool isInc);
+
 #define DEFINE_SHARED_OP(op) MOZ_MUST_USE bool emit##op();
   CACHE_IR_SHARED_OPS(DEFINE_SHARED_OP)
 #undef DEFINE_SHARED_OP
 
   void emitLoadStubField(StubFieldOffset val, Register dest);
   void emitLoadStubFieldConstant(StubFieldOffset val, Register dest);
 
   uintptr_t readStubWord(uint32_t offset, StubField::Type type) {
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -714,16 +714,18 @@ AbortReasonOr<Ok> IonBuilder::analyzeNew
           type = MIRType::Symbol;
           break;
         case JSOP_ADD:
         case JSOP_SUB:
         case JSOP_MUL:
         case JSOP_DIV:
         case JSOP_MOD:
         case JSOP_NEG:
+        case JSOP_INC:
+        case JSOP_DEC:
           type = inspector->expectedResultType(last);
           break;
         default:
           break;
       }
       if (type != MIRType::None) {
         if (!phi->addBackedgeType(alloc(), type, nullptr)) {
           return abort(AbortReason::Alloc);
@@ -1906,16 +1908,20 @@ AbortReasonOr<Ok> IonBuilder::inspectOpc
       return jsop_pow();
 
     case JSOP_POS:
       return jsop_pos();
 
     case JSOP_NEG:
       return jsop_neg();
 
+    case JSOP_INC:
+    case JSOP_DEC:
+      return jsop_inc_or_dec(op);
+
     case JSOP_TOSTRING:
       return jsop_tostring();
 
     case JSOP_DEFVAR:
       return jsop_defvar();
 
     case JSOP_DEFLET:
     case JSOP_DEFCONST:
@@ -3649,16 +3655,24 @@ AbortReasonOr<Ok> IonBuilder::arithTryBi
     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_INC:
+      MOZ_ASSERT(op == JSOP_ADD && right->toConstant()->toInt32() == 1);
+      stub = MUnaryCache::New(alloc(), left);
+      break;
+    case JSOP_DEC:
+      MOZ_ASSERT(op == JSOP_SUB && right->toConstant()->toInt32() == 1);
+      stub = MUnaryCache::New(alloc(), left);
+      break;
     case JSOP_ADD:
     case JSOP_SUB:
     case JSOP_MUL:
     case JSOP_DIV:
     case JSOP_MOD:
       stub = MBinaryCache::New(alloc(), left, right, MIRType::Value);
       break;
     default:
@@ -3785,16 +3799,37 @@ 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_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) {
+    case JSOP_INC:
+      op = JSOP_ADD;
+      break;
+    case JSOP_DEC:
+      op = JSOP_SUB;
+      break;
+    default:
+      MOZ_CRASH("jsop_inc_or_dec with bad op");
+  }
+
+  return jsop_binary_arith(op, value, one);
+}
+
 AbortReasonOr<Ok> IonBuilder::jsop_tostring() {
   if (current->peek(-1)->type() == MIRType::String) {
     return Ok();
   }
 
   MDefinition* value = current->pop();
   MToString* ins = MToString::New(alloc(), value);
   current->add(ins);
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -530,16 +530,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_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();
   AbortReasonOr<Ok> jsop_checklexical();
--- a/js/src/jit/IonIC.cpp
+++ b/js/src/jit/IonIC.cpp
@@ -489,32 +489,44 @@ static void TryAttachIonStub(JSContext* 
                                            HandleScript outerScript,
                                            IonUnaryArithIC* ic, HandleValue val,
                                            MutableHandleValue res) {
   IonScript* ionScript = outerScript->ionScript();
   RootedScript script(cx, ic->script());
   jsbytecode* pc = ic->pc();
   JSOp op = JSOp(*pc);
 
+  // The unary operations take a copied val because the original value is needed
+  // below.
+  RootedValue valCopy(cx, val);
   switch (op) {
     case JSOP_BITNOT: {
-      RootedValue valCopy(cx, val);
       if (!BitNot(cx, &valCopy, res)) {
         return false;
       }
       break;
     }
     case JSOP_NEG: {
-      // We copy val here because the original value is needed below.
-      RootedValue valCopy(cx, val);
       if (!NegOperation(cx, &valCopy, res)) {
         return false;
       }
       break;
     }
+    case JSOP_INC: {
+      if (!IncOperation(cx, &valCopy, res)) {
+        return false;
+      }
+      break;
+    }
+    case JSOP_DEC: {
+      if (!DecOperation(cx, &valCopy, res)) {
+        return false;
+      }
+      break;
+    }
     default:
       MOZ_CRASH("Unexpected op");
   }
 
   TryAttachIonStub<UnaryArithIRGenerator, IonUnaryArithIC>(cx, ic, ionScript,
                                                            op, val, res);
 
   return true;
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -400,16 +400,46 @@ static MOZ_ALWAYS_INLINE bool NegOperati
     return BigInt::neg(cx, val, res);
   }
 #endif
 
   res.setNumber(-val.toNumber());
   return true;
 }
 
+static MOZ_ALWAYS_INLINE bool IncOperation(JSContext* cx,
+                                           MutableHandleValue val,
+                                           MutableHandleValue res) {
+  MOZ_ASSERT(val.isNumber(), "+1 only callable on result of JSOP_TONUMERIC");
+
+  int32_t i;
+  if (val.isInt32() && (i = val.toInt32()) != INT32_MAX) {
+    res.setInt32(i + 1);
+    return true;
+  }
+
+  res.setNumber(val.toNumber() + 1);
+  return true;
+}
+
+static MOZ_ALWAYS_INLINE bool DecOperation(JSContext* cx,
+                                           MutableHandleValue val,
+                                           MutableHandleValue res) {
+  MOZ_ASSERT(val.isNumber(), "-1 only callable on result of JSOP_TONUMERIC");
+
+  int32_t i;
+  if (val.isInt32() && (i = val.toInt32()) != INT32_MIN) {
+    res.setInt32(i - 1);
+    return true;
+  }
+
+  res.setNumber(val.toNumber() - 1);
+  return true;
+}
+
 static MOZ_ALWAYS_INLINE bool ToIdOperation(JSContext* cx, HandleValue idval,
                                             MutableHandleValue res) {
   if (idval.isInt32()) {
     res.set(idval);
     return true;
   }
 
   RootedId id(cx);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -4335,16 +4335,34 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
       }
 #endif
     }
     END_CASE(JSOP_DEBUGCHECKSELFHOSTED)
 
     CASE(JSOP_IS_CONSTRUCTING) { PUSH_MAGIC(JS_IS_CONSTRUCTING); }
     END_CASE(JSOP_IS_CONSTRUCTING)
 
+    CASE(JSOP_INC) {
+      ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]);
+      MutableHandleValue res = REGS.stackHandleAt(-1);
+      if (!IncOperation(cx, &val, res)) {
+        goto error;
+      }
+    }
+    END_CASE(JSOP_INC)
+
+    CASE(JSOP_DEC) {
+      ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]);
+      MutableHandleValue res = REGS.stackHandleAt(-1);
+      if (!DecOperation(cx, &val, res)) {
+        goto error;
+      }
+    }
+    END_CASE(JSOP_DEC)
+
 #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
@@ -2507,33 +2507,49 @@
      *
      *   Category: Variables and Scopes
      *   Type: Modules
      *   Operands:
      *   Stack: arg => rval
      */ \
     MACRO(JSOP_DYNAMIC_IMPORT, 233, "call-import", NULL, 1, 1, 1, JOF_BYTE) \
     /*
+     * Pops the numeric value 'val' from the stack, then pushes 'val + 1'.
+     *
+     *   Category: Operators
+     *   Type: Arithmetic Operators
+     *   Operands:
+     *   Stack: val => (val + 1)
+     */ \
+    MACRO(JSOP_INC, 234, "inc", NULL, 1, 1, 1, JOF_BYTE|JOF_IC) \
+    /*
+     * Pops the numeric value 'val' from the stack, then pushes 'val - 1'.
+     *
+     *   Category: Operators
+     *   Type: Arithmetic Operators
+     *   Operands:
+     *   Stack: val => (val - 1)
+     */ \
+    MACRO(JSOP_DEC, 235, "dec", NULL, 1, 1, 1, JOF_BYTE|JOF_IC) \
+    /*
      * Pushes a BigInt constant onto the stack.
      *   Category: Literals
      *   Type: Constants
      *   Operands: uint32_t constIndex
      *   Stack: => val
      */ \
-    IF_BIGINT(MACRO(JSOP_BIGINT, 234, "bigint", NULL, 5, 0, 1, JOF_BIGINT),)
+    IF_BIGINT(MACRO(JSOP_BIGINT, 236, "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(234))                      \
-  MACRO(235)                                   \
-  MACRO(236)                                   \
+  IF_BIGINT(, MACRO(236))                      \
   MACRO(237)                                   \
   MACRO(238)                                   \
   MACRO(239)                                   \
   MACRO(240)                                   \
   MACRO(241)                                   \
   MACRO(242)                                   \
   MACRO(243)                                   \
   MACRO(244)                                   \