Bug 1526309 - Add BigInt support to JSOP_INC and JSOP_DEC r=jandem,terpri
authorAndy Wingo <wingo@igalia.com>
Fri, 15 Feb 2019 09:48:33 +0000
changeset 459545 11f5a8e7853b0d751996a28b8797e7837b198e51
parent 459544 ed60c55e6f005b5a382606ec1d4fbc71fd5272c6
child 459546 4cb13cbe5f051f6b55d5e7d089916e6d86fd6383
push id35563
push userccoroiu@mozilla.com
push dateSat, 16 Feb 2019 09:36:04 +0000
treeherdermozilla-central@1cfd69d05aa1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem, terpri
bugs1526309
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 1526309 - Add BigInt support to JSOP_INC and JSOP_DEC r=jandem,terpri Differential Revision: https://phabricator.services.mozilla.com/D19373
js/public/TrackedOptimizationInfo.h
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/tests/jstests.list
js/src/vm/BigIntType.cpp
js/src/vm/BigIntType.h
js/src/vm/Interpreter-inl.h
--- a/js/public/TrackedOptimizationInfo.h
+++ b/js/public/TrackedOptimizationInfo.h
@@ -53,16 +53,20 @@ namespace JS {
   _(SetElem_InlineCache)                    \
                                             \
   _(BinaryArith_Concat)                     \
   _(BinaryArith_SpecializedTypes)           \
   _(BinaryArith_SpecializedOnBaselineTypes) \
   _(BinaryArith_SharedCache)                \
   _(BinaryArith_Call)                       \
                                             \
+  _(UnaryArith_SpecializedTypes)            \
+  _(UnaryArith_SpecializedOnBaselineTypes)  \
+  _(UnaryArith_InlineCache)                 \
+                                            \
   _(InlineCache_OptimizedStub)              \
                                             \
   _(NewArray_TemplateObject)                \
   _(NewArray_SharedCache)                   \
   _(NewArray_Call)                          \
                                             \
   _(NewObject_TemplateObject)               \
   _(NewObject_SharedCache)                  \
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -3438,17 +3438,17 @@ AbortReasonOr<Ok> IonBuilder::jsop_bitop
   current->push(ins);
   if (ins->isEffectful()) {
     MOZ_TRY(resumeAfter(ins));
   }
 
   return Ok();
 }
 
-MDefinition::Opcode JSOpToMDefinition(JSOp op) {
+MDefinition::Opcode BinaryJSOpToMDefinition(JSOp op) {
   switch (op) {
     case JSOP_ADD:
       return MDefinition::Opcode::Add;
     case JSOP_SUB:
       return MDefinition::Opcode::Sub;
     case JSOP_MUL:
       return MDefinition::Opcode::Mul;
     case JSOP_DIV:
@@ -3546,16 +3546,46 @@ AbortReasonOr<Ok> IonBuilder::powTrySpec
     output = toDouble;
   }
 
   current->push(output);
   *emitted = true;
   return Ok();
 }
 
+MIRType IonBuilder::binaryArithNumberSpecialization(MDefinition* left,
+                                                    MDefinition* right) {
+  // Try to specialize as int32.
+  if (left->type() == MIRType::Int32 && right->type() == MIRType::Int32 &&
+      !inspector->hasSeenDoubleResult(pc)) {
+    return MIRType::Int32;
+  }
+  return MIRType::Double;
+}
+
+AbortReasonOr<MBinaryArithInstruction*> IonBuilder::binaryArithEmitSpecialized(
+    MDefinition::Opcode op, MIRType specialization, MDefinition* left,
+    MDefinition* right) {
+  MBinaryArithInstruction* ins =
+      MBinaryArithInstruction::New(alloc(), op, left, right);
+  ins->setSpecialization(specialization);
+
+  if (op == MDefinition::Opcode::Add || op == MDefinition::Opcode::Mul) {
+    ins->setCommutative();
+  }
+
+  current->add(ins);
+  current->push(ins);
+
+  MOZ_ASSERT(!ins->isEffectful());
+  MOZ_TRY(maybeInsertResume());
+
+  return ins;
+}
+
 AbortReasonOr<Ok> IonBuilder::binaryArithTrySpecialized(bool* emitted, JSOp op,
                                                         MDefinition* left,
                                                         MDefinition* right) {
   MOZ_ASSERT(*emitted == false);
 
   // Try to emit a specialized binary instruction based on the input types
   // of the operands.
 
@@ -3568,30 +3598,28 @@ AbortReasonOr<Ok> IonBuilder::binaryArit
   }
 
   // One of the inputs need to be a number.
   if (!IsNumberType(left->type()) && !IsNumberType(right->type())) {
     trackOptimizationOutcome(TrackedOutcome::OperandNotNumber);
     return Ok();
   }
 
-  MDefinition::Opcode defOp = JSOpToMDefinition(op);
-  MBinaryArithInstruction* ins =
-      MBinaryArithInstruction::New(alloc(), defOp, left, right);
-  ins->setNumberSpecialization(alloc(), inspector, pc);
-
-  if (op == JSOP_ADD || op == JSOP_MUL) {
-    ins->setCommutative();
-  }
-
-  current->add(ins);
-  current->push(ins);
-
-  MOZ_ASSERT(!ins->isEffectful());
-  MOZ_TRY(maybeInsertResume());
+  MDefinition::Opcode defOp = BinaryJSOpToMDefinition(op);
+  MIRType specialization = binaryArithNumberSpecialization(left, right);
+  MBinaryArithInstruction* ins;
+  MOZ_TRY_VAR(ins,
+              binaryArithEmitSpecialized(defOp, specialization, left, right));
+
+  // Relax int32 to double if, despite the fact that we have int32 operands and
+  // we've never seen a double result, we know the result may overflow or be a
+  // double.
+  if (specialization == MIRType::Int32 && ins->constantDoubleResult(alloc())) {
+    ins->setSpecialization(MIRType::Double);
+  }
 
   trackOptimizationSuccess();
   *emitted = true;
   return Ok();
 }
 
 AbortReasonOr<Ok> IonBuilder::binaryArithTrySpecializedOnBaselineInspector(
     bool* emitted, JSOp op, MDefinition* left, MDefinition* right) {
@@ -3604,26 +3632,18 @@ AbortReasonOr<Ok> IonBuilder::binaryArit
       TrackedStrategy::BinaryArith_SpecializedOnBaselineTypes);
 
   MIRType specialization = inspector->expectedBinaryArithSpecialization(pc);
   if (specialization == MIRType::None) {
     trackOptimizationOutcome(TrackedOutcome::SpeculationOnInputTypesFailed);
     return Ok();
   }
 
