Bug 1566141 - implement nullish coalescence in ion monkey r=jandem
authoryulia <ystartsev@mozilla.com>
Tue, 12 Nov 2019 16:57:39 +0000
changeset 501728 ddc6fa7e24f2eb5e96e0e0215a943b06d4607b95
parent 501727 2a8c35575189a16dcdf6af014a12b046817b28b2
child 501729 360c8b9f1566c66e8890f8bda2891a508ad36cc9
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1566141
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1566141 - implement nullish coalescence in ion monkey r=jandem Differential Revision: https://phabricator.services.mozilla.com/D51640
js/src/jit/CodeGenerator.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonControlFlow.cpp
js/src/jit/IonControlFlow.h
js/src/jit/Lowering.cpp
js/src/jit/MIR.h
js/src/jit/shared/LIR-shared.h
js/src/vm/BytecodeUtil-inl.h
js/src/vm/BytecodeUtil.cpp
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -13060,16 +13060,32 @@ void CodeGenerator::visitIsObject(LIsObj
   masm.testObjectSet(Assembler::Equal, value, output);
 }
 
 void CodeGenerator::visitIsObjectAndBranch(LIsObjectAndBranch* ins) {
   ValueOperand value = ToValue(ins, LIsObjectAndBranch::Input);
   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) {
   // The "outermost" JSScript means the script that we are compiling
   // basically; this is not always the script associated with the
   // current basic block, which might be an inlined script.
 
   MIRGraph& graph = current->mir()->graph();
   MBasicBlock* entryBlock = graph.entryBlock();
   masm.movePtr(ImmGCPtr(entryBlock->info().script()), reg);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -1927,16 +1927,17 @@ AbortReasonOr<Ok> IonBuilder::inspectOpc
       return Ok();
 
     case JSOP_IFNE:
     case JSOP_IFEQ:
     case JSOP_RETURN:
     case JSOP_RETRVAL:
     case JSOP_AND:
     case JSOP_OR:
+    case JSOP_COALESCE:
     case JSOP_TRY:
     case JSOP_THROW:
     case JSOP_GOTO:
     case JSOP_CONDSWITCH:
     case JSOP_TABLESWITCH:
     case JSOP_CASE:
     case JSOP_DEFAULT:
       // Control flow opcodes should be handled in the ControlFlowGenerator.
@@ -3143,26 +3144,35 @@ AbortReasonOr<Ok> IonBuilder::jsop_dup2(
   uint32_t lhsSlot = current->stackDepth() - 2;
   uint32_t rhsSlot = current->stackDepth() - 1;
   current->pushSlot(lhsSlot);
   current->pushSlot(rhsSlot);
   return Ok();
 }
 
 AbortReasonOr<Ok> IonBuilder::visitTest(CFGTest* test) {
+  CFGTestKind kind = test->getKind();
   MDefinition* ins =
-      test->mustKeepCondition() ? current->peek(-1) : current->pop();
+      kind != CFGTestKind::ToBooleanAndPop ? current->peek(-1) : current->pop();
 
   // Create true and false branches.
   MBasicBlock* ifTrue;
   MOZ_TRY_VAR(ifTrue, newBlock(current, test->trueBranch()->startPc()));
   MBasicBlock* ifFalse;
   MOZ_TRY_VAR(ifFalse, newBlock(current, test->falseBranch()->startPc()));
 
-  MTest* mir = newTest(ins, ifTrue, ifFalse);
+  MTest* mir;
+  if (kind == CFGTestKind::Coalesce) {
+    MIsNullOrUndefined* isNullOrUndefined =
+        MIsNullOrUndefined::New(alloc(), ins);
+    current->add(isNullOrUndefined);
+    mir = newTest(isNullOrUndefined, ifFalse, ifTrue);
+  } else {
+    mir = newTest(ins, ifTrue, ifFalse);
+  }
   current->end(mir);
 
   // Filter the types in the true branch.
   MOZ_TRY(setCurrentAndSpecializePhis(ifTrue));
   MOZ_TRY(improveTypesAtTest(mir->getOperand(0), /* trueBranch = */ true, mir));
 
   blockWorklist[test->trueBranch()->id()] = ifTrue;
 
--- a/js/src/jit/IonControlFlow.cpp
+++ b/js/src/jit/IonControlFlow.cpp
@@ -324,19 +324,20 @@ ControlFlowGenerator::ControlStatus Cont
     case JSOP_IFNE:
       // We should never reach an IFNE, it's a stopAt point, which will
       // trigger closing the loop.
       MOZ_CRASH("we should never reach an ifne!");
 
     case JSOP_IFEQ:
       return processIfStart(JSOP_IFEQ);
 
+    case JSOP_COALESCE:
     case JSOP_AND:
     case JSOP_OR:
-      return processAndOr(op);
+      return processShortCircuit(op);
 
     case JSOP_LABEL:
       return processLabel();
 
     case JSOP_TRY:
       return processTry();
 
     case JSOP_THROWMSG:
@@ -469,18 +470,18 @@ ControlFlowGenerator::ControlStatus Cont
       return processNextTableSwitchCase(state);
 
     case CFGState::COND_SWITCH_CASE:
       return processCondSwitchCase(state);
 
     case CFGState::COND_SWITCH_BODY:
       return processCondSwitchBody(state);
 
-    case CFGState::AND_OR:
-      return processAndOrEnd(state);
+    case CFGState::SHORT_CIRCUIT:
+      return processShortCircuitEnd(state);
 
     case CFGState::LABEL:
       return processLabelEnd(state);
 
     case CFGState::TRY:
       return processTryEnd(state);
 
     default:
@@ -1399,18 +1400,18 @@ ControlFlowGenerator::ControlStatus Cont
   if (currentIdx < bodies.length()) {
     state.stopAt = bodies[currentIdx]->startPc();
   } else {
     state.stopAt = state.switch_.exitpc;
   }
   return ControlStatus::Jumped;
 }
 
-ControlFlowGenerator::ControlStatus ControlFlowGenerator::processAndOrEnd(
-    CFGState& state) {
+ControlFlowGenerator::ControlStatus
+ControlFlowGenerator::processShortCircuitEnd(CFGState& state) {
   MOZ_ASSERT(current);
   CFGBlock* lhs = state.branch.ifFalse;
 
   // Create a new block to represent the join.
   CFGBlock* join = CFGBlock::New(alloc(), state.stopAt);
 
   // End the rhs.
   current->setStopIns(CFGGoto::New(alloc(), join));
@@ -2058,20 +2059,20 @@ ControlFlowGenerator::CFGState ControlFl
       (falseEnd == ifFalse->startPc()) ? IF_TRUE_EMPTY_ELSE : IF_ELSE_TRUE;
   state.stopAt = trueEnd;
   state.branch.falseEnd = falseEnd;
   state.branch.ifFalse = ifFalse;
   state.branch.test = test;
   return state;
 }
 
-ControlFlowGenerator::CFGState ControlFlowGenerator::CFGState::AndOr(
+ControlFlowGenerator::CFGState ControlFlowGenerator::CFGState::ShortCircuit(
     jsbytecode* join, CFGBlock* lhs) {
   CFGState state;
-  state.state = AND_OR;
+  state.state = SHORT_CIRCUIT;
   state.stopAt = join;
   state.branch.ifFalse = lhs;
   state.branch.test = nullptr;
   return state;
 }
 
 ControlFlowGenerator::CFGState ControlFlowGenerator::CFGState::TableSwitch(
     TempAllocator& alloc, jsbytecode* exitpc) {
@@ -2113,35 +2114,47 @@ ControlFlowGenerator::CFGState ControlFl
     jsbytecode* exitpc, CFGBlock* successor) {
   CFGState state;
   state.state = TRY;
   state.stopAt = exitpc;
   state.try_.successor = successor;
   return state;
 }
 
-ControlFlowGenerator::ControlStatus ControlFlowGenerator::processAndOr(
+ControlFlowGenerator::ControlStatus ControlFlowGenerator::processShortCircuit(
     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);
   MOZ_ASSERT(joinStart > pc);
 
   CFGBlock* evalLhs = CFGBlock::New(alloc(), joinStart);
   CFGBlock* evalRhs = CFGBlock::New(alloc(), rhsStart);
 
-  CFGTest* test = (op == JSOP_AND) ? CFGTest::New(alloc(), evalRhs, evalLhs)
-                                   : CFGTest::New(alloc(), evalLhs, evalRhs);
-  test->keepCondition();
+  CFGTest* test;
+  switch (op) {
+    case JSOP_AND:
+      test = CFGTest::New(alloc(), evalRhs, evalLhs, CFGTestKind::ToBoolean);
+      break;
+    case JSOP_OR:
+      test = CFGTest::New(alloc(), evalLhs, evalRhs, CFGTestKind::ToBoolean);
+      break;
+    case JSOP_COALESCE:
+      test = CFGTest::New(alloc(), evalLhs, evalRhs, CFGTestKind::Coalesce);
+      break;
+    default:
+      MOZ_CRASH("Unexpected op code");
+  }
+
   current->setStopIns(test);
   current->setStopPc(pc);
 
   // Create the rhs block.
-  if (!cfgStack_.append(CFGState::AndOr(joinStart, evalLhs))) {
+  if (!cfgStack_.append(CFGState::ShortCircuit(joinStart, evalLhs))) {
     return ControlStatus::Error;
   }
 
   if (!addBlock(evalLhs)) {
     return ControlStatus::Error;
   }
 
   current = evalRhs;
--- a/js/src/jit/IonControlFlow.h
+++ b/js/src/jit/IonControlFlow.h
@@ -289,52 +289,50 @@ class CFGCondSwitchCase : public CFGAryC
   }
 
   CFGBlock* trueBranch() const { return getSuccessor(0); }
   CFGBlock* falseBranch() const { return getSuccessor(1); }
   size_t truePopAmount() const { return truePopAmount_; }
   size_t falsePopAmount() const { return falsePopAmount_; }
 };
 
+enum class CFGTestKind { Coalesce, ToBoolean, ToBooleanAndPop };
 /**
  * CFGTest
  *
  * POP / PEEK (depending on keepCondition_)
  * IFEQ JUMP succ1
  * IFNEQ JUMP succ2
  *
  */
 class CFGTest : public CFGAryControlInstruction<2> {
-  // By default the condition gets popped. This variable
-  // keeps track if we want to keep the condition.
-  bool keepCondition_;
+  CFGTestKind kind_;
 
-  CFGTest(CFGBlock* succ1, CFGBlock* succ2) : keepCondition_(false) {
+  CFGTest(CFGBlock* succ1, CFGBlock* succ2)
+      : kind_(CFGTestKind::ToBooleanAndPop) {
     replaceSuccessor(0, succ1);
     replaceSuccessor(1, succ2);
   }
-  CFGTest(CFGBlock* succ1, CFGBlock* succ2, bool keepCondition)
-      : keepCondition_(keepCondition) {
+  CFGTest(CFGBlock* succ1, CFGBlock* succ2, CFGTestKind kind) : kind_(kind) {
     replaceSuccessor(0, succ1);
     replaceSuccessor(1, succ2);
   }
 
  public:
   CFG_CONTROL_HEADER(Test);
   TRIVIAL_CFG_NEW_WRAPPERS
 
   static CFGTest* CopyWithNewTargets(TempAllocator& alloc, CFGTest* old,
                                      CFGBlock* succ1, CFGBlock* succ2) {
-    return new (alloc) CFGTest(succ1, succ2, old->mustKeepCondition());
+    return new (alloc) CFGTest(succ1, succ2, old->getKind());
   }
 
   CFGBlock* trueBranch() const { return getSuccessor(0); }
   CFGBlock* falseBranch() const { return getSuccessor(1); }
-  void keepCondition() { keepCondition_ = true; }
-  bool mustKeepCondition() const { return keepCondition_; }
+  CFGTestKind getKind() const { return kind_; }
 };
 
 /**
  * CFGReturn
  *
  * POP
  * RETURN popped value
  *
@@ -580,17 +578,17 @@ class ControlFlowGenerator {
       WHILE_LOOP_COND,     // while (x) { }
       WHILE_LOOP_BODY,     // while () { x }
       FOR_LOOP_COND,       // for (; x;) { }
       FOR_LOOP_BODY,       // for (; ;) { x }
       FOR_LOOP_UPDATE,     // for (; ; x) { }
       TABLE_SWITCH,        // switch() { x }
       COND_SWITCH_CASE,    // switch() { case X: ... }
       COND_SWITCH_BODY,    // switch() { case ...: X }
-      AND_OR,              // && x, || x
+      SHORT_CIRCUIT,       // && x, || x, ?? x
       LABEL,               // label: x
       TRY                  // try { x } catch(e) { }
     };
 
     State state;         // Current state of this control structure.
     jsbytecode* stopAt;  // Bytecode at which to stop the processing loop.
 
     // For if structures, this contains branch information.
@@ -668,17 +666,17 @@ class ControlFlowGenerator {
         default:
           return false;
       }
     }
 
     static CFGState If(jsbytecode* join, CFGTest* test);
     static CFGState IfElse(jsbytecode* trueEnd, jsbytecode* falseEnd,
                            CFGTest* test);
-    static CFGState AndOr(jsbytecode* join, CFGBlock* lhs);
+    static CFGState ShortCircuit(jsbytecode* join, CFGBlock* lhs);
     static CFGState TableSwitch(TempAllocator& alloc, jsbytecode* exitpc);
     static CFGState CondSwitch(TempAllocator& alloc, jsbytecode* exitpc,
                                jsbytecode* defaultTarget);
     static CFGState Label(jsbytecode* exitpc);
     static CFGState Try(jsbytecode* exitpc, CFGBlock* successor);
   };
 
   Vector<CFGState, 8, JitAllocPolicy> cfgStack_;
@@ -738,18 +736,18 @@ class ControlFlowGenerator {
   ControlStatus processThrow();
   ControlStatus processTableSwitch(JSOp op, jssrcnote* sn);
   ControlStatus processContinue(JSOp op);
   ControlStatus processBreak(JSOp op, jssrcnote* sn);
   ControlStatus processReturn(JSOp op);
   ControlStatus snoopControlFlow(JSOp op);
   ControlStatus processBrokenLoop(CFGState& state);
   ControlStatus finishLoop(CFGState& state, CFGBlock* successor);
-  ControlStatus processAndOr(JSOp op);
-  ControlStatus processAndOrEnd(CFGState& state);
+  ControlStatus processShortCircuit(JSOp op);
+  ControlStatus processShortCircuitEnd(CFGState& state);
   ControlStatus processLabel();
   ControlStatus processLabelEnd(CFGState& state);
 
   MOZ_MUST_USE bool pushLoop(CFGState::State state, jsbytecode* stopAt,
                              CFGBlock* entry, jsbytecode* loopHead,
                              jsbytecode* initialPc, jsbytecode* bodyStart,
                              jsbytecode* bodyEnd, jsbytecode* exitpc,
                              jsbytecode* continuepc);
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -4209,16 +4209,24 @@ void LIRGenerator::visitIsObject(MIsObje
   }
 
   MDefinition* opd = ins->input();
   MOZ_ASSERT(opd->type() == MIRType::Value);
   LIsObject* lir = new (alloc()) LIsObject(useBoxAtStart(opd));
   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) {
   MOZ_ASSERT(ins->object()->type() == MIRType::Object);
   MOZ_ASSERT(ins->type() == MIRType::Boolean);
   define(new (alloc()) LHasClass(useRegister(ins->object())), ins);
 }
 
 void LIRGenerator::visitGuardToClass(MGuardToClass* ins) {
   MOZ_ASSERT(ins->object()->type() == MIRType::Object);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -10717,16 +10717,35 @@ class MIsObject : public MUnaryInstructi
   NAMED_OPERANDS((0, object))
 
   bool congruentTo(const MDefinition* ins) const override {
     return congruentIfOperandsEqual(ins);
   }
   AliasSet getAliasSet() const override { return AliasSet::None(); }
 };
 
+class MIsNullOrUndefined : public MUnaryInstruction,
+                           public BoxInputsPolicy::Data {
+  explicit MIsNullOrUndefined(MDefinition* value)
+      : MUnaryInstruction(classOpcode, value) {
+    setResultType(MIRType::Boolean);
+    setMovable();
+  }
+
+ public:
+  INSTRUCTION_HEADER(IsNullOrUndefined)
+  TRIVIAL_NEW_WRAPPERS
+  NAMED_OPERANDS((0, value))
+
+  bool congruentTo(const MDefinition* ins) const override {
+    return congruentIfOperandsEqual(ins);
+  }
+  AliasSet getAliasSet() const override { return AliasSet::None(); }
+};
+
 class MHasClass : public MUnaryInstruction, public SingleObjectPolicy::Data {
   const JSClass* class_;
 
   MHasClass(MDefinition* object, const JSClass* clasp)
       : MUnaryInstruction(classOpcode, object), class_(clasp) {
     MOZ_ASSERT(object->type() == MIRType::Object ||
                (object->type() == MIRType::Value &&
                 object->mightBeType(MIRType::Object)));
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -6072,16 +6072,29 @@ class LIsObjectAndBranch : public LContr
   }
 
   static const size_t Input = 0;
 
   MBasicBlock* ifTrue() const { return getSuccessor(0); }
   MBasicBlock* ifFalse() const { return getSuccessor(1); }
 };
 
+class LIsNullOrUndefined : public LInstructionHelper<1, BOX_PIECES, 0> {
+ public:
+  LIR_HEADER(IsNullOrUndefined);
+  static const size_t Input = 0;
+
+  explicit LIsNullOrUndefined(const LBoxAllocation& input)
+      : LInstructionHelper(classOpcode) {
+    setBoxOperand(Input, input);
+  }
+
+  MIsNullOrUndefined* mir() const { return mir_->toIsNullOrUndefined(); }
+};
+
 class LHasClass : public LInstructionHelper<1, 1, 0> {
  public:
   LIR_HEADER(HasClass);
   explicit LHasClass(const LAllocation& lhs) : LInstructionHelper(classOpcode) {
     setOperand(0, lhs);
   }
 
   const LAllocation* lhs() { return getOperand(0); }
--- a/js/src/vm/BytecodeUtil-inl.h
+++ b/js/src/vm/BytecodeUtil-inl.h
@@ -17,16 +17,17 @@ namespace js {
 static inline unsigned GetDefCount(jsbytecode* pc) {
   /*
    * Add an extra pushed value for OR/AND opcodes, so that they are included
    * in the pushed array of stack values for type inference.
    */
   switch (JSOp(*pc)) {
     case JSOP_OR:
     case JSOP_AND:
+    case JSOP_COALESCE:
       return 1;
     case JSOP_PICK:
     case JSOP_UNPICK:
       /*
        * Pick pops and pushes how deep it looks in the stack + 1
        * items. i.e. if the stack were |a b[2] c[1] d[0]|, pick 2
        * would pop b, c, and d to rearrange the stack to |a c[0]
        * d[1] b[2]|.
--- a/js/src/vm/BytecodeUtil.cpp
+++ b/js/src/vm/BytecodeUtil.cpp
@@ -676,16 +676,17 @@ uint32_t BytecodeParser::simulateOp(JSOp
     case JSOP_CHECKOBJCOERCIBLE:
     case JSOP_CHECKTHIS:
     case JSOP_CHECKTHISREINIT:
     case JSOP_CHECKCLASSHERITAGE:
     case JSOP_DEBUGCHECKSELFHOSTED:
     case JSOP_INITGLEXICAL:
     case JSOP_INITLEXICAL:
     case JSOP_OR:
+    case JSOP_COALESCE:
     case JSOP_SETALIASEDVAR:
     case JSOP_SETARG:
     case JSOP_SETINTRINSIC:
     case JSOP_SETLOCAL:
     case JSOP_THROWSETALIASEDCONST:
     case JSOP_THROWSETCALLEE:
     case JSOP_THROWSETCONST:
     case JSOP_INITALIASEDLEXICAL: