From 7392901a9b0166358badea4eb7e0a14163e5208d Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 6 Sep 2023 11:18:05 -0500 Subject: [PATCH 1/8] Issue #2026 Follow-up: Fill in missing BigInt64 cases in js::IsTypedArrayConstructor(). Hopefully fixes RSA errors on ProtonMail. --- js/src/vm/TypedArrayObject.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 28e4090eb8..4c583f10f8 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -3245,6 +3245,10 @@ js::IsTypedArrayConstructor(HandleValue v, uint32_t type) return IsNativeFunction(v, Int32Array::class_constructor); case Scalar::Uint32: return IsNativeFunction(v, Uint32Array::class_constructor); + case Scalar::BigInt64: + return IsNativeFunction(v, BigInt64Array::class_constructor); + case Scalar::BigUint64: + return IsNativeFunction(v, BigUint64Array::class_constructor); case Scalar::Float32: return IsNativeFunction(v, Float32Array::class_constructor); case Scalar::Float64: From 470af6ba50152c415f10081880771f75d9e90b7d Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 7 Sep 2023 15:31:26 -0500 Subject: [PATCH 2/8] Issue #2282 - Performance observer safety checks. Both fixes from later revisions, without the Dispatch API changes. --- dom/performance/Performance.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dom/performance/Performance.cpp b/dom/performance/Performance.cpp index c903f3ec78..6803219985 100755 --- a/dom/performance/Performance.cpp +++ b/dom/performance/Performance.cpp @@ -675,7 +675,8 @@ public: NS_IMETHOD Run() override { MOZ_ASSERT(mPerformance); - mPerformance->NotifyObservers(); + RefPtr performance(mPerformance); + performance->NotifyObservers(); return NS_OK; } @@ -699,7 +700,12 @@ Performance::RunNotificationObserversTask() { mPendingNotificationObserversTask = true; nsCOMPtr task = new NotifyObserversTask(this); - nsresult rv = NS_DispatchToCurrentThread(task); + nsresult rv; + if (NS_IsMainThread()) { + rv = NS_DispatchToCurrentThread(task); + } else { + rv = NS_DispatchToMainThread(task); + } if (NS_WARN_IF(NS_FAILED(rv))) { mPendingNotificationObserversTask = false; } From e9202b0b0f3e9dbd13403b941631a1a25c28980a Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 11 Sep 2023 04:01:25 -0500 Subject: [PATCH 3/8] Issue #2308 - Fix JSON BigInt regressions. https://bugzilla.mozilla.org/show_bug.cgi?id=1522433 --- js/src/json.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/js/src/json.cpp b/js/src/json.cpp index d426fc721a..193fed1e0d 100644 --- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -277,10 +277,15 @@ PreprocessValue(JSContext* cx, HandleObject holder, KeyType key, MutableHandleVa RootedString keyStr(cx); - /* Step 2. */ - if (vp.isObject()) { + // Step 2. Modified by BigInt spec 6.1 to check for a toJSON method on the + // BigInt prototype when the value is a BigInt. + if (vp.isObject() || vp.isBigInt()) { RootedValue toJSON(cx); - RootedObject obj(cx, &vp.toObject()); + RootedObject obj(cx, JS::ToObject(cx, vp)); + if (!obj) { + return false; + } + if (!GetProperty(cx, obj, obj, cx->names().toJSON, &toJSON)) return false; From 5c8a6c5bb855509219732094565ba0e4f5cab165 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Sat, 16 Sep 2023 05:38:42 -0500 Subject: [PATCH 4/8] Issue #2308 & #1240 Follow-up - Introduce new increment and decrement operations. https://bugzilla.mozilla.org/show_bug.cgi?id=1508521 --- js/src/frontend/ElemOpEmitter.cpp | 7 +--- js/src/frontend/NameOpEmitter.cpp | 7 +--- js/src/frontend/PropOpEmitter.cpp | 7 +--- js/src/jit/BaselineCompiler.cpp | 12 ++++++ js/src/jit/BaselineCompiler.h | 8 ++-- js/src/jit/IonBuilder.cpp | 37 +++++++++++++++++++ js/src/jit/IonBuilder.h | 1 + js/src/jit/SharedIC.cpp | 20 ++++++++-- js/src/jit/SharedIC.h | 2 + .../jit/x86-shared/BaseAssembler-x86-shared.h | 8 ++-- js/src/vm/Interpreter-inl.h | 34 +++++++++++++++++ js/src/vm/Interpreter.cpp | 20 ++++++++++ js/src/vm/Opcodes.h | 24 ++++++++++-- 13 files changed, 157 insertions(+), 30 deletions(-) diff --git a/js/src/frontend/ElemOpEmitter.cpp b/js/src/frontend/ElemOpEmitter.cpp index f17b554e7a..dd6da9dcb4 100644 --- a/js/src/frontend/ElemOpEmitter.cpp +++ b/js/src/frontend/ElemOpEmitter.cpp @@ -233,7 +233,7 @@ ElemOpEmitter::emitIncDec() MOZ_ASSERT(state_ == State::Get); - JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB; + JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC; if (!bce_->emit1(JSOP_POS)) { // ... N return false; } @@ -242,10 +242,7 @@ ElemOpEmitter::emitIncDec() return false; } } - if (!bce_->emit1(JSOP_ONE)) { // ... N? N 1 - return false; - } - if (!bce_->emit1(binOp)) { // ... N? N+1 + if (!bce_->emit1(incOp)) { // ... N? N+1 return false; } if (isPostIncDec()) { diff --git a/js/src/frontend/NameOpEmitter.cpp b/js/src/frontend/NameOpEmitter.cpp index 8fa7f9d966..478cd44151 100644 --- a/js/src/frontend/NameOpEmitter.cpp +++ b/js/src/frontend/NameOpEmitter.cpp @@ -339,7 +339,7 @@ NameOpEmitter::emitIncDec() { MOZ_ASSERT(state_ == State::Start); - JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB; + JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC; if (!prepareForRhs()) { // ENV? V return false; } @@ -351,10 +351,7 @@ NameOpEmitter::emitIncDec() return false; } } - if (!bce_->emit1(JSOP_ONE)) { // ENV? N? N 1 - return false; - } - if (!bce_->emit1(binOp)) { // ENV? N? N+1 + if (!bce_->emit1(incOp)) { // ENV? N? N+1 return false; } if (isPostIncDec() && emittedBindOp()) { diff --git a/js/src/frontend/PropOpEmitter.cpp b/js/src/frontend/PropOpEmitter.cpp index bfbca00e1d..01e365f154 100644 --- a/js/src/frontend/PropOpEmitter.cpp +++ b/js/src/frontend/PropOpEmitter.cpp @@ -217,7 +217,7 @@ PropOpEmitter::emitIncDec(JSAtom* prop) MOZ_ASSERT(state_ == State::Get); - JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB; + JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC; if (!bce_->emit1(JSOP_POS)) { // ... N return false; @@ -227,10 +227,7 @@ PropOpEmitter::emitIncDec(JSAtom* prop) return false; } } - if (!bce_->emit1(JSOP_ONE)) { // ... N? N 1 - return false; - } - if (!bce_->emit1(binOp)) { // ... N? N+1 + if (!bce_->emit1(incOp)) { // ... N? N+1 return false; } if (isPostIncDec()) { diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 7dbd076c6b..7985a3b2a9 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1939,6 +1939,18 @@ BaselineCompiler::emit_JSOP_NEG() return emitUnaryArith(); } +bool +BaselineCompiler::emit_JSOP_INC() +{ + return emitUnaryArith(); +} + +bool +BaselineCompiler::emit_JSOP_DEC() +{ + return emitUnaryArith(); +} + bool BaselineCompiler::emit_JSOP_LT() { diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 8e9976b17f..9996c0f47f 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -242,8 +242,10 @@ namespace jit { _(JSOP_JUMPTARGET) \ _(JSOP_IS_CONSTRUCTING) \ _(JSOP_TRY_DESTRUCTURING_ITERCLOSE) \ - _(JSOP_IMPORTMETA) \ - _(JSOP_DYNAMIC_IMPORT) + _(JSOP_IMPORTMETA) \ + _(JSOP_DYNAMIC_IMPORT) \ + _(JSOP_INC) \ + _(JSOP_DEC) class BaselineCompiler : public BaselineCompilerSpecific { @@ -327,7 +329,7 @@ class BaselineCompiler : public BaselineCompilerSpecific 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. diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index a440bfa598..2134d1b613 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -719,6 +719,8 @@ IonBuilder::analyzeNewLoopTypes(MBasicBlock* entry, jsbytecode* start, jsbytecod case JSOP_DIV: case JSOP_MOD: case JSOP_NEG: + case JSOP_INC: + case JSOP_DEC: type = inspector->expectedResultType(last); break; case JSOP_BIGINT: @@ -1740,6 +1742,10 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_NEG: return jsop_neg(); + case JSOP_INC: + case JSOP_DEC: + return jsop_inc_or_dec(op); + case JSOP_TOSTRING: return jsop_tostring(); @@ -5070,6 +5076,14 @@ IonBuilder::arithTrySharedStub(bool* emitted, JSOp op, stub = MUnarySharedStub::New(alloc(), right); break; + case JSOP_INC: + MOZ_ASSERT(op == JSOP_ADD && right->toConstant()->toInt32() == 1); + stub = MUnarySharedStub::New(alloc(), left); + break; + case JSOP_DEC: + MOZ_ASSERT(op == JSOP_SUB && right->toConstant()->toInt32() == 1); + stub = MUnarySharedStub::New(alloc(), left); + break; case JSOP_ADD: case JSOP_SUB: case JSOP_MUL: @@ -5202,6 +5216,29 @@ IonBuilder::jsop_neg() return jsop_binary_arith(JSOP_MUL, negator, right); } +bool +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); +} + bool IonBuilder::jsop_tostring() { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 6f3fc027f6..162edb36f2 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -692,6 +692,7 @@ class IonBuilder MOZ_MUST_USE bool jsop_pow(); MOZ_MUST_USE bool jsop_pos(); MOZ_MUST_USE bool jsop_neg(); + MOZ_MUST_USE bool jsop_inc_or_dec(JSOp op); MOZ_MUST_USE bool jsop_tostring(); MOZ_MUST_USE bool jsop_setarg(uint32_t arg); MOZ_MUST_USE bool jsop_defvar(uint32_t index); diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp index 375edb1400..0afc7e12b5 100644 --- a/js/src/jit/SharedIC.cpp +++ b/js/src/jit/SharedIC.cpp @@ -1469,21 +1469,33 @@ DoUnaryArithFallback(JSContext* cx, void* payload, ICUnaryArith_Fallback* stub_, 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, script, pc, &valCopy, res)) + if (!NegOperation(cx, script, pc, &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"); } diff --git a/js/src/jit/SharedIC.h b/js/src/jit/SharedIC.h index d0038c9378..d259ebf0bc 100644 --- a/js/src/jit/SharedIC.h +++ b/js/src/jit/SharedIC.h @@ -1855,6 +1855,8 @@ class ICBinaryArith_DoubleWithInt32 : public ICStub // UnaryArith // JSOP_BITNOT // JSOP_NEG +// JSOP_INC +// JSOP_DEC class ICUnaryArith_Fallback : public ICFallbackStub { diff --git a/js/src/jit/x86-shared/BaseAssembler-x86-shared.h b/js/src/jit/x86-shared/BaseAssembler-x86-shared.h index 54b862a56c..d14ec89742 100644 --- a/js/src/jit/x86-shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/x86-shared/BaseAssembler-x86-shared.h @@ -4958,7 +4958,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_3A, imm, off void twoByteOp8(TwoByteOpcodeID opcode, RegisterID rm, RegisterID reg) { m_buffer.ensureSpace(MaxInstructionSize); - emitRexIf(byteRegRequiresRex(reg)|byteRegRequiresRex(rm), reg, 0, rm); + emitRexIf(byteRegRequiresRex(reg)||byteRegRequiresRex(rm), reg, 0, rm); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); registerModRM(rm, reg); @@ -4967,7 +4967,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_3A, imm, off void twoByteOp8(TwoByteOpcodeID opcode, int32_t offset, RegisterID base, RegisterID reg) { m_buffer.ensureSpace(MaxInstructionSize); - emitRexIf(byteRegRequiresRex(reg)|regRequiresRex(base), reg, 0, base); + emitRexIf(byteRegRequiresRex(reg)||regRequiresRex(base), reg, 0, base); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); memoryModRM(offset, base, reg); @@ -4977,7 +4977,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_3A, imm, off int scale, RegisterID reg) { m_buffer.ensureSpace(MaxInstructionSize); - emitRexIf(byteRegRequiresRex(reg)|regRequiresRex(base)|regRequiresRex(index), + emitRexIf(byteRegRequiresRex(reg)||regRequiresRex(base)||regRequiresRex(index), reg, index, base); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); @@ -4991,7 +4991,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_3A, imm, off void twoByteOp8_movx(TwoByteOpcodeID opcode, RegisterID rm, RegisterID reg) { m_buffer.ensureSpace(MaxInstructionSize); - emitRexIf(regRequiresRex(reg)|byteRegRequiresRex(rm), reg, 0, rm); + emitRexIf(regRequiresRex(reg)||byteRegRequiresRex(rm), reg, 0, rm); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); registerModRM(rm, reg); diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index a48c753f1d..93d2672b0f 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -416,6 +416,40 @@ NegOperation(JSContext* cx, HandleScript script, jsbytecode* pc, MutableHandleVa 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, HandleScript script, jsbytecode* pc, HandleValue idval, MutableHandleValue res) diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index a03fa847f7..7b04f5fb72 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -4176,6 +4176,26 @@ CASE(JSOP_IS_CONSTRUCTING) PUSH_MAGIC(JS_IS_CONSTRUCTING); END_CASE(JSOP_IS_CONSTRUCTING) +CASE(JSOP_INC) +{ + ReservedRooted 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 val(&rootValue0, REGS.sp[-1]); + MutableHandleValue res = REGS.stackHandleAt(-1); + if (!DecOperation(cx, &val, res)) { + goto error; + } +} +END_CASE(JSOP_DEC) + CASE(JSOP_BIGINT) { PUSH_COPY(script->getConst(GET_UINT32_INDEX(REGS.pc))); diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index ff707aac08..c86a22baac 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2357,7 +2357,25 @@ * Operands: * Stack: arg => rval */ \ - macro(JSOP_DYNAMIC_IMPORT, 234, "call-import", NULL, 1, 1, 1, JOF_BYTE) \ + macro(JSOP_DYNAMIC_IMPORT, 234, "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, 235, "inc", 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_DEC, 236, "dec", NULL, 1, 1, 1, JOF_BYTE) \ /* * Pushes a BigInt constant onto the stack. * Category: Literals @@ -2365,14 +2383,12 @@ * Operands: uint32_t constIndex * Stack: => val */ \ - macro(JSOP_BIGINT, 235, "bigint", NULL, 5, 0, 1, JOF_BIGINT) + macro(JSOP_BIGINT, 237, "bigint", NULL, 5, 0, 1, JOF_BIGINT) /* * 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) \ - macro(236) \ - macro(237) \ macro(238) \ macro(239) \ macro(240) \ From e12e377e514fbef30e731f7e5ad3a552325e9773 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Sun, 17 Sep 2023 04:23:57 -0500 Subject: [PATCH 5/8] Issue #2308 & #1240 Follow-up - Add BigInt support to JSOP_INC and JSOP_DEC. https://bugzilla.mozilla.org/show_bug.cgi?id=1526309 --- js/public/TrackedOptimizationInfo.h | 5 + js/src/jit/IonBuilder.cpp | 201 +++++++++++++++++++++------- js/src/jit/IonBuilder.h | 11 ++ js/src/jit/MIR.cpp | 17 --- js/src/jit/MIR.h | 1 - js/src/vm/BigIntType.cpp | 143 ++++++++++++-------- js/src/vm/BigIntType.h | 10 +- js/src/vm/Interpreter-inl.h | 22 +-- 8 files changed, 283 insertions(+), 127 deletions(-) diff --git a/js/public/TrackedOptimizationInfo.h b/js/public/TrackedOptimizationInfo.h index ce3508cd68..ebaf5915cd 100644 --- a/js/public/TrackedOptimizationInfo.h +++ b/js/public/TrackedOptimizationInfo.h @@ -57,6 +57,10 @@ namespace JS { _(BinaryArith_SharedCache) \ _(BinaryArith_Call) \ \ + _(UnaryArith_SpecializedTypes) \ + _(UnaryArith_SpecializedOnBaselineTypes) \ + _(UnaryArith_InlineCache) \ + \ _(InlineCache_OptimizedStub) \ \ _(Call_Inline) @@ -112,6 +116,7 @@ namespace JS { _(GetElemStringNotCached) \ _(NonNativeReceiver) \ _(IndexType) \ + _(SpeculationOnInputTypesFailed) \ _(SetElemNonDenseNonTANotCached) \ _(NoSimdJitSupport) \ _(SimdTypeNotOptimized) \ diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 2134d1b613..fdbdb55bd8 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -4858,7 +4858,7 @@ IonBuilder::jsop_bitop(JSOp op) } MDefinition::Opcode -JSOpToMDefinition(JSOp op) +BinaryJSOpToMDefinition(JSOp op) { switch (op) { case JSOP_ADD: @@ -4959,6 +4959,40 @@ IonBuilder::powTrySpecialized(bool* emitted, MDefinition* base, MDefinition* pow return true; } +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; +} + +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::Op_Add || op == MDefinition::Op_Mul) { + ins->setCommutative(); + } + + current->add(ins); + current->push(ins); + + MOZ_ASSERT(!ins->isEffectful()); + + if(!maybeInsertResume()) { + return nullptr; + } + + return ins; +} + static inline bool SimpleArithOperand(MDefinition* op) { @@ -4992,19 +5026,20 @@ IonBuilder::binaryArithTrySpecialized(bool* emitted, JSOp op, MDefinition* left, return true; } - MDefinition::Opcode defOp = JSOpToMDefinition(op); - MBinaryArithInstruction* ins = MBinaryArithInstruction::New(alloc(), defOp, left, right); - ins->setNumberSpecialization(alloc(), inspector, pc); + MDefinition::Opcode defOp = BinaryJSOpToMDefinition(op); + MIRType specialization = binaryArithNumberSpecialization(left, right); + MBinaryArithInstruction* ins = binaryArithEmitSpecialized(defOp, specialization, left, right); - if (op == JSOP_ADD || op == JSOP_MUL) - ins->setCommutative(); - - current->add(ins); - current->push(ins); - - MOZ_ASSERT(!ins->isEffectful()); - if (!maybeInsertResume()) + if(!ins) { return false; + } + + // 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; @@ -5028,16 +5063,10 @@ IonBuilder::binaryArithTrySpecializedOnBaselineInspector(bool* emitted, JSOp op, return true; } - 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()); - if (!maybeInsertResume()) + MDefinition::Opcode defOp = BinaryJSOpToMDefinition(op); + if(!binaryArithEmitSpecialized(defOp, specialization, left, right)) { return false; + } trackOptimizationSuccess(); *emitted = true; @@ -5076,14 +5105,6 @@ IonBuilder::arithTrySharedStub(bool* emitted, JSOp op, stub = MUnarySharedStub::New(alloc(), right); break; - case JSOP_INC: - MOZ_ASSERT(op == JSOP_ADD && right->toConstant()->toInt32() == 1); - stub = MUnarySharedStub::New(alloc(), left); - break; - case JSOP_DEC: - MOZ_ASSERT(op == JSOP_SUB && right->toConstant()->toInt32() == 1); - stub = MUnarySharedStub::New(alloc(), left); - break; case JSOP_ADD: case JSOP_SUB: case JSOP_MUL: @@ -5138,8 +5159,8 @@ IonBuilder::jsop_binary_arith(JSOp op, MDefinition* left, MDefinition* right) trackOptimizationAttempt(TrackedStrategy::BinaryArith_Call); trackOptimizationSuccess(); - MDefinition::Opcode def_op = JSOpToMDefinition(op); - MBinaryArithInstruction* ins = MBinaryArithInstruction::New(alloc(), def_op, left, right); + MDefinition::Opcode defOp = BinaryJSOpToMDefinition(op); + MBinaryArithInstruction* ins = MBinaryArithInstruction::New(alloc(), defOp, left, right); // Decrease type from 'any type' to 'empty type' when one of the operands // is 'empty typed'. @@ -5216,27 +5237,117 @@ IonBuilder::jsop_neg() return jsop_binary_arith(JSOP_MUL, negator, right); } +MDefinition* +IonBuilder::unaryArithConvertToBinary(JSOp op, MDefinition::Opcode* defOp) +{ + switch (op) { + case JSOP_INC: { + *defOp = MDefinition::Op_Add; + MConstant* right = MConstant::New(alloc(), Int32Value(1)); + current->add(right); + return right; + } + case JSOP_DEC: { + *defOp = MDefinition::Op_Sub; + MConstant* right = MConstant::New(alloc(), Int32Value(1)); + current->add(right); + return right; + } + default: + MOZ_CRASH("unexpected unary opcode"); + } +} + +bool +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 true; + } + + MDefinition::Opcode defOp; + MDefinition* rhs = unaryArithConvertToBinary(op, &defOp); + MIRType specialization = binaryArithNumberSpecialization(value, rhs); + if (!binaryArithEmitSpecialized(defOp, specialization, value, rhs)) { + return false; + } + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + +bool +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 true; + } + + MDefinition::Opcode defOp; + MDefinition* rhs = unaryArithConvertToBinary(op, &defOp); + if (!binaryArithEmitSpecialized(defOp, specialization, value, rhs)) { + return false; + } + + trackOptimizationSuccess(); + *emitted = true; + return true; +} + bool IonBuilder::jsop_inc_or_dec(JSOp op) { - // As above, pass constant without slot traffic. - MConstant* one = MConstant::New(alloc(), Int32Value(1)); - current->add(one); - + bool emitted = false; 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"); + startTrackingOptimizations(); + + trackTypeInfo(TrackedTypeSite::Operand, value->type(), value->resultTypeSet()); + + if (!unaryArithTrySpecialized(&emitted, op, value)) { + return false; + } + if (emitted) { + return true; } - return jsop_binary_arith(op, value, one); + if (!unaryArithTrySpecializedOnBaselineInspector(&emitted, op, value)) { + return false; + } + if (emitted) { + return true; + } + + trackOptimizationAttempt(TrackedStrategy::UnaryArith_InlineCache); + trackOptimizationSuccess(); + + MInstruction* stub = MUnarySharedStub::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); } bool diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 162edb36f2..f12a24bcfc 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -497,8 +497,13 @@ class IonBuilder // jsop_binary_arith helpers. MBinaryArithInstruction* binaryArithInstruction(JSOp op, MDefinition* left, MDefinition* right); + MIRType binaryArithNumberSpecialization(MDefinition* left, MDefinition* right); MOZ_MUST_USE bool binaryArithTryConcat(bool* emitted, JSOp op, MDefinition* left, MDefinition* right); + MOZ_MUST_USE MBinaryArithInstruction* binaryArithEmitSpecialized(MDefinition::Opcode op, + MIRType specialization, + MDefinition* left, + MDefinition* right); MOZ_MUST_USE bool binaryArithTrySpecialized(bool* emitted, JSOp op, MDefinition* left, MDefinition* right); MOZ_MUST_USE bool binaryArithTrySpecializedOnBaselineInspector(bool* emitted, JSOp op, @@ -510,6 +515,12 @@ class IonBuilder // jsop_bitnot helpers. MOZ_MUST_USE bool bitnotTrySpecialized(bool* emitted, MDefinition* input); + // jsop_inc_or_dec helpers. + MDefinition* unaryArithConvertToBinary(JSOp op, MDefinition::Opcode* defOp); + MOZ_MUST_USE bool unaryArithTrySpecialized(bool* emitted, JSOp op, MDefinition* value); + MOZ_MUST_USE bool unaryArithTrySpecializedOnBaselineInspector(bool* emitted, JSOp op, + MDefinition* value); + // jsop_pow helpers. MOZ_MUST_USE bool powTrySpecialized(bool* emitted, MDefinition* base, MDefinition* power, MIRType outputType); diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 2264bed4f2..05e37cdc6a 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -3110,23 +3110,6 @@ MBinaryArithInstruction::New(TempAllocator& alloc, Opcode op, } } -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) { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 72c5214845..327310122c 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -6219,7 +6219,6 @@ class MBinaryArithInstruction specialization_ = MIRType::Int32; setResultType(MIRType::Int32); } - void setNumberSpecialization(TempAllocator& alloc, BaselineInspector* inspector, jsbytecode* pc); virtual void trySpecializeFloat32(TempAllocator& alloc) override; diff --git a/js/src/vm/BigIntType.cpp b/js/src/vm/BigIntType.cpp index 8382c641fa..1f8b061e02 100644 --- a/js/src/vm/BigIntType.cpp +++ b/js/src/vm/BigIntType.cpp @@ -185,16 +185,21 @@ BigInt* BigInt::zero(ExclusiveContext* cx) { return createUninitialized(cx, 0, false); } -BigInt* BigInt::one(ExclusiveContext* cx) { - BigInt* ret = createUninitialized(cx, 1, false); - - if (!ret) { +BigInt* BigInt::createFromDigit(ExclusiveContext* cx, Digit d, bool isNegative) { + MOZ_ASSERT(d != 0); + BigInt* res = createUninitialized(cx, 1, isNegative); + if (!res) { return nullptr; } - ret->setDigit(0, 1); + res->setDigit(0, d); + return res; +} - return ret; +BigInt* BigInt::one(ExclusiveContext* cx) { return createFromDigit(cx, 1, false); } + +BigInt* BigInt::negativeOne(ExclusiveContext* cx) { + return createFromDigit(cx, 1, true); } BigInt* BigInt::neg(ExclusiveContext* cx, HandleBigInt x) { @@ -977,21 +982,26 @@ BigInt* BigInt::absoluteAddOne(ExclusiveContext* cx, HandleBigInt x, 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(ExclusiveContext* 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; @@ -999,13 +1009,36 @@ BigInt* BigInt::absoluteSubOne(ExclusiveContext* cx, HandleBigInt x, borrow = newBorrow; } MOZ_ASSERT(!borrow); - for (unsigned i = length; i < resultLength; i++) { - result->setDigit(i, 0); - } return destructivelyTrimHighZeroDigits(cx, result); } +BigInt* BigInt::inc(ExclusiveContext* 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(ExclusiveContext* 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: @@ -1569,13 +1602,7 @@ BigInt* BigInt::createFromUint64(ExclusiveContext* cx, uint64_t n) { 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(ExclusiveContext* cx, int64_t n) { @@ -1770,12 +1797,7 @@ BigInt* BigInt::mod(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { 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), @@ -1930,15 +1952,7 @@ BigInt* BigInt::lshByAbsolute(ExclusiveContext* cx, HandleBigInt x, HandleBigInt } BigInt* BigInt::rshByMaximum(ExclusiveContext* 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(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { @@ -2049,14 +2063,13 @@ BigInt* BigInt::bitAnd(ExclusiveContext* cx, HandleBigInt x, HandleBigInt 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; } @@ -2072,7 +2085,7 @@ BigInt* BigInt::bitAnd(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { 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; } @@ -2096,27 +2109,24 @@ BigInt* BigInt::bitXor(ExclusiveContext* cx, HandleBigInt x, HandleBigInt 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; } @@ -2138,7 +2148,6 @@ BigInt* BigInt::bitOr(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { return x; } - unsigned resultLength = std::max(x->digitLength(), y->digitLength()); bool resultNegative = x->isNegative() || y->isNegative(); if (!resultNegative) { @@ -2148,11 +2157,11 @@ BigInt* BigInt::bitOr(ExclusiveContext* cx, HandleBigInt x, HandleBigInt 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; } @@ -2168,7 +2177,7 @@ BigInt* BigInt::bitOr(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { 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; } @@ -2183,7 +2192,7 @@ BigInt* BigInt::bitOr(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { BigInt* BigInt::bitNot(ExclusiveContext* 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; @@ -2535,6 +2544,30 @@ bool BigInt::neg(ExclusiveContext* cx, HandleValue operand, MutableHandleValue r return true; } +bool BigInt::inc(ExclusiveContext* 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(ExclusiveContext* 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(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) { if (!ValidBigIntOperands(cx, lhs, rhs)) { diff --git a/js/src/vm/BigIntType.h b/js/src/vm/BigIntType.h index ea0317fd9c..693f8bf36c 100644 --- a/js/src/vm/BigIntType.h +++ b/js/src/vm/BigIntType.h @@ -97,9 +97,11 @@ class BigInt final : public js::gc::TenuredCell { static BigInt* createFromDouble(js::ExclusiveContext* cx, double d); static BigInt* createFromUint64(js::ExclusiveContext* cx, uint64_t n); static BigInt* createFromInt64(js::ExclusiveContext* cx, int64_t n); + static BigInt* createFromDigit(js::ExclusiveContext* cx, Digit d, bool isNegative); // FIXME: Cache these values. static BigInt* zero(js::ExclusiveContext* cx); static BigInt* one(js::ExclusiveContext* cx); + static BigInt* negativeOne(js::ExclusiveContext* cx); static BigInt* copy(js::ExclusiveContext* cx, Handle x); static BigInt* add(js::ExclusiveContext* cx, Handle x, Handle y); @@ -109,6 +111,8 @@ class BigInt final : public js::gc::TenuredCell { static BigInt* mod(js::ExclusiveContext* cx, Handle x, Handle y); static BigInt* pow(js::ExclusiveContext* cx, Handle x, Handle y); static BigInt* neg(js::ExclusiveContext* cx, Handle x); + static BigInt* inc(js::ExclusiveContext* cx, Handle x); + static BigInt* dec(js::ExclusiveContext* cx, Handle x); static BigInt* lsh(js::ExclusiveContext* cx, Handle x, Handle y); static BigInt* rsh(js::ExclusiveContext* cx, Handle x, Handle y); static BigInt* bitAnd(js::ExclusiveContext* cx, Handle x, Handle y); @@ -145,6 +149,10 @@ class BigInt final : public js::gc::TenuredCell { MutableHandle res); static bool neg(js::ExclusiveContext* cx, Handle operand, MutableHandle res); + static bool inc(js::ExclusiveContext* cx, Handle operand, + MutableHandle res); + static bool dec(js::ExclusiveContext* cx, Handle operand, + MutableHandle res); static bool lsh(js::ExclusiveContext* cx, Handle lhs, Handle rhs, MutableHandle res); static bool rsh(js::ExclusiveContext* cx, Handle lhs, Handle rhs, @@ -288,7 +296,7 @@ class BigInt final : public js::gc::TenuredCell { // Return `(|x| - 1) * (resultNegative ? -1 : +1)`, with the precondition that // |x| != 0. static BigInt* absoluteSubOne(js::ExclusiveContext* cx, Handle 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) { diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 93d2672b0f..cbf3113b50 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -421,16 +421,19 @@ 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 @@ -438,16 +441,19 @@ 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 From 2b637c564f77f7885901f1c4906a7183ceac753a Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Sun, 17 Sep 2023 06:21:49 -0500 Subject: [PATCH 6/8] Issue #2308 & #1240 Follow-up - Fill in missing JSOP_INC/DEC cases for Ion on Intel32/64. --- js/src/jit/CodeGenerator.cpp | 2 ++ js/src/jit/x64/SharedIC-x64.cpp | 10 ++++++++++ js/src/jit/x86/SharedIC-x86.cpp | 11 +++++++++++ 3 files changed, 23 insertions(+) diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 0459592448..5c2d9ac4e7 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -2378,6 +2378,8 @@ CodeGenerator::visitUnarySharedStub(LUnarySharedStub* lir) switch (jsop) { case JSOP_BITNOT: case JSOP_NEG: + case JSOP_INC: + case JSOP_DEC: emitSharedStub(ICStub::Kind::UnaryArith_Fallback, lir); break; case JSOP_CALLPROP: diff --git a/js/src/jit/x64/SharedIC-x64.cpp b/js/src/jit/x64/SharedIC-x64.cpp index 9e38cef6bf..ccad231d90 100644 --- a/js/src/jit/x64/SharedIC-x64.cpp +++ b/js/src/jit/x64/SharedIC-x64.cpp @@ -216,6 +216,16 @@ ICUnaryArith_Int32::Compiler::generateStubCode(MacroAssembler& masm) masm.branchTest32(Assembler::Zero, R0.valueReg(), Imm32(0x7fffffff), &failure); masm.negl(R0.valueReg()); break; + case JSOP_INC: { + RegisterOrInt32Constant rval = RegisterOrInt32Constant(R0.valueReg()); + masm.inc32(&rval); + break; + } + case JSOP_DEC: { + RegisterOrInt32Constant rval = RegisterOrInt32Constant(R0.valueReg()); + masm.dec32(&rval); + break; + } default: MOZ_CRASH("Unexpected op"); } diff --git a/js/src/jit/x86/SharedIC-x86.cpp b/js/src/jit/x86/SharedIC-x86.cpp index ee00e1bf0d..b293106ce3 100644 --- a/js/src/jit/x86/SharedIC-x86.cpp +++ b/js/src/jit/x86/SharedIC-x86.cpp @@ -226,6 +226,17 @@ ICUnaryArith_Int32::Compiler::generateStubCode(MacroAssembler& masm) masm.branchTest32(Assembler::Zero, R0.payloadReg(), Imm32(0x7fffffff), &failure); masm.negl(R0.payloadReg()); break; + case JSOP_INC: { + RegisterOrInt32Constant rval = RegisterOrInt32Constant(R0.payloadReg()); + masm.inc32(&rval); + break; + } + case JSOP_DEC: { + RegisterOrInt32Constant rval = RegisterOrInt32Constant(R0.payloadReg()); + masm.dec32(&rval); + break; + } + default: MOZ_CRASH("Unexpected op"); } From 209d714434f34a311e934893ae47a9ffcdbebc52 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Sun, 17 Sep 2023 12:39:03 -0500 Subject: [PATCH 7/8] Issue #2308 & #1240 Follow-up - Replace JSOP_POS in ++/-- with JSOP_TONUMERIC. https://bugzilla.mozilla.org/show_bug.cgi?id=1519135 --- js/src/frontend/BytecodeEmitter.cpp | 2 +- js/src/frontend/ElemOpEmitter.cpp | 2 +- js/src/frontend/NameOpEmitter.cpp | 2 +- js/src/frontend/PropOpEmitter.cpp | 2 +- js/src/jit/BaselineCompiler.cpp | 31 +++++++++++++++++--- js/src/jit/BaselineCompiler.h | 1 + js/src/jit/BaselineIC.cpp | 36 ----------------------- js/src/jit/BaselineIC.h | 26 ----------------- js/src/jit/BaselineICList.h | 2 -- js/src/jit/BaselineInspector.cpp | 5 ++++ js/src/jit/CodeGenerator.cpp | 42 +++++++++++++++++++++++++++ js/src/jit/CodeGenerator.h | 1 + js/src/jit/IonBuilder.cpp | 44 ++++++++++++++++++++++++++++- js/src/jit/IonBuilder.h | 1 + js/src/jit/IonTypes.h | 4 +++ js/src/jit/Lowering.cpp | 9 ++++++ js/src/jit/Lowering.h | 1 + js/src/jit/MIR.cpp | 17 +++++++++++ js/src/jit/MIR.h | 37 ++++++++++++++++++++++++ js/src/jit/MOpcodes.h | 1 + js/src/jit/RangeAnalysis.cpp | 6 ++++ js/src/jit/TypePolicy.cpp | 2 +- js/src/jit/VMFunctions.cpp | 14 +++++++++ js/src/jit/VMFunctions.h | 3 ++ js/src/jit/shared/LIR-shared.h | 15 ++++++++++ js/src/jit/shared/LOpcodes-shared.h | 1 + js/src/vm/Interpreter.cpp | 8 ++++++ js/src/vm/Opcodes.h | 13 +++++++-- 28 files changed, 251 insertions(+), 77 deletions(-) diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 462edfd91e..7680411f07 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2057,7 +2057,7 @@ BytecodeEmitter::emitCallIncDec(UnaryNode* incDec) MOZ_ASSERT(call->isKind(PNK_CALL)); if (!emitTree(call)) // CALLRESULT return false; - if (!emit1(JSOP_POS)) // N + if (!emit1(JSOP_TONUMERIC)) // N return false; // The increment/decrement has no side effects, so proceed to throw for diff --git a/js/src/frontend/ElemOpEmitter.cpp b/js/src/frontend/ElemOpEmitter.cpp index dd6da9dcb4..39d0eca21c 100644 --- a/js/src/frontend/ElemOpEmitter.cpp +++ b/js/src/frontend/ElemOpEmitter.cpp @@ -234,7 +234,7 @@ ElemOpEmitter::emitIncDec() MOZ_ASSERT(state_ == State::Get); JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC; - if (!bce_->emit1(JSOP_POS)) { // ... N + if (!bce_->emit1(JSOP_TONUMERIC)) { // ... N return false; } if (isPostIncDec()) { diff --git a/js/src/frontend/NameOpEmitter.cpp b/js/src/frontend/NameOpEmitter.cpp index 478cd44151..48caac8de3 100644 --- a/js/src/frontend/NameOpEmitter.cpp +++ b/js/src/frontend/NameOpEmitter.cpp @@ -343,7 +343,7 @@ NameOpEmitter::emitIncDec() if (!prepareForRhs()) { // ENV? V return false; } - if (!bce_->emit1(JSOP_POS)) { // ENV? N + if (!bce_->emit1(JSOP_TONUMERIC)) { // ENV? N return false; } if (isPostIncDec()) { diff --git a/js/src/frontend/PropOpEmitter.cpp b/js/src/frontend/PropOpEmitter.cpp index 01e365f154..278bc0e0fd 100644 --- a/js/src/frontend/PropOpEmitter.cpp +++ b/js/src/frontend/PropOpEmitter.cpp @@ -219,7 +219,7 @@ PropOpEmitter::emitIncDec(JSAtom* prop) JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC; - if (!bce_->emit1(JSOP_POS)) { // ... N + if (!bce_->emit1(JSOP_TONUMERIC)) { // ... N return false; } if (isPostIncDec()) { diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 7985a3b2a9..ab38011be2 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1322,14 +1322,37 @@ BaselineCompiler::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. - ICToNumber_Fallback::Compiler stubCompiler(cx); - if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) + prepareVMCall(); + pushArg(R0); + if (!callVM(ToNumberInfo)) { + return false; + } + + masm.bind(&done); + frame.push(R0); + return true; +} + + +bool +BaselineCompiler::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); diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 9996c0f47f..30da13d9c1 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -50,6 +50,7 @@ namespace jit { _(JSOP_OR) \ _(JSOP_NOT) \ _(JSOP_POS) \ + _(JSOP_TONUMERIC) \ _(JSOP_LOOPHEAD) \ _(JSOP_LOOPENTRY) \ _(JSOP_VOID) \ diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 600b56d096..e678a88658 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -694,42 +694,6 @@ ICToBool_Object::Compiler::generateStubCode(MacroAssembler& masm) return true; } -// -// ToNumber_Fallback -// - -static bool -DoToNumberFallback(JSContext* cx, ICToNumber_Fallback* stub, HandleValue arg, MutableHandleValue ret) -{ - FallbackICSpew(cx, stub, "ToNumber"); - ret.set(arg); - return ToNumber(cx, ret); -} - -typedef bool (*DoToNumberFallbackFn)(JSContext*, ICToNumber_Fallback*, HandleValue, MutableHandleValue); -static const VMFunction DoToNumberFallbackInfo = - FunctionInfo(DoToNumberFallback, "DoToNumberFallback", TailCall, - PopValues(1)); - -bool -ICToNumber_Fallback::Compiler::generateStubCode(MacroAssembler& masm) -{ - MOZ_ASSERT(engine_ == Engine::Baseline); - 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); -} - // // GetElem_Fallback // diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h index 895c8af6b4..bd30ec0369 100644 --- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -333,32 +333,6 @@ class ICToBool_Object : public ICStub }; }; -// 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); - - public: - explicit Compiler(JSContext* cx) - : ICStubCompiler(cx, ICStub::ToNumber_Fallback, Engine::Baseline) {} - - ICStub* getStub(ICStubSpace* space) { - return newStub(space, getStubCode()); - } - }; -}; - // GetElem // JSOP_GETELEM diff --git a/js/src/jit/BaselineICList.h b/js/src/jit/BaselineICList.h index 09b0db82ad..08e61a1872 100644 --- a/js/src/jit/BaselineICList.h +++ b/js/src/jit/BaselineICList.h @@ -34,8 +34,6 @@ namespace jit { _(ToBool_Double) \ _(ToBool_Object) \ \ - _(ToNumber_Fallback) \ - \ _(Call_Fallback) \ _(Call_Scripted) \ _(Call_AnyScripted) \ diff --git a/js/src/jit/BaselineInspector.cpp b/js/src/jit/BaselineInspector.cpp index d3b0fb71b5..cfb3c8b219 100644 --- a/js/src/jit/BaselineInspector.cpp +++ b/js/src/jit/BaselineInspector.cpp @@ -408,6 +408,11 @@ BaselineInspector::expectedBinaryArithSpecialization(jsbytecode* pc) 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); ICStub* stub = entry.fallbackStub(); if (stub->isBinaryArith_Fallback() && diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 5c2d9ac4e7..f2c3076f99 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -3415,6 +3415,48 @@ CodeGenerator::visitLoadUnboxedExpando(LLoadUnboxedExpando* lir) 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; + bool maybeBigInt = lir->mir()->mightBeType(MIRType::BigInt); + int checks = int(maybeNumber) + int(maybeBigInt); + + 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); + } + if (maybeBigInt) { + checks--; + Condition cond = checks ? Equal : NotEqual; + Label* target = checks ? &done : ool->entry(); + masm.branchTestBigInt(cond, operand, target); + } + + MOZ_ASSERT(checks == 0); + masm.bind(&done); + masm.moveValue(operand, output); + } + + masm.bind(ool->rejoin()); +} + void CodeGenerator::visitTypeBarrierV(LTypeBarrierV* lir) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 64fe9378b8..ff68eebd19 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -149,6 +149,7 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitGuardReceiverPolymorphic(LGuardReceiverPolymorphic* lir); void visitGuardUnboxedExpando(LGuardUnboxedExpando* lir); void visitLoadUnboxedExpando(LLoadUnboxedExpando* lir); + void visitToNumeric(LToNumeric* lir); void visitTypeBarrierV(LTypeBarrierV* lir); void visitTypeBarrierO(LTypeBarrierO* lir); void visitMonitorTypes(LMonitorTypes* lir); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index fdbdb55bd8..1fec408a7e 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -636,7 +636,7 @@ IonBuilder::analyzeNewLoopTypes(MBasicBlock* entry, jsbytecode* start, jsbytecod 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) { @@ -1586,6 +1586,7 @@ IonBuilder::traverseBytecode() break; case JSOP_POS: + case JSOP_TONUMERIC: case JSOP_TOID: case JSOP_TOSTRING: // These ops may leave their input on the stack without setting @@ -1739,6 +1740,9 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_POS: return jsop_pos(); + case JSOP_TONUMERIC: + return jsop_tonumeric(); + case JSOP_NEG: return jsop_neg(); @@ -5312,6 +5316,44 @@ IonBuilder::unaryArithTrySpecializedOnBaselineInspector(bool* emitted, JSOp op, return true; } +bool +IonBuilder::jsop_tonumeric() +{ + MDefinition* peeked = current->peek(-1); + + if (IsNumericType(peeked->type())) { + // Elide the ToNumeric as we already unboxed the value. + peeked->setImplicitlyUsedUnchecked(); + return true; + } + + LifoAlloc* lifoAlloc = alloc().lifoAlloc(); + TemporaryTypeSet* types = lifoAlloc->new_(); + if (!types) { + return false; + } + + types->addType(TypeSet::Int32Type(), lifoAlloc); + types->addType(TypeSet::DoubleType(), lifoAlloc); + types->addType(TypeSet::BigIntType(), lifoAlloc); + + 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 true; + } + + // 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); +} + bool IonBuilder::jsop_inc_or_dec(JSOp op) { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index f12a24bcfc..9051325eca 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -703,6 +703,7 @@ class IonBuilder MOZ_MUST_USE bool jsop_pow(); MOZ_MUST_USE bool jsop_pos(); MOZ_MUST_USE bool jsop_neg(); + MOZ_MUST_USE bool jsop_tonumeric(); MOZ_MUST_USE bool jsop_inc_or_dec(JSOp op); MOZ_MUST_USE bool jsop_tostring(); MOZ_MUST_USE bool jsop_setarg(uint32_t arg); diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h index 50b09cc30e..af712a3709 100644 --- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -644,6 +644,10 @@ IsNumberType(MIRType type) type == MIRType::Int64; } +static inline bool IsNumericType(MIRType type) { + return IsNumberType(type) || type == MIRType::BigInt; +} + static inline bool IsTypeRepresentableAsDouble(MIRType type) { diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index d315c618e7..61c0c8350f 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2165,6 +2165,15 @@ LIRGenerator::visitToInt32(MToInt32* convert) } } +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) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index d0e00fb82f..e1fc611684 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -336,6 +336,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitDebugCheckSelfHosted(MDebugCheckSelfHosted* ins); void visitModuleMetadata(MModuleMetadata* ins); void visitDynamicImport(MDynamicImport* ins); + void visitToNumeric(MToNumeric* ins); }; } // namespace jit diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 05e37cdc6a..a68e344011 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -4172,6 +4172,23 @@ 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* MToInt32::foldsTo(TempAllocator& alloc) { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 327310122c..5c854f6060 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -5534,6 +5534,43 @@ class MInt64ToFloatingPoint } }; +// 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(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) + TRIVIAL_NEW_WRAPPERS + + 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) +}; + // Converts a primitive (either typed or untyped) to an int32. 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. diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 54c65aff90..b580a3b7dd 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -123,6 +123,7 @@ namespace jit { _(ToDouble) \ _(ToFloat32) \ _(ToInt32) \ + _(ToNumeric) \ _(TruncateToInt32) \ _(WrapInt64ToInt32) \ _(ExtendInt32ToInt64) \ diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index 52c737e677..71c58cb421 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -1741,6 +1741,12 @@ MTruncateToInt32::computeRange(TempAllocator& alloc) setRange(output); } +void +MToNumeric::computeRange(TempAllocator& alloc) +{ + setRange(new (alloc) Range(getOperand(0))); +} + void MToInt32::computeRange(TempAllocator& alloc) { diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index 1222cdd2b2..567c1d0125 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -1186,6 +1186,7 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) // Lists of all TypePolicy specializations which are used by MIR Instructions. #define TYPE_POLICY_LIST(_) \ + _(AllDoublePolicy) \ _(ArithPolicy) \ _(BitwisePolicy) \ _(BoxInputsPolicy) \ @@ -1204,7 +1205,6 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) _(StoreUnboxedScalarPolicy) \ _(StoreUnboxedObjectOrNullPolicy) \ _(TestPolicy) \ - _(AllDoublePolicy) \ _(ToDoublePolicy) \ _(ToInt32Policy) \ _(ToStringPolicy) \ diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 8802d3582b..68e4eb30e4 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -1357,5 +1357,19 @@ CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind) return true; } +template +static bool DoToNumeric(JSContext* cx, HandleValue arg, MutableHandleValue ret) +{ + ret.set(arg); + if (allowBigInt) { + return ToNumeric(cx, ret); + } + return ToNumber(cx, ret); +} + +typedef bool (*ToNumericFn)(JSContext*, HandleValue, MutableHandleValue); +const VMFunction ToNumberInfo = FunctionInfo(DoToNumeric, "ToNumber"); +const VMFunction ToNumericInfo = FunctionInfo(DoToNumeric, "ToNumeric"); + } // namespace jit } // namespace js diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index f4280f5800..b134c5df05 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -803,6 +803,9 @@ BaselineGetFunctionThis(JSContext* cx, BaselineFrame* frame, MutableHandleValue MOZ_MUST_USE bool CheckIsCallable(JSContext* cx, HandleValue v, CheckIsCallableKind kind); +extern const VMFunction ToNumberInfo; +extern const VMFunction ToNumericInfo; + } // namespace jit } // namespace js diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index 69782f8061..c0abfc9123 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -7494,6 +7494,21 @@ class LLoadUnboxedExpando : public LInstructionHelper<1, 1, 0> } }; +// Ensure that a value is numeric, possibly via a VM call-out that invokes +// valueOf(). +class LToNumeric : public LInstructionHelper { + public: + LIR_HEADER(ToNumeric) + + explicit LToNumeric(const LBoxAllocation& input) { + 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<0, BOX_PIECES, 1> { diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index 396765fbd0..92fcf16e7a 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -258,6 +258,7 @@ _(GuardClass) \ _(GuardUnboxedExpando) \ _(LoadUnboxedExpando) \ + _(ToNumeric) \ _(TypeBarrierV) \ _(TypeBarrierO) \ _(MonitorTypes) \ diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 7b04f5fb72..f97ba99272 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -4196,6 +4196,14 @@ CASE(JSOP_DEC) } END_CASE(JSOP_DEC) +CASE(JSOP_TONUMERIC) +{ + if (!ToNumeric(cx, REGS.stackHandleAt(-1))) { + goto error; + } +} +END_CASE(JSOP_TONUMERIC) + CASE(JSOP_BIGINT) { PUSH_COPY(script->getConst(GET_UINT32_INDEX(REGS.pc))); diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index c86a22baac..ad140ff7bc 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2357,7 +2357,7 @@ * Operands: * Stack: arg => rval */ \ - macro(JSOP_DYNAMIC_IMPORT, 234, "call-import", NULL, 1, 1, 1, JOF_BYTE) \ + macro(JSOP_DYNAMIC_IMPORT, 234, "call-import", NULL, 1, 1, 1, JOF_BYTE) \ /* * Pops the numeric value 'val' from the stack, then pushes 'val + 1'. * @@ -2376,6 +2376,14 @@ * Stack: val => (val - 1) */ \ macro(JSOP_DEC, 236, "dec", NULL, 1, 1, 1, JOF_BYTE) \ + /* + * 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, 237, "tonumeric", NULL, 1, 1, 1, JOF_BYTE) \ /* * Pushes a BigInt constant onto the stack. * Category: Literals @@ -2383,13 +2391,12 @@ * Operands: uint32_t constIndex * Stack: => val */ \ - macro(JSOP_BIGINT, 237, "bigint", NULL, 5, 0, 1, JOF_BIGINT) + macro(JSOP_BIGINT, 238, "bigint", NULL, 5, 0, 1, JOF_BIGINT) /* * 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) \ - macro(238) \ macro(239) \ macro(240) \ macro(241) \ From 8f44226445c3ccb8179d0b3bee2013f64ac93b19 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Mon, 18 Sep 2023 01:10:07 -0500 Subject: [PATCH 8/8] Issue #2308 & #1240 Follow-up - Fill in missing JSOP_INC/DEC cases for Doubles in Ion. --- js/src/jit/SharedIC.cpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp index 0afc7e12b5..d337534768 100644 --- a/js/src/jit/SharedIC.cpp +++ b/js/src/jit/SharedIC.cpp @@ -1570,12 +1570,8 @@ ICUnaryArith_Double::Compiler::generateStubCode(MacroAssembler& masm) Label failure; masm.ensureDouble(R0, FloatReg0, &failure); - MOZ_ASSERT(op == JSOP_NEG || op == JSOP_BITNOT); - - if (op == JSOP_NEG) { - masm.negateDouble(FloatReg0); - masm.boxDouble(FloatReg0, R0); - } else { + switch (op) { + case JSOP_BITNOT: { // Truncate the double to an int32. Register scratchReg = R1.scratchReg(); @@ -1593,6 +1589,24 @@ ICUnaryArith_Double::Compiler::generateStubCode(MacroAssembler& masm) masm.bind(&doneTruncate); masm.not32(scratchReg); masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); + break; + } + case JSOP_NEG: + masm.negateDouble(FloatReg0); + masm.boxDouble(FloatReg0, R0); + break; + case JSOP_INC: + case JSOP_DEC: + masm.loadConstantDouble(1.0, ScratchDoubleReg); + if (op == JSOP_INC) { + masm.addDouble(ScratchDoubleReg, FloatReg0); + } else { + masm.subDouble(ScratchDoubleReg, FloatReg0); + } + masm.boxDouble(FloatReg0, R0); + break; + default: + MOZ_CRASH("Unexpected op"); } EmitReturnFromIC(masm);