-  MDefinition::Opcode def_op = JSOpToMDefinition(op);
-  MBinaryArithInstruction* ins =
-      MBinaryArithInstruction::New(alloc(), def_op, left, right);
-  ins->setSpecialization(specialization);
-
-  current->add(ins);
-  current->push(ins);
-
-  MOZ_ASSERT(!ins->isEffectful());
-  MOZ_TRY(maybeInsertResume());
+  MDefinition::Opcode defOp = BinaryJSOpToMDefinition(op);
+  MOZ_TRY(binaryArithEmitSpecialized(defOp, specialization, left, right));
 
   trackOptimizationSuccess();
   *emitted = true;
   return Ok();
 }
 
 AbortReasonOr<Ok> IonBuilder::arithTryBinaryStub(bool* emitted, JSOp op,
                                                  MDefinition* left,
@@ -3647,24 +3667,16 @@ 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:
@@ -3716,19 +3728,19 @@ AbortReasonOr<Ok> IonBuilder::jsop_binar
   if (emitted) {
     return Ok();
   }
 
   // Not possible to optimize. Do a slow vm call.
   trackOptimizationAttempt(TrackedStrategy::BinaryArith_Call);
   trackOptimizationSuccess();
 
-  MDefinition::Opcode def_op = JSOpToMDefinition(op);
+  MDefinition::Opcode defOp = BinaryJSOpToMDefinition(op);
   MBinaryArithInstruction* ins =
-      MBinaryArithInstruction::New(alloc(), def_op, left, right);
+      MBinaryArithInstruction::New(alloc(), defOp, left, right);
 
   // Decrease type from 'any type' to 'empty type' when one of the operands
   // is 'empty typed'.
   maybeMarkEmpty(ins);
 
   current->add(ins);
   current->push(ins);
   MOZ_ASSERT(ins->isEffectful());
@@ -3827,35 +3839,116 @@ AbortReasonOr<Ok> IonBuilder::jsop_tonum
   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();
-
+MDefinition* IonBuilder::unaryArithConvertToBinary(JSOp op,
+                                                   MDefinition::Opcode* defOp) {
   switch (op) {
-    case JSOP_INC:
-      op = JSOP_ADD;
-      break;
-    case JSOP_DEC:
-      op = JSOP_SUB;
-      break;
+    case JSOP_INC: {
+      *defOp = MDefinition::Opcode::Add;
+      MConstant* right = MConstant::New(alloc(), Int32Value(1));
+      current->add(right);
+      return right;
+    }
+    case JSOP_DEC: {
+      *defOp = MDefinition::Opcode::Sub;
+      MConstant* right = MConstant::New(alloc(), Int32Value(1));
+      current->add(right);
+      return right;
+    }
     default:
-      MOZ_CRASH("jsop_inc_or_dec with bad op");
-  }
-
-  return jsop_binary_arith(op, value, one);
+      MOZ_CRASH("unexpected unary opcode");
+  }
+}
+
+AbortReasonOr<Ok> IonBuilder::unaryArithTrySpecialized(bool* emitted, JSOp op,
+                                                       MDefinition* value) {
+  MOZ_ASSERT(*emitted == false);
+
+  // Try to convert Inc(x) or Dec(x) to Add(x,1) or Sub(x,1) if the operand is a
+  // number.
+
+  trackOptimizationAttempt(TrackedStrategy::UnaryArith_SpecializedTypes);
+
+  if (!IsNumberType(value->type())) {
+    trackOptimizationOutcome(TrackedOutcome::OperandNotNumber);
+    return Ok();
+  }
+
+  MDefinition::Opcode defOp;
+  MDefinition* rhs = unaryArithConvertToBinary(op, &defOp);
+  MIRType specialization = binaryArithNumberSpecialization(value, rhs);
+  MOZ_TRY(binaryArithEmitSpecialized(defOp, specialization, value, rhs));
+
+  trackOptimizationSuccess();
+  *emitted = true;
+  return Ok();
+}
+
+AbortReasonOr<Ok> IonBuilder::unaryArithTrySpecializedOnBaselineInspector(
+    bool* emitted, JSOp op, MDefinition* value) {
+  MOZ_ASSERT(*emitted == false);
+
+  // Try to emit a specialized binary instruction speculating the
+  // type using the baseline caches.
+
+  trackOptimizationAttempt(
+      TrackedStrategy::UnaryArith_SpecializedOnBaselineTypes);
+
+  MIRType specialization = inspector->expectedBinaryArithSpecialization(pc);
+  if (specialization == MIRType::None) {
+    trackOptimizationOutcome(TrackedOutcome::SpeculationOnInputTypesFailed);
+    return Ok();
+  }
+
+  MDefinition::Opcode defOp;
+  MDefinition* rhs = unaryArithConvertToBinary(op, &defOp);
+  MOZ_TRY(binaryArithEmitSpecialized(defOp, specialization, value, rhs));
+
+  trackOptimizationSuccess();
+  *emitted = true;
+  return Ok();
+}
+
+AbortReasonOr<Ok> IonBuilder::jsop_inc_or_dec(JSOp op) {
+  bool emitted = false;
+  MDefinition* value = current->pop();
+
+  startTrackingOptimizations();
+
+  trackTypeInfo(TrackedTypeSite::Operand, value->type(),
+                value->resultTypeSet());
+
+  MOZ_TRY(unaryArithTrySpecialized(&emitted, op, value));
+  if (emitted) {
+    return Ok();
+  }
+
+  MOZ_TRY(unaryArithTrySpecializedOnBaselineInspector(&emitted, op, value));
+  if (emitted) {
+    return Ok();
+  }
+
+  trackOptimizationAttempt(TrackedStrategy::UnaryArith_InlineCache);
+  trackOptimizationSuccess();
+
+  MInstruction* stub = MUnaryCache::New(alloc(), value);
+  current->add(stub);
+  current->push(stub);
+
+  // Decrease type from 'any type' to 'empty type' when one of the operands
+  // is 'empty typed'.
+  maybeMarkEmpty(stub);
+
+  return resumeAfter(stub);
 }
 
 AbortReasonOr<Ok> IonBuilder::jsop_tostring() {
   if (current->peek(-1)->type() == MIRType::String) {
     return Ok();
   }
 
   MDefinition* value = current->pop();
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -341,29 +341,41 @@ class IonBuilder : public MIRGenerator,
       ScalarTypeDescr::Type type, MDefinition* value);
   AbortReasonOr<Ok> setPropTryCache(bool* emitted, MDefinition* obj,
                                     PropertyName* name, MDefinition* value,
                                     bool barrier);
 
   // jsop_binary_arith helpers.
   MBinaryArithInstruction* binaryArithInstruction(JSOp op, MDefinition* left,
                                                   MDefinition* right);
