diff options
author | Moonchild <moonchild@palemoon.org> | 2023-09-20 11:56:31 +0200 |
---|---|---|
committer | Moonchild <moonchild@palemoon.org> | 2023-09-20 11:56:31 +0200 |
commit | d2eb2ce9f4760ad2dd5aaa474013d42e5bc9d762 (patch) | |
tree | 9a7bca4b6417d8a11ac6f102e7d3b24fb8008fd7 /js/src | |
parent | bb39e1e38f75d115bd1f0dc82d5b561a2da57d2e (diff) | |
parent | dba1e366014e91a04823e047dc20c8d01d259702 (diff) | |
download | uxp-d2eb2ce9f4760ad2dd5aaa474013d42e5bc9d762.tar.gz |
Merge branch 'master' into simdjs-removal
Diffstat (limited to 'js/src')
38 files changed, 709 insertions, 211 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 f17b554e7a..39d0eca21c 100644 --- a/js/src/frontend/ElemOpEmitter.cpp +++ b/js/src/frontend/ElemOpEmitter.cpp @@ -233,8 +233,8 @@ ElemOpEmitter::emitIncDec() MOZ_ASSERT(state_ == State::Get); - JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB; - if (!bce_->emit1(JSOP_POS)) { // ... N + JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC; + if (!bce_->emit1(JSOP_TONUMERIC)) { // ... N return false; } if (isPostIncDec()) { @@ -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..48caac8de3 100644 --- a/js/src/frontend/NameOpEmitter.cpp +++ b/js/src/frontend/NameOpEmitter.cpp @@ -339,11 +339,11 @@ 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; } - if (!bce_->emit1(JSOP_POS)) { // ENV? N + if (!bce_->emit1(JSOP_TONUMERIC)) { // ENV? N return false; } if (isPostIncDec()) { @@ -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..278bc0e0fd 100644 --- a/js/src/frontend/PropOpEmitter.cpp +++ b/js/src/frontend/PropOpEmitter.cpp @@ -217,9 +217,9 @@ 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 + if (!bce_->emit1(JSOP_TONUMERIC)) { // ... N return false; } if (isPostIncDec()) { @@ -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..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); @@ -1940,6 +1963,18 @@ BaselineCompiler::emit_JSOP_NEG() } bool +BaselineCompiler::emit_JSOP_INC() +{ + return emitUnaryArith(); +} + +bool +BaselineCompiler::emit_JSOP_DEC() +{ + return emitUnaryArith(); +} + +bool BaselineCompiler::emit_JSOP_LT() { return emitCompare(); diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 8e9976b17f..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) \ @@ -242,8 +243,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 +330,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/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index fa02c24374..b657359d3e 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -694,42 +694,6 @@ ICToBool_Object::Compiler::generateStubCode(MacroAssembler& masm) } // -// 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<DoToNumberFallbackFn>(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<ICToNumber_Fallback>(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 4c22a1839c..1b639d6b10 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 d427f5f7f4..0c28e978c4 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -2377,6 +2377,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: @@ -3413,6 +3415,48 @@ CodeGenerator::visitLoadUnboxedExpando(LLoadUnboxedExpando* lir) } 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) { ValueOperand operand = ToValue(lir, LTypeBarrierV::Input); diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 44804794a2..719f7ca1b1 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 ae3776c888..7eb69ec00f 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) { @@ -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: @@ -1584,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 @@ -1737,9 +1740,16 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_POS: return jsop_pos(); + case JSOP_TONUMERIC: + return jsop_tonumeric(); + case JSOP_NEG: return jsop_neg(); + case JSOP_INC: + case JSOP_DEC: + return jsop_inc_or_dec(op); + case JSOP_TOSTRING: return jsop_tostring(); @@ -4852,7 +4862,7 @@ IonBuilder::jsop_bitop(JSOp op) } MDefinition::Opcode -JSOpToMDefinition(JSOp op) +BinaryJSOpToMDefinition(JSOp op) { switch (op) { case JSOP_ADD: @@ -4953,6 +4963,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) { @@ -4986,19 +5030,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); - - if (op == JSOP_ADD || op == JSOP_MUL) - ins->setCommutative(); - - current->add(ins); - current->push(ins); + MDefinition::Opcode defOp = BinaryJSOpToMDefinition(op); + MIRType specialization = binaryArithNumberSpecialization(left, right); + MBinaryArithInstruction* ins = binaryArithEmitSpecialized(defOp, specialization, left, right); - 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; @@ -5022,16 +5067,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; @@ -5124,8 +5163,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'. @@ -5202,6 +5241,157 @@ 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_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_<TemporaryTypeSet>(); + 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) +{ + bool emitted = false; + MDefinition* value = current->pop(); + + startTrackingOptimizations(); + + trackTypeInfo(TrackedTypeSite::Operand, value->type(), value->resultTypeSet()); + + if (!unaryArithTrySpecialized(&emitted, op, value)) { + return false; + } + if (emitted) { + return true; + } + + 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 IonBuilder::jsop_tostring() { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 96cfd821ca..8f616b74eb 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); @@ -692,6 +703,8 @@ 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); MOZ_MUST_USE bool jsop_defvar(uint32_t index); diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h index 50d612ac8e..95e6aaf459 100644 --- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -619,6 +619,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 11e7348547..d2ce1596ff 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2166,6 +2166,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) { MDefinition* opd = truncate->input(); diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 9217ed9dcd..6a59c1b2ea 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -325,6 +325,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 34d9e07bf9..e30188cb48 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -2586,23 +2586,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) { @@ -3666,6 +3649,23 @@ MResumePoint::isRecoverableOperand(MUse* u) const } 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) { MDefinition* input = getOperand(0); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 532f404b51..23cce805c1 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -4335,6 +4335,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. @@ -5020,7 +5057,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/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index aa2dda77a2..857193e911 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -102,6 +102,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 7b35349ab2..bf1ea72d2a 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -1742,6 +1742,12 @@ MTruncateToInt32::computeRange(TempAllocator& alloc) } void +MToNumeric::computeRange(TempAllocator& alloc) +{ + setRange(new (alloc) Range(getOperand(0))); +} + +void MToInt32::computeRange(TempAllocator& alloc) { // No clamping since this computes the range *before* bailouts. diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp index 375edb1400..d337534768 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"); } @@ -1558,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(); @@ -1581,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); 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/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index 59ac9e2c21..5023d00f77 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -1071,6 +1071,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) \ @@ -1086,7 +1087,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 <bool allowBigInt = false> +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<ToNumericFn>(DoToNumeric, "ToNumber"); +const VMFunction ToNumericInfo = FunctionInfo<ToNumericFn>(DoToNumeric<true>, "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 437c3fa7f3..7f8e77ea69 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -6797,6 +6797,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<BOX_PIECES, BOX_PIECES, 0> { + 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 10c6be0b36..a411d3f066 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -217,6 +217,7 @@ _(GuardClass) \ _(GuardUnboxedExpando) \ _(LoadUnboxedExpando) \ + _(ToNumeric) \ _(TypeBarrierV) \ _(TypeBarrierO) \ _(MonitorTypes) \ 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-shared/BaseAssembler-x86-shared.h b/js/src/jit/x86-shared/BaseAssembler-x86-shared.h index 399380ae53..31a8d1f65c 100644 --- a/js/src/jit/x86-shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/x86-shared/BaseAssembler-x86-shared.h @@ -4942,7 +4942,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); @@ -4951,7 +4951,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); @@ -4961,7 +4961,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); @@ -4975,7 +4975,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/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"); } 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; 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<BigInt*> x); static BigInt* add(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); @@ -109,6 +111,8 @@ class BigInt final : public js::gc::TenuredCell { static BigInt* mod(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); static BigInt* pow(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); static BigInt* neg(js::ExclusiveContext* cx, Handle<BigInt*> x); + static BigInt* inc(js::ExclusiveContext* cx, Handle<BigInt*> x); + static BigInt* dec(js::ExclusiveContext* cx, Handle<BigInt*> x); static BigInt* lsh(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); static BigInt* rsh(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); static BigInt* bitAnd(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); @@ -145,6 +149,10 @@ class BigInt final : public js::gc::TenuredCell { MutableHandle<Value> res); static bool neg(js::ExclusiveContext* cx, Handle<Value> operand, MutableHandle<Value> res); + static bool inc(js::ExclusiveContext* cx, Handle<Value> operand, + MutableHandle<Value> res); + static bool dec(js::ExclusiveContext* cx, Handle<Value> operand, + MutableHandle<Value> res); static bool lsh(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, MutableHandle<Value> res); static bool rsh(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> 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<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) { diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index a48c753f1d..cbf3113b50 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -417,6 +417,46 @@ NegOperation(JSContext* cx, HandleScript script, jsbytecode* pc, MutableHandleVa } static MOZ_ALWAYS_INLINE bool +IncOperation(JSContext* cx, + MutableHandleValue val, + MutableHandleValue res) +{ + int32_t i; + if (val.isInt32() && (i = val.toInt32()) != INT32_MAX) { + res.setInt32(i + 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) +{ + int32_t i; + if (val.isInt32() && (i = val.toInt32()) != INT32_MIN) { + res.setInt32(i - 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, HandleScript script, jsbytecode* pc, HandleValue idval, MutableHandleValue res) { diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index a03fa847f7..f97ba99272 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -4176,6 +4176,34 @@ CASE(JSOP_IS_CONSTRUCTING) PUSH_MAGIC(JS_IS_CONSTRUCTING); END_CASE(JSOP_IS_CONSTRUCTING) +CASE(JSOP_INC) +{ + ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]); + MutableHandleValue res = REGS.stackHandleAt(-1); + if (!IncOperation(cx, &val, res)) { + goto error; + } +} +END_CASE(JSOP_INC) + +CASE(JSOP_DEC) +{ + ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]); + MutableHandleValue res = REGS.stackHandleAt(-1); + if (!DecOperation(cx, &val, res)) { + goto error; + } +} +END_CASE(JSOP_DEC) + +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 ff707aac08..ad140ff7bc 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2357,7 +2357,33 @@ * 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) \ + /* + * 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 @@ -2365,15 +2391,12 @@ * Operands: uint32_t constIndex * Stack: => val */ \ - macro(JSOP_BIGINT, 235, "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(236) \ - macro(237) \ - macro(238) \ macro(239) \ macro(240) \ macro(241) \ diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index fc3d6c5df2..de7ce028eb 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -3233,6 +3233,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: |