From bbe40dab58fa3e6ee395974da641581e08958de8 Mon Sep 17 00:00:00 2001 From: FranklinDM Date: Fri, 20 May 2022 15:38:43 +0800 Subject: Issue #1894 - Part 4: Implement IonMonkey support for nullish coalescing Based on https://bugzilla.mozilla.org/show_bug.cgi?id=1566141 --- js/src/jit/BaselineCompiler.cpp | 19 ++++++++++++++++++ js/src/jit/BaselineCompiler.h | 1 + js/src/jit/CodeGenerator.cpp | 18 +++++++++++++++++ js/src/jit/CodeGenerator.h | 1 + js/src/jit/IonBuilder.cpp | 40 ++++++++++++++++++++++++++----------- js/src/jit/IonBuilder.h | 8 ++++---- js/src/jit/Lowering.cpp | 10 ++++++++++ js/src/jit/Lowering.h | 1 + js/src/jit/MIR.h | 24 ++++++++++++++++++++++ js/src/jit/MOpcodes.h | 1 + js/src/jit/shared/LIR-shared.h | 15 ++++++++++++++ js/src/jit/shared/LOpcodes-shared.h | 1 + 12 files changed, 123 insertions(+), 16 deletions(-) diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index fd85ec00ed..53254718c0 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1252,6 +1252,25 @@ BaselineCompiler::emit_JSOP_IFNE() return emitTest(true); } +bool +BaselineCompiler::emit_JSOP_COALESCE() { + // COALESCE leaves the original value on the stack. + frame.syncStack(0); + + masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); + + Label undefinedOrNull; + + masm.branchTestUndefined(Assembler::Equal, R0, &undefinedOrNull); + masm.branchTestNull(Assembler::Equal, R0, &undefinedOrNull); + + jsbytecode* target = pc + GET_JUMP_OFFSET(pc); + masm.jump(labelOf(target)); + + masm.bind(&undefinedOrNull); + return true; +} + bool BaselineCompiler::emitAndOr(bool branchIfTrue) { diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index f975162a6a..d3ab58aa4e 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -45,6 +45,7 @@ namespace jit { _(JSOP_GOTO) \ _(JSOP_IFEQ) \ _(JSOP_IFNE) \ + _(JSOP_COALESCE) \ _(JSOP_AND) \ _(JSOP_OR) \ _(JSOP_NOT) \ diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index ec3d35ff0b..66e8e25ddf 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -11517,6 +11517,24 @@ CodeGenerator::visitIsObjectAndBranch(LIsObjectAndBranch* ins) testObjectEmitBranch(Assembler::Equal, value, ins->ifTrue(), ins->ifFalse()); } +void +CodeGenerator::visitIsNullOrUndefined(LIsNullOrUndefined* ins) +{ + Register output = ToRegister(ins->output()); + ValueOperand value = ToValue(ins, LIsNullOrUndefined::Input); + + Label isNotNull, done; + masm.branchTestNull(Assembler::NotEqual, value, &isNotNull); + + masm.move32(Imm32(1), output); + masm.jump(&done); + + masm.bind(&isNotNull); + masm.testUndefinedSet(Assembler::Equal, value, output); + + masm.bind(&done); +} + void CodeGenerator::loadOutermostJSScript(Register reg) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index b8aecc8ba8..ff09222f17 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -376,6 +376,7 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitOutOfLineIsConstructor(OutOfLineIsConstructor* ool); void visitIsObject(LIsObject* lir); void visitIsObjectAndBranch(LIsObjectAndBranch* lir); + void visitIsNullOrUndefined(LIsNullOrUndefined* ins); void visitHasClass(LHasClass* lir); void visitGuardToClass(LGuardToClass* lir); void visitWasmParameter(LWasmParameter* lir); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index dbecec2a7e..62470a2b81 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -276,10 +276,10 @@ IonBuilder::CFGState::IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd, MTest* t } IonBuilder::CFGState -IonBuilder::CFGState::AndOr(jsbytecode* join, MBasicBlock* lhs) +IonBuilder::CFGState::Logical(jsbytecode* join, MBasicBlock* lhs) { CFGState state; - state.state = AND_OR; + state.state = LOGICAL; state.stopAt = join; state.branch.ifFalse = lhs; state.branch.test = nullptr; @@ -1737,9 +1737,10 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_TOSTRING: return jsop_tostring(); + case JSOP_COALESCE: case JSOP_AND: case JSOP_OR: - return jsop_andor(op); + return jsop_logical(op); case JSOP_DEFVAR: return jsop_defvar(GET_UINT32_INDEX(pc)); @@ -2345,8 +2346,8 @@ IonBuilder::processCfgEntry(CFGState& state) case CFGState::COND_SWITCH_BODY: return processCondSwitchBody(state); - case CFGState::AND_OR: - return processAndOrEnd(state); + case CFGState::LOGICAL: + return processLogicalEnd(state); case CFGState::LABEL: return processLabelEnd(state); @@ -2948,7 +2949,7 @@ IonBuilder::processNextTableSwitchCase(CFGState& state) } IonBuilder::ControlStatus -IonBuilder::processAndOrEnd(CFGState& state) +IonBuilder::processLogicalEnd(CFGState& state) { MOZ_ASSERT(current); MBasicBlock* lhs = state.branch.ifFalse; @@ -4413,9 +4414,9 @@ IonBuilder::processCondSwitchBody(CFGState& state) } bool -IonBuilder::jsop_andor(JSOp op) +IonBuilder::jsop_logical(JSOp op) { - MOZ_ASSERT(op == JSOP_AND || op == JSOP_OR); + MOZ_ASSERT(op == JSOP_AND || op == JSOP_OR || op == JSOP_COALESCE); jsbytecode* rhsStart = pc + CodeSpec[op].length; jsbytecode* joinStart = pc + GetJumpOffset(pc); @@ -4429,9 +4430,24 @@ IonBuilder::jsop_andor(JSOp op) if (!evalLhs || !evalRhs) return false; - MTest* test = (op == JSOP_AND) - ? newTest(lhs, evalRhs, evalLhs) - : newTest(lhs, evalLhs, evalRhs); + MTest* test; + switch (op) { + case JSOP_COALESCE: { + MIsNullOrUndefined* isNullOrUndefined = + MIsNullOrUndefined::New(alloc(), lhs); + current->add(isNullOrUndefined); + test = newTest(isNullOrUndefined, evalLhs, evalRhs); + break; + } + + case JSOP_AND: + test = newTest(lhs, evalRhs, evalLhs); + break; + + case JSOP_OR: + test = newTest(lhs, evalLhs, evalRhs); + break; + } current->end(test); // Create the lhs block and specialize. @@ -4442,7 +4458,7 @@ IonBuilder::jsop_andor(JSOp op) return false; // Create the rhs block. - if (!cfgStack_.append(CFGState::AndOr(joinStart, evalLhs))) + if (!cfgStack_.append(CFGState::Logical(joinStart, evalLhs))) return false; if (!setCurrentAndSpecializePhis(evalRhs)) diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 402fbbf1af..a07020a4e8 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -90,7 +90,7 @@ class IonBuilder TABLE_SWITCH, // switch() { x } COND_SWITCH_CASE, // switch() { case X: ... } COND_SWITCH_BODY, // switch() { case ...: X } - AND_OR, // && x, || x + LOGICAL, // && x, || x, ?? x LABEL, // label: x TRY // try { x } catch(e) { } }; @@ -197,7 +197,7 @@ class IonBuilder static CFGState If(jsbytecode* join, MTest* test); static CFGState IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd, MTest* test); - static CFGState AndOr(jsbytecode* join, MBasicBlock* lhs); + static CFGState Logical(jsbytecode* join, MBasicBlock* lhs); static CFGState TableSwitch(jsbytecode* exitpc, MTableSwitch* ins); static CFGState CondSwitch(IonBuilder* builder, jsbytecode* exitpc, jsbytecode* defaultTarget); static CFGState Label(jsbytecode* exitpc); @@ -257,7 +257,7 @@ class IonBuilder ControlStatus processCondSwitchBody(CFGState& state); ControlStatus processSwitchBreak(JSOp op); ControlStatus processSwitchEnd(DeferredEdge* breaks, jsbytecode* exitpc); - ControlStatus processAndOrEnd(CFGState& state); + ControlStatus processLogicalEnd(CFGState& state); ControlStatus processLabelEnd(CFGState& state); ControlStatus processTryEnd(CFGState& state); ControlStatus processReturn(JSOp op); @@ -712,7 +712,7 @@ class IonBuilder MOZ_MUST_USE bool jsop_try(); MOZ_MUST_USE bool jsop_label(); MOZ_MUST_USE bool jsop_condswitch(); - MOZ_MUST_USE bool jsop_andor(JSOp op); + MOZ_MUST_USE bool jsop_logical(JSOp op); MOZ_MUST_USE bool jsop_dup2(); MOZ_MUST_USE bool jsop_loophead(jsbytecode* pc); MOZ_MUST_USE bool jsop_compare(JSOp op); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 0347154675..f143ff9e04 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -4162,6 +4162,16 @@ LIRGenerator::visitIsObject(MIsObject* ins) define(lir, ins); } +void +LIRGenerator::visitIsNullOrUndefined(MIsNullOrUndefined* ins) +{ + MDefinition* opd = ins->input(); + MOZ_ASSERT(opd->type() == MIRType::Value); + LIsNullOrUndefined* lir = + new(alloc()) LIsNullOrUndefined(useBoxAtStart(opd)); + define(lir, ins); +} + void LIRGenerator::visitHasClass(MHasClass* ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index d5d0c3af20..5340fce762 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -288,6 +288,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitIsCallable(MIsCallable* ins); void visitIsConstructor(MIsConstructor* ins); void visitIsObject(MIsObject* ins); + void visitIsNullOrUndefined(MIsNullOrUndefined* ins); void visitHasClass(MHasClass* ins); void visitGuardToClass(MGuardToClass* ins); void visitWasmAddOffset(MWasmAddOffset* ins); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index a398ef3344..ea00529e09 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -13156,6 +13156,30 @@ class MIsObject return AliasSet::None(); } }; + +class MIsNullOrUndefined + : public MUnaryInstruction, + public BoxInputsPolicy::Data +{ + explicit MIsNullOrUndefined(MDefinition* object) + : MUnaryInstruction(object) + { + setResultType(MIRType::Boolean); + setMovable(); + } + + public: + INSTRUCTION_HEADER(IsNullOrUndefined) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, object)) + + bool congruentTo(const MDefinition* ins) const override { + return congruentIfOperandsEqual(ins); + } + AliasSet getAliasSet() const override { + return AliasSet::None(); + } +}; class MHasClass : public MUnaryInstruction, diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 3185d0a836..c1644ba8b3 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -271,6 +271,7 @@ namespace jit { _(IsConstructor) \ _(IsCallable) \ _(IsObject) \ + _(IsNullOrUndefined) \ _(HasClass) \ _(GuardToClass) \ _(CopySign) \ diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index 62dc574b6c..f97dc1534f 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -7851,6 +7851,21 @@ class LIsObjectAndBranch : public LControlInstructionHelper<2, BOX_PIECES, 0> } }; +class LIsNullOrUndefined : public LInstructionHelper<1, BOX_PIECES, 0> +{ + public: + LIR_HEADER(IsNullOrUndefined); + static const size_t Input = 0; + + explicit LIsNullOrUndefined(const LBoxAllocation& input) { + setBoxOperand(Input, input); + } + + MIsNullOrUndefined* mir() const { + return mir_->toIsNullOrUndefined(); + } +}; + class LHasClass : public LInstructionHelper<1, 1, 0> { public: diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index d0651af1d2..7f4cd770ac 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -385,6 +385,7 @@ _(IsConstructor) \ _(IsObject) \ _(IsObjectAndBranch) \ + _(IsNullOrUndefined) \ _(HasClass) \ _(GuardToClass) \ _(RecompileCheck) \ -- cgit v1.2.3