+  MIRType binaryArithNumberSpecialization(MDefinition* left,
+                                          MDefinition* right);
   AbortReasonOr<Ok> binaryArithTryConcat(bool* emitted, JSOp op,
                                          MDefinition* left, MDefinition* right);
+  AbortReasonOr<MBinaryArithInstruction*> binaryArithEmitSpecialized(
+      MDefinition::Opcode op, MIRType specialization, MDefinition* left,
+      MDefinition* right);
   AbortReasonOr<Ok> binaryArithTrySpecialized(bool* emitted, JSOp op,
                                               MDefinition* left,
                                               MDefinition* right);
   AbortReasonOr<Ok> binaryArithTrySpecializedOnBaselineInspector(
       bool* emitted, JSOp op, MDefinition* left, MDefinition* right);
   AbortReasonOr<Ok> arithTryBinaryStub(bool* emitted, JSOp op,
                                        MDefinition* left, MDefinition* right);
 
   // jsop_bitnot helpers.
   AbortReasonOr<Ok> bitnotTrySpecialized(bool* emitted, MDefinition* input);
 
+  // jsop_inc_or_dec helpers.
+  MDefinition* unaryArithConvertToBinary(JSOp op, MDefinition::Opcode* defOp);
+  AbortReasonOr<Ok> unaryArithTrySpecialized(bool* emitted, JSOp op,
+                                             MDefinition* value);
+  AbortReasonOr<Ok> unaryArithTrySpecializedOnBaselineInspector(
+      bool* emitted, JSOp op, MDefinition* value);
+
   // jsop_pow helpers.
   AbortReasonOr<Ok> powTrySpecialized(bool* emitted, MDefinition* base,
                                       MDefinition* power, MIRType outputType);
 
   // jsop_compare helpers.
   AbortReasonOr<Ok> compareTrySpecialized(bool* emitted, JSOp op,
                                           MDefinition* left,
                                           MDefinition* right);
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -2754,33 +2754,16 @@ MBinaryArithInstruction* MBinaryArithIns
       return MDiv::New(alloc, left, right);
     case Opcode::Mod:
       return MMod::New(alloc, left, right);
     default:
       MOZ_CRASH("unexpected binary opcode");
   }
 }
 
-void MBinaryArithInstruction::setNumberSpecialization(
-    TempAllocator& alloc, BaselineInspector* inspector, jsbytecode* pc) {
-  setSpecialization(MIRType::Double);
-
-  // Try to specialize as int32.
-  if (getOperand(0)->type() == MIRType::Int32 &&
-      getOperand(1)->type() == MIRType::Int32) {
-    bool seenDouble = inspector->hasSeenDoubleResult(pc);
-
-    // Use int32 specialization if the operation doesn't overflow on its
-    // constant operands and if the operation has never overflowed.
-    if (!seenDouble && !constantDoubleResult(alloc)) {
-      setInt32Specialization();
-    }
-  }
-}
-
 bool MBinaryArithInstruction::constantDoubleResult(TempAllocator& alloc) {
   bool typeChange = false;
   EvaluateConstantOperands(alloc, this, &typeChange);
   return typeChange;
 }
 
 MDefinition* MRsh::foldsTo(TempAllocator& alloc) {
   MDefinition* f = MBinaryBitwiseInstruction::foldsTo(alloc);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -4724,18 +4724,16 @@ class MBinaryArithInstruction : public M
   void setSpecialization(MIRType type) {
     specialization_ = type;
     setResultType(type);
   }
   void setInt32Specialization() {
     specialization_ = MIRType::Int32;
     setResultType(MIRType::Int32);
   }
-  void setNumberSpecialization(TempAllocator& alloc,
-                               BaselineInspector* inspector, jsbytecode* pc);
 
   virtual void trySpecializeFloat32(TempAllocator& alloc) override;
 
   bool congruentTo(const MDefinition* ins) const override {
     if (!binaryCongruentTo(ins)) {
       return false;
     }
     const auto* other = static_cast<const MBinaryArithInstruction*>(ins);
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -956,22 +956,16 @@ skip script test262/built-ins/DataView/p
 skip script test262/built-ins/DataView/prototype/getBigUint64/to-boolean-littleendian.js
 skip script test262/built-ins/DataView/prototype/getBigUint64/toindex-byteoffset.js
 skip script test262/built-ins/DataView/prototype/getBigUint64/length.js
 skip script test262/built-ins/DataView/prototype/getBigUint64/toindex-byteoffset-toprimitive.js
 skip script test262/built-ins/DataView/prototype/getBigUint64/return-values.js
 skip script test262/built-ins/DataView/prototype/getBigUint64/index-is-out-of-range.js
 skip script test262/built-ins/DataView/prototype/getBigUint64/toindex-byteoffset-wrapped-values.js
 
-# https://bugzilla.mozilla.org/show_bug.cgi?id=1501105
-skip script test262/language/expressions/prefix-increment/bigint.js
-skip script test262/language/expressions/postfix-increment/bigint.js
-skip script test262/language/expressions/postfix-decrement/bigint.js
-skip script test262/language/expressions/prefix-decrement/bigint.js
-
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317405
 skip script test262/language/computed-property-names/class/static/method-number.js
 skip script test262/language/computed-property-names/class/static/method-string.js
 skip script test262/language/computed-property-names/class/static/method-symbol.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1286997
 # Bug 1286997 probably doesn't cover all spec violations.
 skip script test262/language/expressions/assignment/S11.13.1_A5_T5.js
--- a/js/src/vm/BigIntType.cpp
+++ b/js/src/vm/BigIntType.cpp
@@ -181,26 +181,30 @@ js::HashNumber BigInt::hash() {
 size_t BigInt::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
   return hasInlineDigits() ? 0 : mallocSizeOf(heapDigits_);
 }
 
 BigInt* BigInt::zero(JSContext* cx) {
   return createUninitialized(cx, 0, false);
 }
 
-BigInt* BigInt::one(JSContext* cx) {
-  BigInt* ret = createUninitialized(cx, 1, false);
-
-  if (!ret) {
+BigInt* BigInt::createFromDigit(JSContext* cx, Digit d, bool isNegative) {
+  MOZ_ASSERT(d != 0);
+  BigInt* res = createUninitialized(cx, 1, isNegative);
+  if (!res) {
     return nullptr;
   }
-
-  ret->setDigit(0, 1);
-
-  return ret;
+  res->setDigit(0, d);
+  return res;
+}
+
+BigInt* BigInt::one(JSContext* cx) { return createFromDigit(cx, 1, false); }
+
+BigInt* BigInt::negativeOne(JSContext* cx) {
+  return createFromDigit(cx, 1, true);
 }
 
 BigInt* BigInt::neg(JSContext* cx, HandleBigInt x) {
   if (x->isZero()) {
     return x;
   }
 
   BigInt* result = copy(cx, x);
@@ -971,45 +975,73 @@ BigInt* BigInt::absoluteAddOne(JSContext
     result->setDigit(inputLength, 1);
   } else {
     MOZ_ASSERT(!carry);
   }
 
   return destructivelyTrimHighZeroDigits(cx, result);
 }
 
-// Like the above, but you can specify that the allocated result should have
-// length `resultLength`, which must be at least as large as `x->digitLength()`.
-// The result will be unsigned.
 BigInt* BigInt::absoluteSubOne(JSContext* cx, HandleBigInt x,
-                               unsigned resultLength) {
+                               bool resultNegative) {
   MOZ_ASSERT(!x->isZero());
-  MOZ_ASSERT(resultLength >= x->digitLength());
-  bool resultNegative = false;
-  RootedBigInt result(cx,
-                      createUninitialized(cx, resultLength, resultNegative));
+
+  unsigned length = x->digitLength();
+
+  if (length == 1) {
+    Digit d = x->digit(0);
+    if (d == 1) {
+      // Ignore resultNegative.
+      return zero(cx);
+    }
+    return createFromDigit(cx, d - 1, resultNegative);
+  }
+
+  RootedBigInt result(cx, createUninitialized(cx, length, resultNegative));
   if (!result) {
     return nullptr;
   }
 
-  unsigned length = x->digitLength();
   Digit borrow = 1;
   for (unsigned i = 0; i < length; i++) {
     Digit newBorrow = 0;
     result->setDigit(i, digitSub(x->digit(i), borrow, &newBorrow));
     borrow = newBorrow;
   }
   MOZ_ASSERT(!borrow);
-  for (unsigned i = length; i < resultLength; i++) {
-    result->setDigit(i, 0);
-  }
 
   return destructivelyTrimHighZeroDigits(cx, result);
 }
 
+BigInt* BigInt::inc(JSContext* cx, HandleBigInt x) {
+  if (x->isZero()) {
+    return one(cx);
+  }
+
+  bool isNegative = x->isNegative();
+  if (isNegative) {
+    return absoluteSubOne(cx, x, isNegative);
+  }
+
+  return absoluteAddOne(cx, x, isNegative);
+}
+
+BigInt* BigInt::dec(JSContext* cx, HandleBigInt x) {
+  if (x->isZero()) {
+    return negativeOne(cx);
+  }
+
+  bool isNegative = x->isNegative();
+  if (isNegative) {
+    return absoluteAddOne(cx, x, isNegative);
+  }
+
+  return absoluteSubOne(cx, x, isNegative);
+}
+
 // Lookup table for the maximum number of bits required per character of a
 // base-N string representation of a number. To increase accuracy, the array
 // value is the actual value multiplied by 32. To generate this table:
 // for (var i = 0; i <= 36; i++) { print(Math.ceil(Math.log2(i) * 32) + ","); }
 static constexpr uint8_t maxBitsPerCharTable[] = {
     0,   0,   32,  51,  64,  75,  83,  90,  96,  // 0..8
     102, 107, 111, 115, 119, 122, 126, 128,      // 9..16
     131, 134, 136, 139, 141, 143, 145, 147,      // 17..24
@@ -1595,23 +1627,17 @@ BigInt* BigInt::createFromUint64(JSConte
     }
     res->setDigit(0, low);
     if (high) {
       res->setDigit(1, high);
     }
     return res;
   }
 
-  BigInt* res = createUninitialized(cx, 1, isNegative);
-  if (!res) {
-    return nullptr;
-  }
-
-  res->setDigit(0, n);
-  return res;
+  return createFromDigit(cx, n, isNegative);
 }
 
 BigInt* BigInt::createFromInt64(JSContext* cx, int64_t n) {
   BigInt* res = createFromUint64(cx, Abs(n));
   if (!res) {
     return nullptr;
   }
 
@@ -1790,22 +1816,17 @@ BigInt* BigInt::mod(JSContext* cx, Handl
                                      unusedQuotientNegative)) {
       MOZ_CRASH("BigInt div by digit failed unexpectedly");
     }
 
     if (!remainderDigit) {
       return zero(cx);
     }
 
-    BigInt* remainder = createUninitialized(cx, 1, x->isNegative());
-    if (!remainder) {
-      return nullptr;
-    }
-    remainder->setDigit(0, remainderDigit);
-    return remainder;
+    return createFromDigit(cx, remainderDigit, x->isNegative());
   } else {
     RootedBigInt remainder(cx);
     if (!absoluteDivWithBigIntDivisor(cx, x, y, Nothing(), Some(&remainder),
                                       x->isNegative())) {
       return nullptr;
     }
     MOZ_ASSERT(remainder);
     return destructivelyTrimHighZeroDigits(cx, remainder);
@@ -1942,25 +1963,17 @@ BigInt* BigInt::lshByAbsolute(JSContext*
     } else {
       MOZ_ASSERT(!carry);
     }
   }
   return result;
 }
 
 BigInt* BigInt::rshByMaximum(JSContext* cx, bool isNegative) {
-  if (isNegative) {
-    RootedBigInt negativeOne(cx, createUninitialized(cx, 1, isNegative));
-    if (!negativeOne) {
-      return nullptr;
-    }
-    negativeOne->setDigit(0, 1);
-    return negativeOne;
-  }
-  return zero(cx);
+  return isNegative ? negativeOne(cx) : zero(cx);
 }
 
 BigInt* BigInt::rshByAbsolute(JSContext* cx, HandleBigInt x, HandleBigInt y) {
   if (x->isZero() || y->isZero()) {
     return x;
   }
 
   if (y->digitLength() > 1 || y->digit(0) >= MaxBitLength) {
@@ -2059,40 +2072,39 @@ BigInt* BigInt::bitAnd(JSContext* cx, Ha
     return y;
   }
 
   if (!x->isNegative() && !y->isNegative()) {
     return absoluteAnd(cx, x, y);
   }
 
   if (x->isNegative() && y->isNegative()) {
-    int resultLength = std::max(x->digitLength(), y->digitLength()) + 1;
     // (-x) & (-y) == ~(x-1) & ~(y-1) == ~((x-1) | (y-1))
     // == -(((x-1) | (y-1)) + 1)
-    RootedBigInt x1(cx, absoluteSubOne(cx, x, resultLength));
+    RootedBigInt x1(cx, absoluteSubOne(cx, x));
     if (!x1) {
       return nullptr;
     }
-    RootedBigInt y1(cx, absoluteSubOne(cx, y, y->digitLength()));
+    RootedBigInt y1(cx, absoluteSubOne(cx, y));
     if (!y1) {
       return nullptr;
     }
     RootedBigInt result(cx, absoluteOr(cx, x1, y1));
     if (!result) {
       return nullptr;
     }
     bool resultNegative = true;
     return absoluteAddOne(cx, result, resultNegative);
   }
 
   MOZ_ASSERT(x->isNegative() != y->isNegative());
   HandleBigInt& pos = x->isNegative() ? y : x;
   HandleBigInt& neg = x->isNegative() ? x : y;
 
-  RootedBigInt neg1(cx, absoluteSubOne(cx, neg, neg->digitLength()));
+  RootedBigInt neg1(cx, absoluteSubOne(cx, neg));
   if (!neg1) {
     return nullptr;
   }
 
   // x & (-y) == x & ~(y-1) == x & ~(y-1)
   return absoluteAndNot(cx, pos, neg1);
 }
 
@@ -2106,37 +2118,34 @@ BigInt* BigInt::bitXor(JSContext* cx, Ha
     return x;
   }
 
   if (!x->isNegative() && !y->isNegative()) {
     return absoluteXor(cx, x, y);
   }
 
   if (x->isNegative() && y->isNegative()) {
-    int resultLength = std::max(x->digitLength(), y->digitLength());
-
     // (-x) ^ (-y) == ~(x-1) ^ ~(y-1) == (x-1) ^ (y-1)
-    RootedBigInt x1(cx, absoluteSubOne(cx, x, resultLength));
+    RootedBigInt x1(cx, absoluteSubOne(cx, x));
     if (!x1) {
       return nullptr;
     }
-    RootedBigInt y1(cx, absoluteSubOne(cx, y, y->digitLength()));
+    RootedBigInt y1(cx, absoluteSubOne(cx, y));
     if (!y1) {
       return nullptr;
     }
     return absoluteXor(cx, x1, y1);
   }
   MOZ_ASSERT(x->isNegative() != y->isNegative());
-  int resultLength = std::max(x->digitLength(), y->digitLength()) + 1;
 
   HandleBigInt& pos = x->isNegative() ? y : x;
   HandleBigInt& neg = x->isNegative() ? x : y;
 
   // x ^ (-y) == x ^ ~(y-1) == ~(x ^ (y-1)) == -((x ^ (y-1)) + 1)
-  RootedBigInt result(cx, absoluteSubOne(cx, neg, resultLength));
+  RootedBigInt result(cx, absoluteSubOne(cx, neg));
   if (!result) {
     return nullptr;
   }
   result = absoluteXor(cx, result, pos);
   if (!result) {
     return nullptr;
   }
   bool resultNegative = true;
@@ -2148,62 +2157,61 @@ BigInt* BigInt::bitOr(JSContext* cx, Han
   if (x->isZero()) {
     return y;
   }
 
   if (y->isZero()) {
     return x;
   }
 
-  unsigned resultLength = std::max(x->digitLength(), y->digitLength());
   bool resultNegative = x->isNegative() || y->isNegative();
 
   if (!resultNegative) {
     return absoluteOr(cx, x, y);
   }
 
   if (x->isNegative() && y->isNegative()) {
     // (-x) | (-y) == ~(x-1) | ~(y-1) == ~((x-1) & (y-1))
     // == -(((x-1) & (y-1)) + 1)
-    RootedBigInt result(cx, absoluteSubOne(cx, x, resultLength));
+    RootedBigInt result(cx, absoluteSubOne(cx, x));
     if (!result) {
       return nullptr;
     }
-    RootedBigInt y1(cx, absoluteSubOne(cx, y, y->digitLength()));
+    RootedBigInt y1(cx, absoluteSubOne(cx, y));
     if (!y1) {
       return nullptr;
     }
     result = absoluteAnd(cx, result, y1);
     if (!result) {
       return nullptr;
     }
     return absoluteAddOne(cx, result, resultNegative);
   }
 
   MOZ_ASSERT(x->isNegative() != y->isNegative());
   HandleBigInt& pos = x->isNegative() ? y : x;
   HandleBigInt& neg = x->isNegative() ? x : y;
 
   // x | (-y) == x | ~(y-1) == ~((y-1) &~ x) == -(((y-1) &~ x) + 1)
-  RootedBigInt result(cx, absoluteSubOne(cx, neg, resultLength));
+  RootedBigInt result(cx, absoluteSubOne(cx, neg));
   if (!result) {
     return nullptr;
   }
   result = absoluteAndNot(cx, result, pos);
   if (!result) {
     return nullptr;
   }
   return absoluteAddOne(cx, result, resultNegative);
 }
 
 // BigInt proposal section 1.1.2. BigInt::bitwiseNOT ( x )
 BigInt* BigInt::bitNot(JSContext* cx, HandleBigInt x) {
   if (x->isNegative()) {
     // ~(-x) == ~(~(x-1)) == x-1
-    return absoluteSubOne(cx, x, x->digitLength());
+    return absoluteSubOne(cx, x);
   } else {
     // ~x == -x-1 == -(x+1)
     bool resultNegative = true;
     return absoluteAddOne(cx, x, resultNegative);
   }
 }
 
 int64_t BigInt::toInt64(BigInt* x) { return WrapToSigned(toUint64(x)); }
@@ -2511,16 +2519,40 @@ bool BigInt::neg(JSContext* cx, HandleVa
   BigInt* resBigInt = BigInt::neg(cx, operandBigInt);
   if (!resBigInt) {
     return false;
   }
   res.setBigInt(resBigInt);
   return true;
 }
 
+bool BigInt::inc(JSContext* cx, HandleValue operand, MutableHandleValue res) {
+  MOZ_ASSERT(operand.isBigInt());
+
+  RootedBigInt operandBigInt(cx, operand.toBigInt());
+  BigInt* resBigInt = BigInt::inc(cx, operandBigInt);
+  if (!resBigInt) {
+    return false;
+  }
+  res.setBigInt(resBigInt);
+  return true;
+}
+
+bool BigInt::dec(JSContext* cx, HandleValue operand, MutableHandleValue res) {
+  MOZ_ASSERT(operand.isBigInt());
+
+  RootedBigInt operandBigInt(cx, operand.toBigInt());
+  BigInt* resBigInt = BigInt::dec(cx, operandBigInt);
+  if (!resBigInt) {
+    return false;
+  }
+  res.setBigInt(resBigInt);
+  return true;
+}
+
 bool BigInt::lsh(JSContext* cx, HandleValue lhs, HandleValue rhs,
                  MutableHandleValue res) {
   if (!ValidBigIntOperands(cx, lhs, rhs)) {
     return false;
   }
 
   RootedBigInt lhsBigInt(cx, lhs.toBigInt());
   RootedBigInt rhsBigInt(cx, rhs.toBigInt());
--- a/js/src/vm/BigIntType.h
+++ b/js/src/vm/BigIntType.h
@@ -90,28 +90,32 @@ class BigInt final : public js::gc::Tenu
   js::HashNumber hash();
   size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
   static BigInt* createUninitialized(JSContext* cx, size_t length,
                                      bool isNegative);
   static BigInt* createFromDouble(JSContext* cx, double d);
   static BigInt* createFromUint64(JSContext* cx, uint64_t n);
   static BigInt* createFromInt64(JSContext* cx, int64_t n);
+  static BigInt* createFromDigit(JSContext* cx, Digit d, bool isNegative);
   // FIXME: Cache these values.
   static BigInt* zero(JSContext* cx);
   static BigInt* one(JSContext* cx);
+  static BigInt* negativeOne(JSContext* cx);
 
   static BigInt* copy(JSContext* cx, Handle<BigInt*> x);
   static BigInt* add(JSContext* cx, Handle<BigInt*> x, Handle<BigInt*> y);
   static BigInt* sub(JSContext* cx, Handle<BigInt*> x, Handle<BigInt*> y);
   static BigInt* mul(JSContext* cx, Handle<BigInt*> x, Handle<BigInt*> y);
   static BigInt* div(JSContext* cx, Handle<BigInt*> x, Handle<BigInt*> y);
   static BigInt* mod(JSContext* cx, Handle<BigInt*> x, Handle<BigInt*> y);
   static BigInt* pow(JSContext* cx, Handle<BigInt*> x, Handle<BigInt*> y);
   static BigInt* neg(JSContext* cx, Handle<BigInt*> x);
+  static BigInt* inc(JSContext* cx, Handle<BigInt*> x);
+  static BigInt* dec(JSContext* cx, Handle<BigInt*> x);
   static BigInt* lsh(JSContext* cx, Handle<BigInt*> x, Handle<BigInt*> y);
   static BigInt* rsh(JSContext* cx, Handle<BigInt*> x, Handle<BigInt*> y);
   static BigInt* bitAnd(JSContext* cx, Handle<BigInt*> x, Handle<BigInt*> y);
   static BigInt* bitXor(JSContext* cx, Handle<BigInt*> x, Handle<BigInt*> y);
   static BigInt* bitOr(JSContext* cx, Handle<BigInt*> x, Handle<BigInt*> y);
   static BigInt* bitNot(JSContext* cx, Handle<BigInt*> x);
 
   static int64_t toInt64(BigInt* x);
@@ -133,16 +137,20 @@ class BigInt final : public js::gc::Tenu
   static bool div(JSContext* cx, Handle<Value> lhs, Handle<Value> rhs,
                   MutableHandle<Value> res);
   static bool mod(JSContext* cx, Handle<Value> lhs, Handle<Value> rhs,
                   MutableHandle<Value> res);
   static bool pow(JSContext* cx, Handle<Value> lhs, Handle<Value> rhs,
                   MutableHandle<Value> res);
   static bool neg(JSContext* cx, Handle<Value> operand,
                   MutableHandle<Value> res);
+  static bool inc(JSContext* cx, Handle<Value> operand,
+                  MutableHandle<Value> res);
+  static bool dec(JSContext* cx, Handle<Value> operand,
+                  MutableHandle<Value> res);
   static bool lsh(JSContext* cx, Handle<Value> lhs, Handle<Value> rhs,
                   MutableHandle<Value> res);
   static bool rsh(JSContext* cx, Handle<Value> lhs, Handle<Value> rhs,
                   MutableHandle<Value> res);
   static bool bitAnd(JSContext* cx, Handle<Value> lhs, Handle<Value> rhs,
                      MutableHandle<Value> res);
   static bool bitXor(JSContext* cx, Handle<Value> lhs, Handle<Value> rhs,
                      MutableHandle<Value> res);
@@ -277,17 +285,17 @@ class BigInt final : public js::gc::Tenu
 
   // Return `(|x| + 1) * (resultNegative ? -1 : +1)`.
   static BigInt* absoluteAddOne(JSContext* cx, Handle<BigInt*> x,
                                 bool resultNegative);
 
   // Return `(|x| - 1) * (resultNegative ? -1 : +1)`, with the precondition that
   // |x| != 0.
   static BigInt* absoluteSubOne(JSContext* cx, Handle<BigInt*> x,
-                                unsigned resultLength);
+                                bool resultNegative = false);
 
   // Return `a + b`, incrementing `*carry` if the addition overflows.
   static inline Digit digitAdd(Digit a, Digit b, Digit* carry) {
     Digit result = a + b;
     *carry += static_cast<Digit>(result < a);
     return result;
   }
 
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -396,41 +396,47 @@ static MOZ_ALWAYS_INLINE bool NegOperati
 
   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;
+  if (val.isNumber()) {
+    res.setNumber(val.toNumber() + 1);
+    return true;
+  }
+
+  MOZ_ASSERT(val.isBigInt(), "+1 only callable on result of JSOP_TONUMERIC");
+  return BigInt::inc(cx, val, res);
 }
 
 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;
+  if (val.isNumber()) {
+    res.setNumber(val.toNumber() - 1);
+    return true;
+  }
+
+  MOZ_ASSERT(val.isBigInt(), "-1 only callable on result of JSOP_TONUMERIC");
+  return BigInt::dec(cx, val, res);
 }
 
 static MOZ_ALWAYS_INLINE bool ToIdOperation(JSContext* cx, HandleValue idval,
                                             MutableHandleValue res) {
   if (idval.isInt32()) {
     res.set(idval);
     return true;
   }