/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "jit/WarpBuilder.h" #include "mozilla/DebugOnly.h" #include "jit/BaselineFrame.h" #include "jit/CacheIR.h" #include "jit/CompileInfo.h" #include "jit/InlineScriptTree.h" #include "jit/MIR.h" #include "jit/MIRGenerator.h" #include "jit/MIRGraph.h" #include "jit/WarpCacheIRTranspiler.h" #include "jit/WarpSnapshot.h" #include "js/friend/ErrorMessages.h" // JSMSG_BAD_CONST_ASSIGN #include "vm/GeneratorObject.h" #include "vm/Interpreter.h" #include "vm/Opcodes.h" #include "gc/ObjectKind-inl.h" #include "vm/BytecodeIterator-inl.h" #include "vm/BytecodeLocation-inl.h" #include "vm/JSObject-inl.h" using namespace js; using namespace js::jit; // Used for building the outermost script. WarpBuilder::WarpBuilder(WarpSnapshot& snapshot, MIRGenerator& mirGen, WarpCompilation* warpCompilation) : WarpBuilderShared(snapshot, mirGen, nullptr), warpCompilation_(warpCompilation), graph_(mirGen.graph()), info_(mirGen.outerInfo()), scriptSnapshot_(snapshot.rootScript()), script_(snapshot.rootScript()->script()), loopStack_(mirGen.alloc()) { opSnapshotIter_ = scriptSnapshot_->opSnapshots().getFirst(); } // Used for building inlined scripts. WarpBuilder::WarpBuilder(WarpBuilder* caller, WarpScriptSnapshot* snapshot, CompileInfo& compileInfo, CallInfo* inlineCallInfo, MResumePoint* callerResumePoint) : WarpBuilderShared(caller->snapshot(), caller->mirGen(), nullptr), warpCompilation_(caller->warpCompilation()), graph_(caller->mirGen().graph()), info_(compileInfo), scriptSnapshot_(snapshot), script_(snapshot->script()), loopStack_(caller->mirGen().alloc()), callerBuilder_(caller), callerResumePoint_(callerResumePoint), inlineCallInfo_(inlineCallInfo) { opSnapshotIter_ = snapshot->opSnapshots().getFirst(); } BytecodeSite* WarpBuilder::newBytecodeSite(BytecodeLocation loc) { jsbytecode* pc = loc.toRawBytecode(); MOZ_ASSERT(info().inlineScriptTree()->script()->containsPC(pc)); return new (alloc()) BytecodeSite(info().inlineScriptTree(), pc); } const WarpOpSnapshot* WarpBuilder::getOpSnapshotImpl( BytecodeLocation loc, WarpOpSnapshot::Kind kind) { uint32_t offset = loc.bytecodeToOffset(script_); // Skip snapshots until we get to a snapshot with offset >= offset. This is // a loop because WarpBuilder can skip unreachable bytecode ops. while (opSnapshotIter_ && opSnapshotIter_->offset() < offset) { opSnapshotIter_ = opSnapshotIter_->getNext(); } if (!opSnapshotIter_ || opSnapshotIter_->offset() != offset || opSnapshotIter_->kind() != kind) { return nullptr; } return opSnapshotIter_; } void WarpBuilder::initBlock(MBasicBlock* block) { graph().addBlock(block); block->setLoopDepth(loopDepth()); current = block; } bool WarpBuilder::startNewBlock(MBasicBlock* predecessor, BytecodeLocation loc, size_t numToPop) { MBasicBlock* block = MBasicBlock::NewPopN(graph(), info(), predecessor, newBytecodeSite(loc), MBasicBlock::NORMAL, numToPop); if (!block) { return false; } initBlock(block); return true; } bool WarpBuilder::startNewEntryBlock(size_t stackDepth, BytecodeLocation loc) { MBasicBlock* block = MBasicBlock::New(graph(), stackDepth, info(), /* maybePred = */ nullptr, newBytecodeSite(loc), MBasicBlock::NORMAL); if (!block) { return false; } initBlock(block); return true; } bool WarpBuilder::startNewLoopHeaderBlock(BytecodeLocation loopHead) { MBasicBlock* header = MBasicBlock::NewPendingLoopHeader( graph(), info(), current, newBytecodeSite(loopHead)); if (!header) { return false; } initBlock(header); return loopStack_.emplaceBack(header); } bool WarpBuilder::startNewOsrPreHeaderBlock(BytecodeLocation loopHead) { MOZ_ASSERT(loopHead.is(JSOp::LoopHead)); MOZ_ASSERT(loopHead.toRawBytecode() == info().osrPc()); // Create two blocks: // * The OSR entry block. This is always the graph's second block and has no // predecessors. This is the entry point for OSR from the Baseline JIT. // * The OSR preheader block. This has two predecessors: the OSR entry block // and the current block. MBasicBlock* pred = current; // Create the OSR entry block. if (!startNewEntryBlock(pred->stackDepth(), loopHead)) { return false; } MBasicBlock* osrBlock = current; graph().setOsrBlock(osrBlock); graph().moveBlockAfter(*graph().begin(), osrBlock); MOsrEntry* entry = MOsrEntry::New(alloc()); osrBlock->add(entry); // Initialize environment chain. { uint32_t slot = info().environmentChainSlot(); MInstruction* envv; if (usesEnvironmentChain()) { envv = MOsrEnvironmentChain::New(alloc(), entry); } else { // Use an undefined value if the script does not need its environment // chain, to match the main entry point. envv = MConstant::New(alloc(), UndefinedValue()); } osrBlock->add(envv); osrBlock->initSlot(slot, envv); } // Initialize return value. { MInstruction* returnValue; if (!script_->noScriptRval()) { returnValue = MOsrReturnValue::New(alloc(), entry); } else { returnValue = MConstant::New(alloc(), UndefinedValue()); } osrBlock->add(returnValue); osrBlock->initSlot(info().returnValueSlot(), returnValue); } // Initialize arguments object. MInstruction* argsObj = nullptr; if (info().needsArgsObj()) { argsObj = MOsrArgumentsObject::New(alloc(), entry); osrBlock->add(argsObj); osrBlock->initSlot(info().argsObjSlot(), argsObj); } if (info().hasFunMaybeLazy()) { // Initialize |this| parameter. MParameter* thisv = MParameter::New(alloc(), MParameter::THIS_SLOT); osrBlock->add(thisv); osrBlock->initSlot(info().thisSlot(), thisv); // Initialize arguments. There are three cases: // // 1) There's no ArgumentsObject or it doesn't alias formals. In this case // we can just use the frame's argument slot. // 2) The ArgumentsObject aliases formals and the argument is stored in the // CallObject. Use |undefined| because we can't load from the arguments // object and code will use the CallObject anyway. // 3) The ArgumentsObject aliases formals and the argument isn't stored in // the CallObject. We have to load it from the ArgumentsObject. for (uint32_t i = 0; i < info().nargs(); i++) { uint32_t slot = info().argSlotUnchecked(i); MInstruction* osrv; if (!info().argsObjAliasesFormals()) { osrv = MParameter::New(alloc().fallible(), i); } else if (script_->formalIsAliased(i)) { osrv = MConstant::New(alloc().fallible(), UndefinedValue()); } else { osrv = MGetArgumentsObjectArg::New(alloc().fallible(), argsObj, i); } if (!osrv) { return false; } current->add(osrv); current->initSlot(slot, osrv); } } // Initialize locals. uint32_t nlocals = info().nlocals(); for (uint32_t i = 0; i < nlocals; i++) { uint32_t slot = info().localSlot(i); ptrdiff_t offset = BaselineFrame::reverseOffsetOfLocal(i); MOsrValue* osrv = MOsrValue::New(alloc().fallible(), entry, offset); if (!osrv) { return false; } current->add(osrv); current->initSlot(slot, osrv); } // Initialize expression stack slots. uint32_t numStackSlots = current->stackDepth() - info().firstStackSlot(); for (uint32_t i = 0; i < numStackSlots; i++) { uint32_t slot = info().stackSlot(i); ptrdiff_t offset = BaselineFrame::reverseOffsetOfLocal(nlocals + i); MOsrValue* osrv = MOsrValue::New(alloc().fallible(), entry, offset); if (!osrv) { return false; } current->add(osrv); current->initSlot(slot, osrv); } MStart* start = MStart::New(alloc()); current->add(start); // Note: phi specialization can add type guard instructions to the OSR entry // block if needed. See TypeAnalyzer::shouldSpecializeOsrPhis. // Create the preheader block, with the predecessor block and OSR block as // predecessors. if (!startNewBlock(pred, loopHead)) { return false; } pred->end(MGoto::New(alloc(), current)); osrBlock->end(MGoto::New(alloc(), current)); if (!current->addPredecessor(alloc(), osrBlock)) { return false; } return true; } bool WarpBuilder::addPendingEdge(BytecodeLocation target, MBasicBlock* block, uint32_t successor, uint32_t numToPop) { MOZ_ASSERT(successor < block->lastIns()->numSuccessors()); MOZ_ASSERT(numToPop <= block->stackDepth()); jsbytecode* targetPC = target.toRawBytecode(); PendingEdgesMap::AddPtr p = pendingEdges_.lookupForAdd(targetPC); if (p) { return p->value().emplaceBack(block, successor, numToPop); } PendingEdges edges; static_assert(PendingEdges::InlineLength >= 1, "Appending one element should be infallible"); MOZ_ALWAYS_TRUE(edges.emplaceBack(block, successor, numToPop)); return pendingEdges_.add(p, targetPC, std::move(edges)); } bool WarpBuilder::build() { if (!buildPrologue()) { return false; } if (!buildBody()) { return false; } if (!MPhi::markIteratorPhis(*iterators())) { return false; } MOZ_ASSERT_IF(info().osrPc(), graph().osrBlock()); MOZ_ASSERT(loopStack_.empty()); MOZ_ASSERT(loopDepth() == 0); return true; } bool WarpBuilder::buildInline() { if (!buildInlinePrologue()) { return false; } if (!buildBody()) { return false; } MOZ_ASSERT(loopStack_.empty()); return true; } MInstruction* WarpBuilder::buildNamedLambdaEnv(MDefinition* callee, MDefinition* env, NamedLambdaObject* templateObj) { MOZ_ASSERT(templateObj->numDynamicSlots() == 0); MInstruction* namedLambda = MNewNamedLambdaObject::New(alloc(), templateObj); current->add(namedLambda); #ifdef DEBUG // Assert in debug mode we can elide the post write barriers. current->add(MAssertCanElidePostWriteBarrier::New(alloc(), namedLambda, env)); current->add( MAssertCanElidePostWriteBarrier::New(alloc(), namedLambda, callee)); #endif // Initialize the object's reserved slots. No post barrier is needed here: // the object will be allocated in the nursery if possible, and if the // tenured heap is used instead, a minor collection will have been performed // that moved env/callee to the tenured heap. size_t enclosingSlot = NamedLambdaObject::enclosingEnvironmentSlot(); size_t lambdaSlot = NamedLambdaObject::lambdaSlot(); current->add(MStoreFixedSlot::NewUnbarriered(alloc(), namedLambda, enclosingSlot, env)); current->add(MStoreFixedSlot::NewUnbarriered(alloc(), namedLambda, lambdaSlot, callee)); return namedLambda; } MInstruction* WarpBuilder::buildCallObject(MDefinition* callee, MDefinition* env, CallObject* templateObj) { MConstant* templateCst = constant(ObjectValue(*templateObj)); MNewCallObject* callObj = MNewCallObject::New(alloc(), templateCst); current->add(callObj); #ifdef DEBUG // Assert in debug mode we can elide the post write barriers. current->add(MAssertCanElidePostWriteBarrier::New(alloc(), callObj, env)); current->add(MAssertCanElidePostWriteBarrier::New(alloc(), callObj, callee)); #endif // Initialize the object's reserved slots. No post barrier is needed here, // for the same reason as in buildNamedLambdaEnv. size_t enclosingSlot = CallObject::enclosingEnvironmentSlot(); size_t calleeSlot = CallObject::calleeSlot(); current->add( MStoreFixedSlot::NewUnbarriered(alloc(), callObj, enclosingSlot, env)); current->add( MStoreFixedSlot::NewUnbarriered(alloc(), callObj, calleeSlot, callee)); return callObj; } bool WarpBuilder::buildEnvironmentChain() { const WarpEnvironment& env = scriptSnapshot()->environment(); if (env.is()) { return true; } MInstruction* envDef = env.match( [](const NoEnvironment&) -> MInstruction* { MOZ_CRASH("Already handled"); }, [this](JSObject* obj) -> MInstruction* { return constant(ObjectValue(*obj)); }, [this](const FunctionEnvironment& env) -> MInstruction* { MDefinition* callee = getCallee(); MInstruction* envDef = MFunctionEnvironment::New(alloc(), callee); current->add(envDef); if (NamedLambdaObject* obj = env.namedLambdaTemplate) { envDef = buildNamedLambdaEnv(callee, envDef, obj); } if (CallObject* obj = env.callObjectTemplate) { envDef = buildCallObject(callee, envDef, obj); if (!envDef) { return nullptr; } } return envDef; }); if (!envDef) { return false; } // Update the environment slot from UndefinedValue only after the initial // environment is created so that bailout doesn't see a partial environment. // See: |BaselineStackBuilder::buildBaselineFrame| current->setEnvironmentChain(envDef); return true; } bool WarpBuilder::buildPrologue() { BytecodeLocation startLoc(script_, script_->code()); if (!startNewEntryBlock(info().firstStackSlot(), startLoc)) { return false; } if (info().hasFunMaybeLazy()) { // Initialize |this|. MParameter* param = MParameter::New(alloc(), MParameter::THIS_SLOT); current->add(param); current->initSlot(info().thisSlot(), param); // Initialize arguments. for (uint32_t i = 0; i < info().nargs(); i++) { MParameter* param = MParameter::New(alloc().fallible(), i); if (!param) { return false; } current->add(param); current->initSlot(info().argSlotUnchecked(i), param); } } MConstant* undef = constant(UndefinedValue()); // Initialize local slots. for (uint32_t i = 0; i < info().nlocals(); i++) { current->initSlot(info().localSlot(i), undef); } // Initialize the environment chain, return value, and arguments object slots. current->initSlot(info().environmentChainSlot(), undef); current->initSlot(info().returnValueSlot(), undef); if (info().needsArgsObj()) { current->initSlot(info().argsObjSlot(), undef); } current->add(MStart::New(alloc())); // Guard against over-recursion. MCheckOverRecursed* check = MCheckOverRecursed::New(alloc()); current->add(check); if (!buildEnvironmentChain()) { return false; } #ifdef JS_CACHEIR_SPEW if (snapshot().needsFinalWarmUpCount()) { MIncrementWarmUpCounter* ins = MIncrementWarmUpCounter::New(alloc(), script_); current->add(ins); } #endif return true; } bool WarpBuilder::buildInlinePrologue() { // Generate entry block. BytecodeLocation startLoc(script_, script_->code()); if (!startNewEntryBlock(info().firstStackSlot(), startLoc)) { return false; } current->setCallerResumePoint(callerResumePoint()); // Connect the entry block to the last block in the caller's graph. MBasicBlock* pred = callerBuilder()->current; MOZ_ASSERT(pred == callerResumePoint()->block()); pred->end(MGoto::New(alloc(), current)); if (!current->addPredecessorWithoutPhis(pred)) { return false; } MConstant* undef = constant(UndefinedValue()); // Initialize env chain slot to Undefined. It's set later by // |buildEnvironmentChain|. current->initSlot(info().environmentChainSlot(), undef); // Initialize |return value| slot. current->initSlot(info().returnValueSlot(), undef); // Initialize |arguments| slot if needed. if (info().needsArgsObj()) { current->initSlot(info().argsObjSlot(), undef); } // Initialize |this| slot. current->initSlot(info().thisSlot(), inlineCallInfo()->thisArg()); uint32_t callerArgs = inlineCallInfo()->argc(); uint32_t actualArgs = info().nargs(); uint32_t passedArgs = std::min(callerArgs, actualArgs); // Initialize actually set arguments. for (uint32_t i = 0; i < passedArgs; i++) { MDefinition* arg = inlineCallInfo()->getArg(i); current->initSlot(info().argSlotUnchecked(i), arg); } // Pass undefined for missing arguments. for (uint32_t i = passedArgs; i < actualArgs; i++) { current->initSlot(info().argSlotUnchecked(i), undef); } // Initialize local slots. for (uint32_t i = 0; i < info().nlocals(); i++) { current->initSlot(info().localSlot(i), undef); } MOZ_ASSERT(current->entryResumePoint()->stackDepth() == info().totalSlots()); if (!buildEnvironmentChain()) { return false; } return true; } #ifdef DEBUG // In debug builds, after compiling a bytecode op, this class is used to check // that all values popped by this opcode either: // // (1) Have the ImplicitlyUsed flag set on them. // (2) Have more uses than before compiling this op (the value is // used as operand of a new MIR instruction). // // This is used to catch problems where WarpBuilder pops a value without // adding any SSA uses and doesn't call setImplicitlyUsedUnchecked on it. class MOZ_RAII WarpPoppedValueUseChecker { Vector popped_; Vector poppedUses_; MBasicBlock* current_; BytecodeLocation loc_; public: WarpPoppedValueUseChecker(MBasicBlock* current, BytecodeLocation loc) : current_(current), loc_(loc) {} [[nodiscard]] bool init() { // Don't require SSA uses for values popped by these ops. switch (loc_.getOp()) { case JSOp::Pop: case JSOp::PopN: case JSOp::DupAt: case JSOp::Dup: case JSOp::Dup2: case JSOp::Pick: case JSOp::Unpick: case JSOp::Swap: case JSOp::SetArg: case JSOp::SetLocal: case JSOp::InitLexical: case JSOp::SetRval: case JSOp::Void: // Basic stack/local/argument management opcodes. return true; case JSOp::Case: case JSOp::Default: // These ops have to pop the switch value when branching but don't // actually use it. return true; default: break; } unsigned nuses = loc_.useCount(); for (unsigned i = 0; i < nuses; i++) { MDefinition* def = current_->peek(-int32_t(i + 1)); if (!popped_.append(def) || !poppedUses_.append(def->defUseCount())) { return false; } } return true; } void checkAfterOp() { for (size_t i = 0; i < popped_.length(); i++) { // First value popped by JSOp::EndIter is not used at all, it's similar // to JSOp::Pop above. if (loc_.is(JSOp::EndIter) && i == 0) { continue; } MOZ_ASSERT(popped_[i]->isImplicitlyUsed() || popped_[i]->defUseCount() > poppedUses_[i]); } } }; #endif bool WarpBuilder::buildBody() { for (BytecodeLocation loc : AllBytecodesIterable(script_)) { if (mirGen().shouldCancel("WarpBuilder (opcode loop)")) { return false; } // Skip unreachable ops (for example code after a 'return' or 'throw') until // we get to the next jump target. if (hasTerminatedBlock()) { // Finish any "broken" loops with an unreachable backedge. For example: // // do { // ... // return; // ... // } while (x); // // This loop never actually loops. if (loc.isBackedge() && !loopStack_.empty()) { BytecodeLocation loopHead(script_, loopStack_.back().header()->pc()); if (loc.isBackedgeForLoophead(loopHead)) { decLoopDepth(); loopStack_.popBack(); } } if (!loc.isJumpTarget()) { continue; } } if (!alloc().ensureBallast()) { return false; } #ifdef DEBUG WarpPoppedValueUseChecker useChecker(current, loc); if (!useChecker.init()) { return false; } #endif JSOp op = loc.getOp(); #define BUILD_OP(OP, ...) \ case JSOp::OP: \ if (MOZ_UNLIKELY(!this->build_##OP(loc))) { \ return false; \ } \ break; switch (op) { FOR_EACH_OPCODE(BUILD_OP) } #undef BUILD_OP #ifdef DEBUG useChecker.checkAfterOp(); #endif } return true; } #define DEF_OP(OP) \ bool WarpBuilder::build_##OP(BytecodeLocation) { \ MOZ_CRASH("Unsupported op"); \ } WARP_UNSUPPORTED_OPCODE_LIST(DEF_OP) #undef DEF_OP bool WarpBuilder::build_Nop(BytecodeLocation) { return true; } bool WarpBuilder::build_NopDestructuring(BytecodeLocation) { return true; } bool WarpBuilder::build_TryDestructuring(BytecodeLocation) { // Set the hasTryBlock flag to turn off optimizations that eliminate dead // resume points operands because the exception handler code for // TryNoteKind::Destructuring is effectively a (specialized) catch-block. graph().setHasTryBlock(); return true; } bool WarpBuilder::build_Lineno(BytecodeLocation) { return true; } bool WarpBuilder::build_DebugLeaveLexicalEnv(BytecodeLocation) { return true; } bool WarpBuilder::build_Undefined(BytecodeLocation) { pushConstant(UndefinedValue()); return true; } bool WarpBuilder::build_Void(BytecodeLocation) { current->pop(); pushConstant(UndefinedValue()); return true; } bool WarpBuilder::build_Null(BytecodeLocation) { pushConstant(NullValue()); return true; } bool WarpBuilder::build_Hole(BytecodeLocation) { pushConstant(MagicValue(JS_ELEMENTS_HOLE)); return true; } bool WarpBuilder::build_Uninitialized(BytecodeLocation) { pushConstant(MagicValue(JS_UNINITIALIZED_LEXICAL)); return true; } bool WarpBuilder::build_IsConstructing(BytecodeLocation) { pushConstant(MagicValue(JS_IS_CONSTRUCTING)); return true; } bool WarpBuilder::build_False(BytecodeLocation) { pushConstant(BooleanValue(false)); return true; } bool WarpBuilder::build_True(BytecodeLocation) { pushConstant(BooleanValue(true)); return true; } bool WarpBuilder::build_Pop(BytecodeLocation) { current->pop(); return true; } bool WarpBuilder::build_PopN(BytecodeLocation loc) { for (uint32_t i = 0, n = loc.getPopCount(); i < n; i++) { current->pop(); } return true; } bool WarpBuilder::build_Dup(BytecodeLocation) { current->pushSlot(current->stackDepth() - 1); return true; } bool WarpBuilder::build_Dup2(BytecodeLocation) { uint32_t lhsSlot = current->stackDepth() - 2; uint32_t rhsSlot = current->stackDepth() - 1; current->pushSlot(lhsSlot); current->pushSlot(rhsSlot); return true; } bool WarpBuilder::build_DupAt(BytecodeLocation loc) { current->pushSlot(current->stackDepth() - 1 - loc.getDupAtIndex()); return true; } bool WarpBuilder::build_Swap(BytecodeLocation) { current->swapAt(-1); return true; } bool WarpBuilder::build_Pick(BytecodeLocation loc) { int32_t depth = -int32_t(loc.getPickDepth()); current->pick(depth); return true; } bool WarpBuilder::build_Unpick(BytecodeLocation loc) { int32_t depth = -int32_t(loc.getUnpickDepth()); current->unpick(depth); return true; } bool WarpBuilder::build_Zero(BytecodeLocation) { pushConstant(Int32Value(0)); return true; } bool WarpBuilder::build_One(BytecodeLocation) { pushConstant(Int32Value(1)); return true; } bool WarpBuilder::build_Int8(BytecodeLocation loc) { pushConstant(Int32Value(loc.getInt8())); return true; } bool WarpBuilder::build_Uint16(BytecodeLocation loc) { pushConstant(Int32Value(loc.getUint16())); return true; } bool WarpBuilder::build_Uint24(BytecodeLocation loc) { pushConstant(Int32Value(loc.getUint24())); return true; } bool WarpBuilder::build_Int32(BytecodeLocation loc) { pushConstant(Int32Value(loc.getInt32())); return true; } bool WarpBuilder::build_Double(BytecodeLocation loc) { pushConstant(loc.getInlineValue()); return true; } bool WarpBuilder::build_BigInt(BytecodeLocation loc) { BigInt* bi = loc.getBigInt(script_); pushConstant(BigIntValue(bi)); return true; } bool WarpBuilder::build_String(BytecodeLocation loc) { JSString* str = loc.getString(script_); pushConstant(StringValue(str)); return true; } bool WarpBuilder::build_Symbol(BytecodeLocation loc) { uint32_t which = loc.getSymbolIndex(); JS::Symbol* sym = mirGen().runtime->wellKnownSymbols().get(which); pushConstant(SymbolValue(sym)); return true; } bool WarpBuilder::build_RegExp(BytecodeLocation loc) { RegExpObject* reObj = loc.getRegExp(script_); auto* snapshot = getOpSnapshot(loc); MRegExp* regexp = MRegExp::New(alloc(), reObj, snapshot->hasShared()); current->add(regexp); current->push(regexp); return true; } bool WarpBuilder::build_Return(BytecodeLocation) { MDefinition* def = current->pop(); MReturn* ret = MReturn::New(alloc(), def); current->end(ret); if (!graph().addReturn(current)) { return false; } setTerminatedBlock(); return true; } bool WarpBuilder::build_RetRval(BytecodeLocation) { MDefinition* rval; if (script_->noScriptRval()) { rval = constant(UndefinedValue()); } else { rval = current->getSlot(info().returnValueSlot()); } MReturn* ret = MReturn::New(alloc(), rval); current->end(ret); if (!graph().addReturn(current)) { return false; } setTerminatedBlock(); return true; } bool WarpBuilder::build_SetRval(BytecodeLocation) { MOZ_ASSERT(!script_->noScriptRval()); MDefinition* rval = current->pop(); current->setSlot(info().returnValueSlot(), rval); return true; } bool WarpBuilder::build_GetRval(BytecodeLocation) { MOZ_ASSERT(!script_->noScriptRval()); MDefinition* rval = current->getSlot(info().returnValueSlot()); current->push(rval); return true; } bool WarpBuilder::build_GetLocal(BytecodeLocation loc) { current->pushLocal(loc.local()); return true; } bool WarpBuilder::build_SetLocal(BytecodeLocation loc) { current->setLocal(loc.local()); return true; } bool WarpBuilder::build_InitLexical(BytecodeLocation loc) { current->setLocal(loc.local()); return true; } bool WarpBuilder::build_GetArg(BytecodeLocation loc) { uint32_t arg = loc.arg(); if (info().argsObjAliasesFormals()) { MDefinition* argsObj = current->argumentsObject(); auto* getArg = MGetArgumentsObjectArg::New(alloc(), argsObj, arg); current->add(getArg); current->push(getArg); } else { current->pushArg(arg); } return true; } bool WarpBuilder::build_GetFrameArg(BytecodeLocation loc) { current->pushArgUnchecked(loc.arg()); return true; } bool WarpBuilder::build_SetArg(BytecodeLocation loc) { uint32_t arg = loc.arg(); MDefinition* val = current->peek(-1); if (!info().argsObjAliasesFormals()) { // Either |arguments| is never referenced within this function, or // it doesn't map to the actual arguments values. Either way, we // don't need to worry about synchronizing the argument values // when writing to them. current->setArg(arg); return true; } // If an arguments object is in use, and it aliases formals, then all SetArgs // must go through the arguments object. MDefinition* argsObj = current->argumentsObject(); current->add(MPostWriteBarrier::New(alloc(), argsObj, val)); auto* ins = MSetArgumentsObjectArg::New(alloc(), argsObj, val, arg); current->add(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_ArgumentsLength(BytecodeLocation) { if (inlineCallInfo()) { pushConstant(Int32Value(inlineCallInfo()->argc())); } else { auto* argsLength = MArgumentsLength::New(alloc()); current->add(argsLength); current->push(argsLength); } return true; } bool WarpBuilder::build_GetActualArg(BytecodeLocation) { MDefinition* index = current->pop(); MInstruction* arg; if (inlineCallInfo()) { arg = MGetInlinedArgument::New(alloc(), index, *inlineCallInfo()); } else { arg = MGetFrameArgument::New(alloc(), index); } current->add(arg); current->push(arg); return true; } bool WarpBuilder::build_ToNumeric(BytecodeLocation loc) { return buildUnaryOp(loc); } bool WarpBuilder::buildUnaryOp(BytecodeLocation loc) { MDefinition* value = current->pop(); return buildIC(loc, CacheKind::UnaryArith, {value}); } bool WarpBuilder::build_Inc(BytecodeLocation loc) { return buildUnaryOp(loc); } bool WarpBuilder::build_Dec(BytecodeLocation loc) { return buildUnaryOp(loc); } bool WarpBuilder::build_Pos(BytecodeLocation loc) { return buildUnaryOp(loc); } bool WarpBuilder::build_Neg(BytecodeLocation loc) { return buildUnaryOp(loc); } bool WarpBuilder::build_BitNot(BytecodeLocation loc) { return buildUnaryOp(loc); } bool WarpBuilder::buildBinaryOp(BytecodeLocation loc) { MDefinition* right = current->pop(); MDefinition* left = current->pop(); return buildIC(loc, CacheKind::BinaryArith, {left, right}); } bool WarpBuilder::build_Add(BytecodeLocation loc) { return buildBinaryOp(loc); } bool WarpBuilder::build_Sub(BytecodeLocation loc) { return buildBinaryOp(loc); } bool WarpBuilder::build_Mul(BytecodeLocation loc) { return buildBinaryOp(loc); } bool WarpBuilder::build_Div(BytecodeLocation loc) { return buildBinaryOp(loc); } bool WarpBuilder::build_Mod(BytecodeLocation loc) { return buildBinaryOp(loc); } bool WarpBuilder::build_Pow(BytecodeLocation loc) { return buildBinaryOp(loc); } bool WarpBuilder::build_BitAnd(BytecodeLocation loc) { return buildBinaryOp(loc); } bool WarpBuilder::build_BitOr(BytecodeLocation loc) { return buildBinaryOp(loc); } bool WarpBuilder::build_BitXor(BytecodeLocation loc) { return buildBinaryOp(loc); } bool WarpBuilder::build_Lsh(BytecodeLocation loc) { return buildBinaryOp(loc); } bool WarpBuilder::build_Rsh(BytecodeLocation loc) { return buildBinaryOp(loc); } bool WarpBuilder::build_Ursh(BytecodeLocation loc) { return buildBinaryOp(loc); } bool WarpBuilder::buildCompareOp(BytecodeLocation loc) { MDefinition* right = current->pop(); MDefinition* left = current->pop(); return buildIC(loc, CacheKind::Compare, {left, right}); } bool WarpBuilder::build_Eq(BytecodeLocation loc) { return buildCompareOp(loc); } bool WarpBuilder::build_Ne(BytecodeLocation loc) { return buildCompareOp(loc); } bool WarpBuilder::build_Lt(BytecodeLocation loc) { return buildCompareOp(loc); } bool WarpBuilder::build_Le(BytecodeLocation loc) { return buildCompareOp(loc); } bool WarpBuilder::build_Gt(BytecodeLocation loc) { return buildCompareOp(loc); } bool WarpBuilder::build_Ge(BytecodeLocation loc) { return buildCompareOp(loc); } bool WarpBuilder::build_StrictEq(BytecodeLocation loc) { return buildCompareOp(loc); } bool WarpBuilder::build_StrictNe(BytecodeLocation loc) { return buildCompareOp(loc); } // Returns true iff the MTest added for |op| has a true-target corresponding // with the join point in the bytecode. static bool TestTrueTargetIsJoinPoint(JSOp op) { switch (op) { case JSOp::JumpIfTrue: case JSOp::Or: case JSOp::Case: return true; case JSOp::JumpIfFalse: case JSOp::And: case JSOp::Coalesce: return false; default: MOZ_CRASH("Unexpected op"); } } bool WarpBuilder::build_JumpTarget(BytecodeLocation loc) { PendingEdgesMap::Ptr p = pendingEdges_.lookup(loc.toRawBytecode()); if (!p) { // No (reachable) jumps so this is just a no-op. return true; } PendingEdges edges(std::move(p->value())); pendingEdges_.remove(p); MOZ_ASSERT(!edges.empty()); // Create join block if there's fall-through from the previous bytecode op. if (!hasTerminatedBlock()) { MBasicBlock* pred = current; if (!startNewBlock(pred, loc)) { return false; } pred->end(MGoto::New(alloc(), current)); } for (const PendingEdge& edge : edges) { MBasicBlock* source = edge.block(); uint32_t numToPop = edge.numToPop(); if (hasTerminatedBlock()) { if (!startNewBlock(source, loc, numToPop)) { return false; } } else { MOZ_ASSERT(source->stackDepth() - numToPop == current->stackDepth()); if (!current->addPredecessorPopN(alloc(), source, numToPop)) { return false; } } MOZ_ASSERT(source->lastIns()->isTest() || source->lastIns()->isGoto() || source->lastIns()->isTableSwitch()); source->lastIns()->initSuccessor(edge.successor(), current); } MOZ_ASSERT(!hasTerminatedBlock()); return true; } bool WarpBuilder::addIteratorLoopPhis(BytecodeLocation loopHead) { // When unwinding the stack for a thrown exception, the exception handler must // close live iterators. For ForIn and Destructuring loops, the exception // handler needs access to values on the stack. To prevent them from being // optimized away (and replaced with the JS_OPTIMIZED_OUT MagicValue), we need // to mark the phis (and phis they flow into) as having implicit uses. // See ProcessTryNotes in vm/Interpreter.cpp and CloseLiveIteratorIon in // jit/JitFrames.cpp MOZ_ASSERT(current->stackDepth() >= info().firstStackSlot()); bool emptyStack = current->stackDepth() == info().firstStackSlot(); if (emptyStack) { return true; } jsbytecode* loopHeadPC = loopHead.toRawBytecode(); for (TryNoteIterAllNoGC tni(script_, loopHeadPC); !tni.done(); ++tni) { const TryNote& tn = **tni; // Stop if we reach an outer loop because outer loops were already // processed when we visited their loop headers. if (tn.isLoop()) { BytecodeLocation tnStart = script_->offsetToLocation(tn.start); if (tnStart != loopHead) { MOZ_ASSERT(tnStart.is(JSOp::LoopHead)); MOZ_ASSERT(tnStart < loopHead); return true; } } switch (tn.kind()) { case TryNoteKind::Destructuring: case TryNoteKind::ForIn: { // For for-in loops we add the iterator object to iterators(). For // destructuring loops we add the "done" value that's on top of the // stack and used in the exception handler. MOZ_ASSERT(tn.stackDepth >= 1); uint32_t slot = info().stackSlot(tn.stackDepth - 1); MPhi* phi = current->getSlot(slot)->toPhi(); if (!iterators()->append(phi)) { return false; } break; } case TryNoteKind::Loop: case TryNoteKind::ForOf: // Regular loops do not have iterators to close. ForOf loops handle // unwinding using catch blocks. break; default: break; } } return true; } bool WarpBuilder::build_LoopHead(BytecodeLocation loc) { // All loops have the following bytecode structure: // // LoopHead // ... // JumpIfTrue/Goto to LoopHead if (hasTerminatedBlock()) { // The whole loop is unreachable. return true; } // Handle OSR from Baseline JIT code. if (loc.toRawBytecode() == info().osrPc()) { if (!startNewOsrPreHeaderBlock(loc)) { return false; } } incLoopDepth(); MBasicBlock* pred = current; if (!startNewLoopHeaderBlock(loc)) { return false; } pred->end(MGoto::New(alloc(), current)); if (!addIteratorLoopPhis(loc)) { return false; } MInterruptCheck* check = MInterruptCheck::New(alloc()); current->add(check); #ifdef JS_CACHEIR_SPEW if (snapshot().needsFinalWarmUpCount()) { MIncrementWarmUpCounter* ins = MIncrementWarmUpCounter::New(alloc(), script_); current->add(ins); } #endif return true; } bool WarpBuilder::buildTestOp(BytecodeLocation loc) { MDefinition* originalValue = current->peek(-1); if (auto* cacheIRSnapshot = getOpSnapshot(loc)) { // If we have CacheIR, we can use it to refine the input. Note that // the transpiler doesn't generate any control instructions. Instead, // we fall through and generate them below. MDefinition* value = current->pop(); if (!TranspileCacheIRToMIR(this, loc, cacheIRSnapshot, {value})) { return false; } } if (loc.isBackedge()) { return buildTestBackedge(loc); } JSOp op = loc.getOp(); BytecodeLocation target1 = loc.next(); BytecodeLocation target2 = loc.getJumpTarget(); if (TestTrueTargetIsJoinPoint(op)) { std::swap(target1, target2); } MDefinition* value = current->pop(); // JSOp::And and JSOp::Or leave the top stack value unchanged. The // top stack value may have been converted to bool by a transpiled // ToBool IC, so we push the original value. bool mustKeepCondition = (op == JSOp::And || op == JSOp::Or); if (mustKeepCondition) { current->push(originalValue); } // If this op always branches to the same location we treat this as a // JSOp::Goto. if (target1 == target2) { value->setImplicitlyUsedUnchecked(); return buildForwardGoto(target1); } MTest* test = MTest::New(alloc(), value, /* ifTrue = */ nullptr, /* ifFalse = */ nullptr); current->end(test); // JSOp::Case must pop a second value on the true-branch (the input to the // switch-statement). uint32_t numToPop = (loc.getOp() == JSOp::Case) ? 1 : 0; if (!addPendingEdge(target1, current, MTest::TrueBranchIndex, numToPop)) { return false; } if (!addPendingEdge(target2, current, MTest::FalseBranchIndex)) { return false; } if (const auto* typesSnapshot = getOpSnapshot(loc)) { test->setObservedTypes(typesSnapshot->list()); } setTerminatedBlock(); return true; } bool WarpBuilder::buildTestBackedge(BytecodeLocation loc) { MOZ_ASSERT(loc.is(JSOp::JumpIfTrue)); MOZ_ASSERT(loopDepth() > 0); MDefinition* value = current->pop(); BytecodeLocation loopHead = loc.getJumpTarget(); MOZ_ASSERT(loopHead.is(JSOp::LoopHead)); BytecodeLocation successor = loc.next(); // We can finish the loop now. Use the loophead pc instead of the current pc // because the stack depth at the start of that op matches the current stack // depth (after popping our operand). MBasicBlock* pred = current; if (!startNewBlock(current, loopHead)) { return false; } MTest* test = MTest::New(alloc(), value, /* ifTrue = */ current, /* ifFalse = */ nullptr); pred->end(test); if (const auto* typesSnapshot = getOpSnapshot(loc)) { test->setObservedTypes(typesSnapshot->list()); } if (!addPendingEdge(successor, pred, MTest::FalseBranchIndex)) { return false; } return buildBackedge(); } bool WarpBuilder::build_JumpIfFalse(BytecodeLocation loc) { return buildTestOp(loc); } bool WarpBuilder::build_JumpIfTrue(BytecodeLocation loc) { return buildTestOp(loc); } bool WarpBuilder::build_And(BytecodeLocation loc) { return buildTestOp(loc); } bool WarpBuilder::build_Or(BytecodeLocation loc) { return buildTestOp(loc); } bool WarpBuilder::build_Case(BytecodeLocation loc) { return buildTestOp(loc); } bool WarpBuilder::build_Default(BytecodeLocation loc) { current->pop(); return buildForwardGoto(loc.getJumpTarget()); } bool WarpBuilder::build_Coalesce(BytecodeLocation loc) { BytecodeLocation target1 = loc.next(); BytecodeLocation target2 = loc.getJumpTarget(); MOZ_ASSERT(target2 > target1); MDefinition* value = current->peek(-1); MInstruction* isNullOrUndefined = MIsNullOrUndefined::New(alloc(), value); current->add(isNullOrUndefined); current->end(MTest::New(alloc(), isNullOrUndefined, /* ifTrue = */ nullptr, /* ifFalse = */ nullptr)); if (!addPendingEdge(target1, current, MTest::TrueBranchIndex)) { return false; } if (!addPendingEdge(target2, current, MTest::FalseBranchIndex)) { return false; } setTerminatedBlock(); return true; } bool WarpBuilder::buildBackedge() { decLoopDepth(); MBasicBlock* header = loopStack_.popCopy().header(); current->end(MGoto::New(alloc(), header)); if (!header->setBackedge(current)) { return false; } setTerminatedBlock(); return true; } bool WarpBuilder::buildForwardGoto(BytecodeLocation target) { current->end(MGoto::New(alloc(), nullptr)); if (!addPendingEdge(target, current, MGoto::TargetIndex)) { return false; } setTerminatedBlock(); return true; } bool WarpBuilder::build_Goto(BytecodeLocation loc) { if (loc.isBackedge()) { return buildBackedge(); } return buildForwardGoto(loc.getJumpTarget()); } bool WarpBuilder::build_IsNullOrUndefined(BytecodeLocation loc) { MDefinition* value = current->peek(-1); auto* isNullOrUndef = MIsNullOrUndefined::New(alloc(), value); current->add(isNullOrUndef); current->push(isNullOrUndef); return true; } bool WarpBuilder::build_DebugCheckSelfHosted(BytecodeLocation loc) { #ifdef DEBUG MDefinition* val = current->pop(); MDebugCheckSelfHosted* check = MDebugCheckSelfHosted::New(alloc(), val); current->add(check); current->push(check); if (!resumeAfter(check, loc)) { return false; } #endif return true; } bool WarpBuilder::build_DynamicImport(BytecodeLocation loc) { MDefinition* options = current->pop(); MDefinition* specifier = current->pop(); MDynamicImport* ins = MDynamicImport::New(alloc(), specifier, options); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_Not(BytecodeLocation loc) { if (auto* cacheIRSnapshot = getOpSnapshot(loc)) { // If we have CacheIR, we can use it to refine the input before // emitting the MNot. MDefinition* value = current->pop(); if (!TranspileCacheIRToMIR(this, loc, cacheIRSnapshot, {value})) { return false; } } MDefinition* value = current->pop(); MNot* ins = MNot::New(alloc(), value); current->add(ins); current->push(ins); if (const auto* typesSnapshot = getOpSnapshot(loc)) { ins->setObservedTypes(typesSnapshot->list()); } return true; } bool WarpBuilder::build_ToString(BytecodeLocation loc) { MDefinition* value = current->pop(); if (value->type() == MIRType::String) { value->setImplicitlyUsedUnchecked(); current->push(value); return true; } MToString* ins = MToString::New(alloc(), value, MToString::SideEffectHandling::Supported); current->add(ins); current->push(ins); if (ins->isEffectful()) { return resumeAfter(ins, loc); } return true; } bool WarpBuilder::usesEnvironmentChain() const { return script_->jitScript()->usesEnvironmentChain(); } bool WarpBuilder::build_GlobalOrEvalDeclInstantiation(BytecodeLocation loc) { MOZ_ASSERT(!script_->isForEval(), "Eval scripts not supported"); auto* redeclCheck = MGlobalDeclInstantiation::New(alloc()); current->add(redeclCheck); return resumeAfter(redeclCheck, loc); } bool WarpBuilder::build_BindVar(BytecodeLocation) { MOZ_ASSERT(usesEnvironmentChain()); MDefinition* env = current->environmentChain(); MCallBindVar* ins = MCallBindVar::New(alloc(), env); current->add(ins); current->push(ins); return true; } bool WarpBuilder::build_MutateProto(BytecodeLocation loc) { MDefinition* value = current->pop(); MDefinition* obj = current->peek(-1); MMutateProto* mutate = MMutateProto::New(alloc(), obj, value); current->add(mutate); return resumeAfter(mutate, loc); } MDefinition* WarpBuilder::getCallee() { if (inlineCallInfo()) { return inlineCallInfo()->callee(); } MInstruction* callee = MCallee::New(alloc()); current->add(callee); return callee; } bool WarpBuilder::build_Callee(BytecodeLocation) { MDefinition* callee = getCallee(); current->push(callee); return true; } bool WarpBuilder::build_ToAsyncIter(BytecodeLocation loc) { MDefinition* nextMethod = current->pop(); MDefinition* iterator = current->pop(); MToAsyncIter* ins = MToAsyncIter::New(alloc(), iterator, nextMethod); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_ToPropertyKey(BytecodeLocation loc) { MDefinition* value = current->pop(); return buildIC(loc, CacheKind::ToPropertyKey, {value}); } bool WarpBuilder::build_Typeof(BytecodeLocation loc) { MDefinition* input = current->pop(); if (const auto* typesSnapshot = getOpSnapshot(loc)) { auto* typeOf = MTypeOf::New(alloc(), input); typeOf->setObservedTypes(typesSnapshot->list()); current->add(typeOf); auto* ins = MTypeOfName::New(alloc(), typeOf); current->add(ins); current->push(ins); return true; } return buildIC(loc, CacheKind::TypeOf, {input}); } bool WarpBuilder::build_TypeofExpr(BytecodeLocation loc) { return build_Typeof(loc); } bool WarpBuilder::build_Arguments(BytecodeLocation loc) { auto* snapshot = getOpSnapshot(loc); MOZ_ASSERT(info().needsArgsObj()); MOZ_ASSERT(snapshot); MOZ_ASSERT(usesEnvironmentChain()); ArgumentsObject* templateObj = snapshot->templateObj(); MDefinition* env = current->environmentChain(); MInstruction* argsObj; if (inlineCallInfo()) { argsObj = MCreateInlinedArgumentsObject::New( alloc(), env, getCallee(), inlineCallInfo()->argv(), templateObj); if (!argsObj) { return false; } } else { argsObj = MCreateArgumentsObject::New(alloc(), env, templateObj); } current->add(argsObj); current->setArgumentsObject(argsObj); current->push(argsObj); return true; } bool WarpBuilder::build_ObjWithProto(BytecodeLocation loc) { MDefinition* proto = current->pop(); MInstruction* ins = MObjectWithProto::New(alloc(), proto); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } MDefinition* WarpBuilder::walkEnvironmentChain(uint32_t numHops) { MDefinition* env = current->environmentChain(); for (uint32_t i = 0; i < numHops; i++) { if (!alloc().ensureBallast()) { return nullptr; } MInstruction* ins = MEnclosingEnvironment::New(alloc(), env); current->add(ins); env = ins; } return env; } bool WarpBuilder::build_GetAliasedVar(BytecodeLocation loc) { EnvironmentCoordinate ec = loc.getEnvironmentCoordinate(); MDefinition* obj = walkEnvironmentChain(ec.hops()); if (!obj) { return false; } MInstruction* load; if (EnvironmentObject::nonExtensibleIsFixedSlot(ec)) { load = MLoadFixedSlot::New(alloc(), obj, ec.slot()); } else { MInstruction* slots = MSlots::New(alloc(), obj); current->add(slots); uint32_t slot = EnvironmentObject::nonExtensibleDynamicSlotIndex(ec); load = MLoadDynamicSlot::New(alloc(), slots, slot); } current->add(load); current->push(load); return true; } bool WarpBuilder::build_SetAliasedVar(BytecodeLocation loc) { EnvironmentCoordinate ec = loc.getEnvironmentCoordinate(); MDefinition* val = current->peek(-1); MDefinition* obj = walkEnvironmentChain(ec.hops()); if (!obj) { return false; } current->add(MPostWriteBarrier::New(alloc(), obj, val)); MInstruction* store; if (EnvironmentObject::nonExtensibleIsFixedSlot(ec)) { store = MStoreFixedSlot::NewBarriered(alloc(), obj, ec.slot(), val); } else { MInstruction* slots = MSlots::New(alloc(), obj); current->add(slots); uint32_t slot = EnvironmentObject::nonExtensibleDynamicSlotIndex(ec); store = MStoreDynamicSlot::NewBarriered(alloc(), slots, slot, val); } current->add(store); return resumeAfter(store, loc); } bool WarpBuilder::build_InitAliasedLexical(BytecodeLocation loc) { return build_SetAliasedVar(loc); } bool WarpBuilder::build_EnvCallee(BytecodeLocation loc) { uint32_t numHops = loc.getEnvCalleeNumHops(); MDefinition* env = walkEnvironmentChain(numHops); if (!env) { return false; } auto* callee = MLoadFixedSlot::New(alloc(), env, CallObject::calleeSlot()); current->add(callee); current->push(callee); return true; } bool WarpBuilder::build_Iter(BytecodeLocation loc) { MDefinition* obj = current->pop(); return buildIC(loc, CacheKind::GetIterator, {obj}); } bool WarpBuilder::build_MoreIter(BytecodeLocation loc) { MDefinition* iter = current->peek(-1); MInstruction* ins = MIteratorMore::New(alloc(), iter); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_EndIter(BytecodeLocation loc) { current->pop(); // Iterator value is not used. MDefinition* iter = current->pop(); MInstruction* ins = MIteratorEnd::New(alloc(), iter); current->add(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_CloseIter(BytecodeLocation loc) { MDefinition* iter = current->pop(); return buildIC(loc, CacheKind::CloseIter, {iter}); } bool WarpBuilder::build_IsNoIter(BytecodeLocation) { MDefinition* def = current->peek(-1); MOZ_ASSERT(def->isIteratorMore()); MInstruction* ins = MIsNoIter::New(alloc(), def); current->add(ins); current->push(ins); return true; } bool WarpBuilder::transpileCall(BytecodeLocation loc, const WarpCacheIR* cacheIRSnapshot, CallInfo* callInfo) { // Synthesize the constant number of arguments for this call op. auto* argc = MConstant::New(alloc(), Int32Value(callInfo->argc())); current->add(argc); return TranspileCacheIRToMIR(this, loc, cacheIRSnapshot, {argc}, callInfo); } void WarpBuilder::buildCreateThis(CallInfo& callInfo) { MOZ_ASSERT(callInfo.constructing()); // Inline the this-object allocation on the caller-side. MDefinition* callee = callInfo.callee(); MDefinition* newTarget = callInfo.getNewTarget(); auto* createThis = MCreateThis::New(alloc(), callee, newTarget); current->add(createThis); callInfo.thisArg()->setImplicitlyUsedUnchecked(); callInfo.setThis(createThis); } bool WarpBuilder::buildCallOp(BytecodeLocation loc) { uint32_t argc = loc.getCallArgc(); JSOp op = loc.getOp(); bool constructing = IsConstructOp(op); bool ignoresReturnValue = (op == JSOp::CallIgnoresRv || loc.resultIsPopped()); CallInfo callInfo(alloc(), constructing, ignoresReturnValue); if (!callInfo.init(current, argc)) { return false; } if (const auto* inliningSnapshot = getOpSnapshot(loc)) { // Transpile the CacheIR to generate the correct guards before // inlining. In this case, CacheOp::CallInlinedFunction updates // the CallInfo, but does not generate a call. callInfo.markAsInlined(); if (!transpileCall(loc, inliningSnapshot->cacheIRSnapshot(), &callInfo)) { return false; } // Generate the body of the inlined function. return buildInlinedCall(loc, inliningSnapshot, callInfo); } if (auto* cacheIRSnapshot = getOpSnapshot(loc)) { return transpileCall(loc, cacheIRSnapshot, &callInfo); } if (getOpSnapshot(loc)) { callInfo.setImplicitlyUsedUnchecked(); return buildBailoutForColdIC(loc, CacheKind::Call); } bool needsThisCheck = false; if (callInfo.constructing()) { buildCreateThis(callInfo); needsThisCheck = true; } MCall* call = makeCall(callInfo, needsThisCheck); if (!call) { return false; } current->add(call); current->push(call); return resumeAfter(call, loc); } bool WarpBuilder::build_Call(BytecodeLocation loc) { return buildCallOp(loc); } bool WarpBuilder::build_CallContent(BytecodeLocation loc) { return buildCallOp(loc); } bool WarpBuilder::build_CallIgnoresRv(BytecodeLocation loc) { return buildCallOp(loc); } bool WarpBuilder::build_CallIter(BytecodeLocation loc) { return buildCallOp(loc); } bool WarpBuilder::build_CallContentIter(BytecodeLocation loc) { return buildCallOp(loc); } bool WarpBuilder::build_New(BytecodeLocation loc) { return buildCallOp(loc); } bool WarpBuilder::build_NewContent(BytecodeLocation loc) { return buildCallOp(loc); } bool WarpBuilder::build_SuperCall(BytecodeLocation loc) { return buildCallOp(loc); } bool WarpBuilder::build_FunctionThis(BytecodeLocation loc) { MOZ_ASSERT(info().hasFunMaybeLazy()); if (script_->strict()) { // No need to wrap primitive |this| in strict mode. current->pushSlot(info().thisSlot()); return true; } MOZ_ASSERT(!script_->hasNonSyntacticScope(), "WarpOracle should have aborted compilation"); MDefinition* def = current->getSlot(info().thisSlot()); JSObject* globalThis = snapshot().globalLexicalEnvThis(); auto* thisObj = MBoxNonStrictThis::New(alloc(), def, globalThis); current->add(thisObj); current->push(thisObj); return true; } bool WarpBuilder::build_GlobalThis(BytecodeLocation loc) { MOZ_ASSERT(!script_->hasNonSyntacticScope()); JSObject* obj = snapshot().globalLexicalEnvThis(); pushConstant(ObjectValue(*obj)); return true; } MConstant* WarpBuilder::globalLexicalEnvConstant() { JSObject* globalLexical = snapshot().globalLexicalEnv(); return constant(ObjectValue(*globalLexical)); } bool WarpBuilder::build_GetName(BytecodeLocation loc) { MOZ_ASSERT(usesEnvironmentChain()); MDefinition* env = current->environmentChain(); return buildIC(loc, CacheKind::GetName, {env}); } bool WarpBuilder::build_GetGName(BytecodeLocation loc) { MOZ_ASSERT(!script_->hasNonSyntacticScope()); MDefinition* env = globalLexicalEnvConstant(); return buildIC(loc, CacheKind::GetName, {env}); } bool WarpBuilder::build_BindName(BytecodeLocation loc) { MOZ_ASSERT(usesEnvironmentChain()); MDefinition* env = current->environmentChain(); return buildIC(loc, CacheKind::BindName, {env}); } bool WarpBuilder::build_BindGName(BytecodeLocation loc) { MOZ_ASSERT(!script_->hasNonSyntacticScope()); if (const auto* snapshot = getOpSnapshot(loc)) { JSObject* globalEnv = snapshot->globalEnv(); pushConstant(ObjectValue(*globalEnv)); return true; } MDefinition* env = globalLexicalEnvConstant(); return buildIC(loc, CacheKind::BindName, {env}); } bool WarpBuilder::build_GetProp(BytecodeLocation loc) { MDefinition* val = current->pop(); return buildIC(loc, CacheKind::GetProp, {val}); } bool WarpBuilder::build_GetElem(BytecodeLocation loc) { MDefinition* id = current->pop(); MDefinition* val = current->pop(); return buildIC(loc, CacheKind::GetElem, {val, id}); } bool WarpBuilder::build_SetProp(BytecodeLocation loc) { MDefinition* val = current->pop(); MDefinition* obj = current->pop(); current->push(val); return buildIC(loc, CacheKind::SetProp, {obj, val}); } bool WarpBuilder::build_StrictSetProp(BytecodeLocation loc) { return build_SetProp(loc); } bool WarpBuilder::build_SetName(BytecodeLocation loc) { return build_SetProp(loc); } bool WarpBuilder::build_StrictSetName(BytecodeLocation loc) { return build_SetProp(loc); } bool WarpBuilder::build_SetGName(BytecodeLocation loc) { return build_SetProp(loc); } bool WarpBuilder::build_StrictSetGName(BytecodeLocation loc) { return build_SetProp(loc); } bool WarpBuilder::build_InitGLexical(BytecodeLocation loc) { MOZ_ASSERT(!script_->hasNonSyntacticScope()); MDefinition* globalLexical = globalLexicalEnvConstant(); MDefinition* val = current->peek(-1); return buildIC(loc, CacheKind::SetProp, {globalLexical, val}); } bool WarpBuilder::build_SetElem(BytecodeLocation loc) { MDefinition* val = current->pop(); MDefinition* id = current->pop(); MDefinition* obj = current->pop(); current->push(val); return buildIC(loc, CacheKind::SetElem, {obj, id, val}); } bool WarpBuilder::build_StrictSetElem(BytecodeLocation loc) { return build_SetElem(loc); } bool WarpBuilder::build_DelProp(BytecodeLocation loc) { PropertyName* name = loc.getPropertyName(script_); MDefinition* obj = current->pop(); bool strict = loc.getOp() == JSOp::StrictDelProp; MInstruction* ins = MDeleteProperty::New(alloc(), obj, name, strict); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_StrictDelProp(BytecodeLocation loc) { return build_DelProp(loc); } bool WarpBuilder::build_DelElem(BytecodeLocation loc) { MDefinition* id = current->pop(); MDefinition* obj = current->pop(); bool strict = loc.getOp() == JSOp::StrictDelElem; MInstruction* ins = MDeleteElement::New(alloc(), obj, id, strict); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_StrictDelElem(BytecodeLocation loc) { return build_DelElem(loc); } bool WarpBuilder::build_SetFunName(BytecodeLocation loc) { FunctionPrefixKind prefixKind = loc.getFunctionPrefixKind(); MDefinition* name = current->pop(); MDefinition* fun = current->pop(); MSetFunName* ins = MSetFunName::New(alloc(), fun, name, uint8_t(prefixKind)); current->add(ins); current->push(fun); return resumeAfter(ins, loc); } bool WarpBuilder::build_PushLexicalEnv(BytecodeLocation loc) { MOZ_ASSERT(usesEnvironmentChain()); const auto* snapshot = getOpSnapshot(loc); MOZ_ASSERT(snapshot); MDefinition* env = current->environmentChain(); MConstant* templateCst = constant(ObjectValue(*snapshot->templateObj())); auto* ins = MNewLexicalEnvironmentObject::New(alloc(), templateCst); current->add(ins); #ifdef DEBUG // Assert in debug mode we can elide the post write barrier. current->add(MAssertCanElidePostWriteBarrier::New(alloc(), ins, env)); #endif // Initialize the object's reserved slots. No post barrier is needed here, // for the same reason as in buildNamedLambdaEnv. current->add(MStoreFixedSlot::NewUnbarriered( alloc(), ins, EnvironmentObject::enclosingEnvironmentSlot(), env)); current->setEnvironmentChain(ins); return true; } bool WarpBuilder::build_PushClassBodyEnv(BytecodeLocation loc) { MOZ_ASSERT(usesEnvironmentChain()); const auto* snapshot = getOpSnapshot(loc); MOZ_ASSERT(snapshot); MDefinition* env = current->environmentChain(); MConstant* templateCst = constant(ObjectValue(*snapshot->templateObj())); auto* ins = MNewClassBodyEnvironmentObject::New(alloc(), templateCst); current->add(ins); #ifdef DEBUG // Assert in debug mode we can elide the post write barrier. current->add(MAssertCanElidePostWriteBarrier::New(alloc(), ins, env)); #endif // Initialize the object's reserved slots. No post barrier is needed here, // for the same reason as in buildNamedLambdaEnv. current->add(MStoreFixedSlot::NewUnbarriered( alloc(), ins, EnvironmentObject::enclosingEnvironmentSlot(), env)); current->setEnvironmentChain(ins); return true; } bool WarpBuilder::build_PopLexicalEnv(BytecodeLocation) { MDefinition* enclosingEnv = walkEnvironmentChain(1); if (!enclosingEnv) { return false; } current->setEnvironmentChain(enclosingEnv); return true; } bool WarpBuilder::build_FreshenLexicalEnv(BytecodeLocation loc) { MOZ_ASSERT(usesEnvironmentChain()); const auto* snapshot = getOpSnapshot(loc); MOZ_ASSERT(snapshot); MDefinition* enclosingEnv = walkEnvironmentChain(1); if (!enclosingEnv) { return false; } MDefinition* env = current->environmentChain(); MConstant* templateCst = constant(ObjectValue(*snapshot->templateObj())); auto* templateObj = snapshot->templateObj(); auto* scope = &templateObj->scope(); MOZ_ASSERT(scope->hasEnvironment()); auto* ins = MNewLexicalEnvironmentObject::New(alloc(), templateCst); current->add(ins); #ifdef DEBUG // Assert in debug mode we can elide the post write barrier. current->add( MAssertCanElidePostWriteBarrier::New(alloc(), ins, enclosingEnv)); #endif // Initialize the object's reserved slots. No post barrier is needed here, // for the same reason as in buildNamedLambdaEnv. current->add(MStoreFixedSlot::NewUnbarriered( alloc(), ins, EnvironmentObject::enclosingEnvironmentSlot(), enclosingEnv)); // Copy environment slots. MSlots* envSlots = nullptr; MSlots* slots = nullptr; for (BindingIter iter(scope); iter; iter++) { auto loc = iter.location(); if (loc.kind() != BindingLocation::Kind::Environment) { MOZ_ASSERT(loc.kind() == BindingLocation::Kind::Frame); continue; } if (!alloc().ensureBallast()) { return false; } uint32_t slot = loc.slot(); uint32_t numFixedSlots = templateObj->numFixedSlots(); if (slot >= numFixedSlots) { if (!envSlots) { envSlots = MSlots::New(alloc(), env); current->add(envSlots); } if (!slots) { slots = MSlots::New(alloc(), ins); current->add(slots); } uint32_t dynamicSlot = slot - numFixedSlots; auto* load = MLoadDynamicSlot::New(alloc(), envSlots, dynamicSlot); current->add(load); #ifdef DEBUG // Assert in debug mode we can elide the post write barrier. current->add(MAssertCanElidePostWriteBarrier::New(alloc(), ins, load)); #endif current->add( MStoreDynamicSlot::NewUnbarriered(alloc(), slots, dynamicSlot, load)); } else { auto* load = MLoadFixedSlot::New(alloc(), env, slot); current->add(load); #ifdef DEBUG // Assert in debug mode we can elide the post write barrier. current->add(MAssertCanElidePostWriteBarrier::New(alloc(), ins, load)); #endif current->add(MStoreFixedSlot::NewUnbarriered(alloc(), ins, slot, load)); } } current->setEnvironmentChain(ins); return true; } bool WarpBuilder::build_RecreateLexicalEnv(BytecodeLocation loc) { MOZ_ASSERT(usesEnvironmentChain()); const auto* snapshot = getOpSnapshot(loc); MOZ_ASSERT(snapshot); MDefinition* enclosingEnv = walkEnvironmentChain(1); if (!enclosingEnv) { return false; } MConstant* templateCst = constant(ObjectValue(*snapshot->templateObj())); auto* ins = MNewLexicalEnvironmentObject::New(alloc(), templateCst); current->add(ins); #ifdef DEBUG // Assert in debug mode we can elide the post write barrier. current->add( MAssertCanElidePostWriteBarrier::New(alloc(), ins, enclosingEnv)); #endif // Initialize the object's reserved slots. No post barrier is needed here, // for the same reason as in buildNamedLambdaEnv. current->add(MStoreFixedSlot::NewUnbarriered( alloc(), ins, EnvironmentObject::enclosingEnvironmentSlot(), enclosingEnv)); current->setEnvironmentChain(ins); return true; } bool WarpBuilder::build_PushVarEnv(BytecodeLocation loc) { MOZ_ASSERT(usesEnvironmentChain()); const auto* snapshot = getOpSnapshot(loc); MOZ_ASSERT(snapshot); MDefinition* env = current->environmentChain(); MConstant* templateCst = constant(ObjectValue(*snapshot->templateObj())); auto* ins = MNewVarEnvironmentObject::New(alloc(), templateCst); current->add(ins); #ifdef DEBUG // Assert in debug mode we can elide the post write barrier. current->add(MAssertCanElidePostWriteBarrier::New(alloc(), ins, env)); #endif // Initialize the object's reserved slots. No post barrier is needed here, // for the same reason as in buildNamedLambdaEnv. current->add(MStoreFixedSlot::NewUnbarriered( alloc(), ins, EnvironmentObject::enclosingEnvironmentSlot(), env)); current->setEnvironmentChain(ins); return true; } bool WarpBuilder::build_ImplicitThis(BytecodeLocation loc) { MOZ_ASSERT(usesEnvironmentChain()); PropertyName* name = loc.getPropertyName(script_); MDefinition* env = current->environmentChain(); auto* ins = MImplicitThis::New(alloc(), env, name); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_CheckClassHeritage(BytecodeLocation loc) { MDefinition* def = current->pop(); auto* ins = MCheckClassHeritage::New(alloc(), def); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_CheckThis(BytecodeLocation loc) { MDefinition* def = current->pop(); auto* ins = MCheckThis::New(alloc(), def); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_CheckThisReinit(BytecodeLocation loc) { MDefinition* def = current->pop(); auto* ins = MCheckThisReinit::New(alloc(), def); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_Generator(BytecodeLocation loc) { MOZ_ASSERT(usesEnvironmentChain()); MDefinition* callee = getCallee(); MDefinition* environmentChain = current->environmentChain(); MDefinition* argsObj = info().needsArgsObj() ? current->argumentsObject() : constant(Int32Value(0)); MGenerator* generator = MGenerator::New(alloc(), callee, environmentChain, argsObj); current->add(generator); current->push(generator); return resumeAfter(generator, loc); } bool WarpBuilder::build_AfterYield(BytecodeLocation loc) { // Unreachable blocks don't need to generate a bail. if (hasTerminatedBlock()) { return true; } // This comes after a yield, which we generate as a return, // so we know this should be unreachable code. // // We emit an unreachable bail for this, which will assert if we // ever execute this. // // An Unreachable bail, instead of MUnreachable, because MUnreachable // is a control instruction, and injecting it in the middle of a block // causes various graph state assertions to fail. MBail* bail = MBail::New(alloc(), BailoutKind::Unreachable); current->add(bail); return true; } bool WarpBuilder::build_FinalYieldRval(BytecodeLocation loc) { MDefinition* gen = current->pop(); auto setSlotNull = [this, gen](size_t slot) { auto* ins = MStoreFixedSlot::NewBarriered(alloc(), gen, slot, constant(NullValue())); current->add(ins); }; // Close the generator setSlotNull(AbstractGeneratorObject::calleeSlot()); setSlotNull(AbstractGeneratorObject::envChainSlot()); setSlotNull(AbstractGeneratorObject::argsObjectSlot()); setSlotNull(AbstractGeneratorObject::stackStorageSlot()); setSlotNull(AbstractGeneratorObject::resumeIndexSlot()); // Return return build_RetRval(loc); } bool WarpBuilder::build_AsyncResolve(BytecodeLocation loc) { MDefinition* generator = current->pop(); MDefinition* valueOrReason = current->pop(); auto resolveKind = loc.getAsyncFunctionResolveKind(); MAsyncResolve* resolve = MAsyncResolve::New(alloc(), generator, valueOrReason, resolveKind); current->add(resolve); current->push(resolve); return resumeAfter(resolve, loc); } bool WarpBuilder::build_ResumeKind(BytecodeLocation loc) { GeneratorResumeKind resumeKind = loc.resumeKind(); current->push(constant(Int32Value(static_cast(resumeKind)))); return true; } bool WarpBuilder::build_CheckResumeKind(BytecodeLocation loc) { // Outside of `yield*`, this is normally unreachable code in Warp, // so we just manipulate the stack appropriately to ensure correct // MIR generation. // // However, `yield*` emits a forced generator return which can be // warp compiled, so in order to correctly handle these semantics // we also generate a bailout, so that the forced generator return // runs in baseline. MDefinition* resumeKind = current->pop(); MDefinition* gen = current->pop(); MDefinition* rval = current->peek(-1); // Mark operands as implicitly used. resumeKind->setImplicitlyUsedUnchecked(); gen->setImplicitlyUsedUnchecked(); rval->setImplicitlyUsedUnchecked(); // Bail out if we encounter CheckResumeKind. MBail* bail = MBail::New(alloc(), BailoutKind::Inevitable); current->add(bail); current->setAlwaysBails(); return true; } bool WarpBuilder::build_CanSkipAwait(BytecodeLocation loc) { MDefinition* val = current->pop(); MCanSkipAwait* canSkip = MCanSkipAwait::New(alloc(), val); current->add(canSkip); current->push(val); current->push(canSkip); return resumeAfter(canSkip, loc); } bool WarpBuilder::build_MaybeExtractAwaitValue(BytecodeLocation loc) { MDefinition* canSkip = current->pop(); MDefinition* value = current->pop(); MMaybeExtractAwaitValue* extracted = MMaybeExtractAwaitValue::New(alloc(), value, canSkip); current->add(extracted); current->push(extracted); current->push(canSkip); return resumeAfter(extracted, loc); } bool WarpBuilder::build_InitialYield(BytecodeLocation loc) { MDefinition* gen = current->pop(); return buildSuspend(loc, gen, gen); } bool WarpBuilder::build_Await(BytecodeLocation loc) { MDefinition* gen = current->pop(); MDefinition* promiseOrGenerator = current->pop(); return buildSuspend(loc, gen, promiseOrGenerator); } bool WarpBuilder::build_Yield(BytecodeLocation loc) { return build_Await(loc); } bool WarpBuilder::buildSuspend(BytecodeLocation loc, MDefinition* gen, MDefinition* retVal) { // If required, unbox the generator object explicitly and infallibly. // // This is done to avoid fuzz-bugs where ApplyTypeInformation does the // unboxing, and generates fallible unboxes which can lead to torn object // state due to `bailAfter`. MDefinition* genObj = gen; if (genObj->type() != MIRType::Object) { auto* unbox = MUnbox::New(alloc(), gen, MIRType::Object, MUnbox::Mode::Infallible); current->add(unbox); genObj = unbox; } int32_t slotsToCopy = current->stackDepth() - info().firstLocalSlot(); MOZ_ASSERT(slotsToCopy >= 0); if (slotsToCopy > 0) { auto* arrayObj = MLoadFixedSlotAndUnbox::New( alloc(), genObj, AbstractGeneratorObject::stackStorageSlot(), MUnbox::Mode::Infallible, MIRType::Object); current->add(arrayObj); auto* stackStorage = MElements::New(alloc(), arrayObj); current->add(stackStorage); for (int32_t i = 0; i < slotsToCopy; i++) { if (!alloc().ensureBallast()) { return false; } // Use peekUnchecked because we're also writing out the argument slots int32_t peek = -slotsToCopy + i; MDefinition* stackElem = current->peekUnchecked(peek); auto* store = MStoreElement::NewUnbarriered( alloc(), stackStorage, constant(Int32Value(i)), stackElem, /* needsHoleCheck = */ false); current->add(store); current->add(MPostWriteBarrier::New(alloc(), arrayObj, stackElem)); } auto* len = constant(Int32Value(slotsToCopy - 1)); auto* setInitLength = MSetInitializedLength::New(alloc(), stackStorage, len); current->add(setInitLength); auto* setLength = MSetArrayLength::New(alloc(), stackStorage, len); current->add(setLength); } // Update Generator Object state uint32_t resumeIndex = loc.getResumeIndex(); // This store is unbarriered, as it's only ever storing an integer, and as // such doesn't partake of object tracing. current->add(MStoreFixedSlot::NewUnbarriered( alloc(), genObj, AbstractGeneratorObject::resumeIndexSlot(), constant(Int32Value(resumeIndex)))); // This store is barriered because it stores an object value. current->add(MStoreFixedSlot::NewBarriered( alloc(), genObj, AbstractGeneratorObject::envChainSlot(), current->environmentChain())); current->add( MPostWriteBarrier::New(alloc(), genObj, current->environmentChain())); // GeneratorReturn will return from the method, however to support MIR // generation isn't treated like the end of a block MGeneratorReturn* ret = MGeneratorReturn::New(alloc(), retVal); current->add(ret); // To ensure the rest of the MIR generation looks correct, fill the stack with // the appropriately typed MUnreachable's for the stack pushes from this // opcode. auto* unreachableResumeKind = MUnreachableResult::New(alloc(), MIRType::Int32); current->add(unreachableResumeKind); current->push(unreachableResumeKind); auto* unreachableGenerator = MUnreachableResult::New(alloc(), MIRType::Object); current->add(unreachableGenerator); current->push(unreachableGenerator); auto* unreachableRval = MUnreachableResult::New(alloc(), MIRType::Value); current->add(unreachableRval); current->push(unreachableRval); return true; } bool WarpBuilder::build_AsyncAwait(BytecodeLocation loc) { MDefinition* gen = current->pop(); MDefinition* value = current->pop(); MAsyncAwait* asyncAwait = MAsyncAwait::New(alloc(), value, gen); current->add(asyncAwait); current->push(asyncAwait); return resumeAfter(asyncAwait, loc); } bool WarpBuilder::build_CheckReturn(BytecodeLocation loc) { MOZ_ASSERT(!script_->noScriptRval()); MDefinition* returnValue = current->getSlot(info().returnValueSlot()); MDefinition* thisValue = current->pop(); auto* ins = MCheckReturn::New(alloc(), returnValue, thisValue); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } void WarpBuilder::buildCheckLexicalOp(BytecodeLocation loc) { JSOp op = loc.getOp(); MOZ_ASSERT(op == JSOp::CheckLexical || op == JSOp::CheckAliasedLexical); MDefinition* input = current->pop(); MInstruction* lexicalCheck = MLexicalCheck::New(alloc(), input); current->add(lexicalCheck); current->push(lexicalCheck); if (snapshot().bailoutInfo().failedLexicalCheck()) { // If we have previously had a failed lexical check in Ion, we want to avoid // hoisting any lexical checks, which can cause spurious failures. In this // case, we also have to be careful not to hoist any loads of this lexical // past the check. For unaliased lexical variables, we can set the local // slot to create a dependency (see below). For aliased lexicals, that // doesn't work, so we disable LICM instead. lexicalCheck->setNotMovable(); if (op == JSOp::CheckAliasedLexical) { mirGen().disableLICM(); } } if (op == JSOp::CheckLexical) { // Set the local slot so that a subsequent GetLocal without a CheckLexical // (the frontend can elide lexical checks) doesn't let a definition with // MIRType::MagicUninitializedLexical escape to arbitrary MIR instructions. // Note that in this case the GetLocal would be unreachable because we throw // an exception here, but we still generate MIR instructions for it. uint32_t slot = info().localSlot(loc.local()); current->setSlot(slot, lexicalCheck); } } bool WarpBuilder::build_CheckLexical(BytecodeLocation loc) { buildCheckLexicalOp(loc); return true; } bool WarpBuilder::build_CheckAliasedLexical(BytecodeLocation loc) { buildCheckLexicalOp(loc); return true; } bool WarpBuilder::build_InitHomeObject(BytecodeLocation loc) { MDefinition* homeObject = current->pop(); MDefinition* function = current->pop(); current->add(MPostWriteBarrier::New(alloc(), function, homeObject)); auto* ins = MInitHomeObject::New(alloc(), function, homeObject); current->add(ins); current->push(ins); return true; } bool WarpBuilder::build_SuperBase(BytecodeLocation) { MDefinition* callee = current->pop(); auto* homeObject = MHomeObject::New(alloc(), callee); current->add(homeObject); auto* superBase = MHomeObjectSuperBase::New(alloc(), homeObject); current->add(superBase); current->push(superBase); return true; } bool WarpBuilder::build_SuperFun(BytecodeLocation) { MDefinition* callee = current->pop(); auto* ins = MSuperFunction::New(alloc(), callee); current->add(ins); current->push(ins); return true; } bool WarpBuilder::build_BuiltinObject(BytecodeLocation loc) { if (auto* snapshot = getOpSnapshot(loc)) { JSObject* builtin = snapshot->builtin(); pushConstant(ObjectValue(*builtin)); return true; } auto kind = loc.getBuiltinObjectKind(); auto* ins = MBuiltinObject::New(alloc(), kind); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_GetIntrinsic(BytecodeLocation loc) { if (auto* snapshot = getOpSnapshot(loc)) { Value intrinsic = snapshot->intrinsic(); pushConstant(intrinsic); return true; } PropertyName* name = loc.getPropertyName(script_); MCallGetIntrinsicValue* ins = MCallGetIntrinsicValue::New(alloc(), name); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_ImportMeta(BytecodeLocation loc) { ModuleObject* moduleObj = scriptSnapshot()->moduleObject(); MOZ_ASSERT(moduleObj); MModuleMetadata* ins = MModuleMetadata::New(alloc(), moduleObj); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_CallSiteObj(BytecodeLocation loc) { return build_Object(loc); } bool WarpBuilder::build_NewArray(BytecodeLocation loc) { return buildIC(loc, CacheKind::NewArray, {}); } bool WarpBuilder::build_NewObject(BytecodeLocation loc) { return buildIC(loc, CacheKind::NewObject, {}); } bool WarpBuilder::build_NewInit(BytecodeLocation loc) { return build_NewObject(loc); } bool WarpBuilder::build_Object(BytecodeLocation loc) { JSObject* obj = loc.getObject(script_); MConstant* objConst = constant(ObjectValue(*obj)); current->push(objConst); return true; } bool WarpBuilder::buildInitPropGetterSetterOp(BytecodeLocation loc) { PropertyName* name = loc.getPropertyName(script_); MDefinition* value = current->pop(); MDefinition* obj = current->peek(-1); auto* ins = MInitPropGetterSetter::New(alloc(), obj, value, name); current->add(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_InitPropGetter(BytecodeLocation loc) { return buildInitPropGetterSetterOp(loc); } bool WarpBuilder::build_InitPropSetter(BytecodeLocation loc) { return buildInitPropGetterSetterOp(loc); } bool WarpBuilder::build_InitHiddenPropGetter(BytecodeLocation loc) { return buildInitPropGetterSetterOp(loc); } bool WarpBuilder::build_InitHiddenPropSetter(BytecodeLocation loc) { return buildInitPropGetterSetterOp(loc); } bool WarpBuilder::buildInitElemGetterSetterOp(BytecodeLocation loc) { MDefinition* value = current->pop(); MDefinition* id = current->pop(); MDefinition* obj = current->peek(-1); auto* ins = MInitElemGetterSetter::New(alloc(), obj, id, value); current->add(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_InitElemGetter(BytecodeLocation loc) { return buildInitElemGetterSetterOp(loc); } bool WarpBuilder::build_InitElemSetter(BytecodeLocation loc) { return buildInitElemGetterSetterOp(loc); } bool WarpBuilder::build_InitHiddenElemGetter(BytecodeLocation loc) { return buildInitElemGetterSetterOp(loc); } bool WarpBuilder::build_InitHiddenElemSetter(BytecodeLocation loc) { return buildInitElemGetterSetterOp(loc); } bool WarpBuilder::build_In(BytecodeLocation loc) { MDefinition* obj = current->pop(); MDefinition* id = current->pop(); return buildIC(loc, CacheKind::In, {id, obj}); } bool WarpBuilder::build_HasOwn(BytecodeLocation loc) { MDefinition* obj = current->pop(); MDefinition* id = current->pop(); return buildIC(loc, CacheKind::HasOwn, {id, obj}); } bool WarpBuilder::build_CheckPrivateField(BytecodeLocation loc) { MDefinition* id = current->peek(-1); MDefinition* obj = current->peek(-2); return buildIC(loc, CacheKind::CheckPrivateField, {obj, id}); } bool WarpBuilder::build_NewPrivateName(BytecodeLocation loc) { JSAtom* name = loc.getAtom(script_); auto* ins = MNewPrivateName::New(alloc(), name); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_Instanceof(BytecodeLocation loc) { MDefinition* rhs = current->pop(); MDefinition* obj = current->pop(); return buildIC(loc, CacheKind::InstanceOf, {obj, rhs}); } bool WarpBuilder::build_NewTarget(BytecodeLocation loc) { MOZ_ASSERT(script_->isFunction()); MOZ_ASSERT(info().hasFunMaybeLazy()); MOZ_ASSERT(!scriptSnapshot()->isArrowFunction()); if (inlineCallInfo()) { if (inlineCallInfo()->constructing()) { current->push(inlineCallInfo()->getNewTarget()); } else { pushConstant(UndefinedValue()); } return true; } MNewTarget* ins = MNewTarget::New(alloc()); current->add(ins); current->push(ins); return true; } bool WarpBuilder::build_CheckIsObj(BytecodeLocation loc) { CheckIsObjectKind kind = loc.getCheckIsObjectKind(); MDefinition* toCheck = current->peek(-1); if (toCheck->type() == MIRType::Object) { toCheck->setImplicitlyUsedUnchecked(); return true; } MDefinition* val = current->pop(); MCheckIsObj* ins = MCheckIsObj::New(alloc(), val, uint8_t(kind)); current->add(ins); current->push(ins); return true; } bool WarpBuilder::build_CheckObjCoercible(BytecodeLocation loc) { MDefinition* val = current->pop(); MCheckObjCoercible* ins = MCheckObjCoercible::New(alloc(), val); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } MInstruction* WarpBuilder::buildLoadSlot(MDefinition* obj, uint32_t numFixedSlots, uint32_t slot) { if (slot < numFixedSlots) { MLoadFixedSlot* load = MLoadFixedSlot::New(alloc(), obj, slot); current->add(load); return load; } MSlots* slots = MSlots::New(alloc(), obj); current->add(slots); MLoadDynamicSlot* load = MLoadDynamicSlot::New(alloc(), slots, slot - numFixedSlots); current->add(load); return load; } bool WarpBuilder::build_GetImport(BytecodeLocation loc) { auto* snapshot = getOpSnapshot(loc); ModuleEnvironmentObject* targetEnv = snapshot->targetEnv(); // Load the target environment slot. MConstant* obj = constant(ObjectValue(*targetEnv)); auto* load = buildLoadSlot(obj, snapshot->numFixedSlots(), snapshot->slot()); if (snapshot->needsLexicalCheck()) { // TODO: IonBuilder has code to mark non-movable. See buildCheckLexicalOp. MInstruction* lexicalCheck = MLexicalCheck::New(alloc(), load); current->add(lexicalCheck); current->push(lexicalCheck); } else { current->push(load); } return true; } bool WarpBuilder::build_GetPropSuper(BytecodeLocation loc) { MDefinition* obj = current->pop(); MDefinition* receiver = current->pop(); return buildIC(loc, CacheKind::GetPropSuper, {obj, receiver}); } bool WarpBuilder::build_GetElemSuper(BytecodeLocation loc) { MDefinition* obj = current->pop(); MDefinition* id = current->pop(); MDefinition* receiver = current->pop(); return buildIC(loc, CacheKind::GetElemSuper, {obj, id, receiver}); } bool WarpBuilder::build_InitProp(BytecodeLocation loc) { MDefinition* val = current->pop(); MDefinition* obj = current->peek(-1); return buildIC(loc, CacheKind::SetProp, {obj, val}); } bool WarpBuilder::build_InitLockedProp(BytecodeLocation loc) { return build_InitProp(loc); } bool WarpBuilder::build_InitHiddenProp(BytecodeLocation loc) { return build_InitProp(loc); } bool WarpBuilder::build_InitElem(BytecodeLocation loc) { MDefinition* val = current->pop(); MDefinition* id = current->pop(); MDefinition* obj = current->peek(-1); return buildIC(loc, CacheKind::SetElem, {obj, id, val}); } bool WarpBuilder::build_InitLockedElem(BytecodeLocation loc) { return build_InitElem(loc); } bool WarpBuilder::build_InitHiddenElem(BytecodeLocation loc) { return build_InitElem(loc); } bool WarpBuilder::build_InitElemArray(BytecodeLocation loc) { MDefinition* val = current->pop(); MDefinition* obj = current->peek(-1); // Note: getInitElemArrayIndex asserts the index fits in int32_t. uint32_t index = loc.getInitElemArrayIndex(); MConstant* indexConst = constant(Int32Value(index)); // Note: InitArrayElemOperation asserts the index does not exceed the array's // dense element capacity. auto* elements = MElements::New(alloc(), obj); current->add(elements); if (val->type() == MIRType::MagicHole) { val->setImplicitlyUsedUnchecked(); auto* store = MStoreHoleValueElement::New(alloc(), elements, indexConst); current->add(store); } else { current->add(MPostWriteBarrier::New(alloc(), obj, val)); auto* store = MStoreElement::NewUnbarriered(alloc(), elements, indexConst, val, /* needsHoleCheck = */ false); current->add(store); } auto* setLength = MSetInitializedLength::New(alloc(), elements, indexConst); current->add(setLength); return resumeAfter(setLength, loc); } bool WarpBuilder::build_InitElemInc(BytecodeLocation loc) { MDefinition* val = current->pop(); MDefinition* index = current->pop(); MDefinition* obj = current->peek(-1); // Push index + 1. MConstant* constOne = constant(Int32Value(1)); MAdd* nextIndex = MAdd::New(alloc(), index, constOne, TruncateKind::Truncate); current->add(nextIndex); current->push(nextIndex); return buildIC(loc, CacheKind::SetElem, {obj, index, val}); } bool WarpBuilder::build_Lambda(BytecodeLocation loc) { MOZ_ASSERT(usesEnvironmentChain()); MDefinition* env = current->environmentChain(); JSFunction* fun = loc.getFunction(script_); MConstant* funConst = constant(ObjectValue(*fun)); auto* ins = MLambda::New(alloc(), env, funConst); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_FunWithProto(BytecodeLocation loc) { MOZ_ASSERT(usesEnvironmentChain()); MDefinition* proto = current->pop(); MDefinition* env = current->environmentChain(); JSFunction* fun = loc.getFunction(script_); MConstant* funConst = constant(ObjectValue(*fun)); auto* ins = MFunctionWithProto::New(alloc(), env, proto, funConst); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } bool WarpBuilder::build_SpreadCall(BytecodeLocation loc) { bool constructing = false; CallInfo callInfo(alloc(), constructing, loc.resultIsPopped()); callInfo.initForSpreadCall(current); if (auto* cacheIRSnapshot = getOpSnapshot(loc)) { return transpileCall(loc, cacheIRSnapshot, &callInfo); } bool needsThisCheck = false; MInstruction* call = makeSpreadCall(callInfo, needsThisCheck); if (!call) { return false; } call->setBailoutKind(BailoutKind::TooManyArguments); current->add(call); current->push(call); return resumeAfter(call, loc); } bool WarpBuilder::build_SpreadNew(BytecodeLocation loc) { bool constructing = true; CallInfo callInfo(alloc(), constructing, loc.resultIsPopped()); callInfo.initForSpreadCall(current); if (auto* cacheIRSnapshot = getOpSnapshot(loc)) { return transpileCall(loc, cacheIRSnapshot, &callInfo); } buildCreateThis(callInfo); bool needsThisCheck = true; MInstruction* call = makeSpreadCall(callInfo, needsThisCheck); if (!call) { return false; } call->setBailoutKind(BailoutKind::TooManyArguments); current->add(call); current->push(call); return resumeAfter(call, loc); } bool WarpBuilder::build_SpreadSuperCall(BytecodeLocation loc) { return build_SpreadNew(loc); } bool WarpBuilder::build_OptimizeSpreadCall(BytecodeLocation loc) { MDefinition* value = current->pop(); return buildIC(loc, CacheKind::OptimizeSpreadCall, {value}); } bool WarpBuilder::build_Debugger(BytecodeLocation loc) { // The |debugger;| statement will bail out to Baseline if the realm is a // debuggee realm with an onDebuggerStatement hook. MDebugger* debugger = MDebugger::New(alloc()); current->add(debugger); return resumeAfter(debugger, loc); } bool WarpBuilder::build_TableSwitch(BytecodeLocation loc) { int32_t low = loc.getTableSwitchLow(); int32_t high = loc.getTableSwitchHigh(); size_t numCases = high - low + 1; MDefinition* input = current->pop(); MTableSwitch* tableswitch = MTableSwitch::New(alloc(), input, low, high); current->end(tableswitch); // Table mapping from target bytecode offset to MTableSwitch successor index. // This prevents adding multiple predecessor/successor edges to the same // target block, which isn't valid in MIR. using TargetToSuccessorMap = InlineMap, SystemAllocPolicy>; TargetToSuccessorMap targetToSuccessor; // Create |default| edge. { BytecodeLocation defaultLoc = loc.getTableSwitchDefaultTarget(); uint32_t defaultOffset = defaultLoc.bytecodeToOffset(script_); size_t index; if (!tableswitch->addDefault(nullptr, &index)) { return false; } if (!addPendingEdge(defaultLoc, current, index)) { return false; } if (!targetToSuccessor.put(defaultOffset, index)) { return false; } } // Add all cases. for (size_t i = 0; i < numCases; i++) { BytecodeLocation caseLoc = loc.getTableSwitchCaseTarget(script_, i); uint32_t caseOffset = caseLoc.bytecodeToOffset(script_); size_t index; if (auto p = targetToSuccessor.lookupForAdd(caseOffset)) { index = p->value(); } else { if (!tableswitch->addSuccessor(nullptr, &index)) { return false; } if (!addPendingEdge(caseLoc, current, index)) { return false; } if (!targetToSuccessor.add(p, caseOffset, index)) { return false; } } if (!tableswitch->addCase(index)) { return false; } } setTerminatedBlock(); return true; } bool WarpBuilder::build_Rest(BytecodeLocation loc) { auto* snapshot = getOpSnapshot(loc); Shape* shape = snapshot ? snapshot->shape() : nullptr; // NOTE: Keep this code in sync with |ArgumentsReplacer|. if (inlineCallInfo()) { // If we are inlining, we know the actual arguments. unsigned numActuals = inlineCallInfo()->argc(); unsigned numFormals = info().nargs() - 1; unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0; // TODO: support pre-tenuring. gc::Heap heap = gc::Heap::Default; // Allocate an array of the correct size. MInstruction* newArray; if (shape && gc::CanUseFixedElementsForArray(numRest)) { auto* shapeConstant = MConstant::NewShape(alloc(), shape); current->add(shapeConstant); newArray = MNewArrayObject::New(alloc(), shapeConstant, numRest, heap); } else { MConstant* templateConst = constant(NullValue()); newArray = MNewArray::NewVM(alloc(), numRest, templateConst, heap); } current->add(newArray); current->push(newArray); if (numRest == 0) { // No more updating to do. return true; } MElements* elements = MElements::New(alloc(), newArray); current->add(elements); // Unroll the argument copy loop. We don't need to do any bounds or hole // checking here. MConstant* index = nullptr; for (uint32_t i = numFormals; i < numActuals; i++) { if (!alloc().ensureBallast()) { return false; } index = MConstant::New(alloc(), Int32Value(i - numFormals)); current->add(index); MDefinition* arg = inlineCallInfo()->argv()[i]; MStoreElement* store = MStoreElement::NewUnbarriered(alloc(), elements, index, arg, /* needsHoleCheck = */ false); current->add(store); current->add(MPostWriteBarrier::New(alloc(), newArray, arg)); } // Update the initialized length for all the (necessarily non-hole) // elements added. MSetInitializedLength* initLength = MSetInitializedLength::New(alloc(), elements, index); current->add(initLength); return true; } MArgumentsLength* numActuals = MArgumentsLength::New(alloc()); current->add(numActuals); // Pass in the number of actual arguments, the number of formals (not // including the rest parameter slot itself), and the shape. unsigned numFormals = info().nargs() - 1; MRest* rest = MRest::New(alloc(), numActuals, numFormals, shape); current->add(rest); current->push(rest); return true; } bool WarpBuilder::build_Try(BytecodeLocation loc) { graph().setHasTryBlock(); MBasicBlock* pred = current; if (!startNewBlock(pred, loc.next())) { return false; } pred->end(MGoto::New(alloc(), current)); return true; } bool WarpBuilder::build_Finally(BytecodeLocation loc) { MOZ_ASSERT(graph().hasTryBlock()); return true; } bool WarpBuilder::build_Exception(BytecodeLocation) { MOZ_CRASH("Unreachable because we skip catch-blocks"); } bool WarpBuilder::build_Throw(BytecodeLocation loc) { MDefinition* def = current->pop(); MThrow* ins = MThrow::New(alloc(), def); current->add(ins); if (!resumeAfter(ins, loc)) { return false; } // Terminate the block. current->end(MUnreachable::New(alloc())); setTerminatedBlock(); return true; } bool WarpBuilder::build_ThrowSetConst(BytecodeLocation loc) { auto* ins = MThrowRuntimeLexicalError::New(alloc(), JSMSG_BAD_CONST_ASSIGN); current->add(ins); if (!resumeAfter(ins, loc)) { return false; } // Terminate the block. current->end(MUnreachable::New(alloc())); setTerminatedBlock(); return true; } bool WarpBuilder::build_ThrowMsg(BytecodeLocation loc) { auto* ins = MThrowMsg::New(alloc(), loc.throwMsgKind()); current->add(ins); if (!resumeAfter(ins, loc)) { return false; } // Terminate the block. current->end(MUnreachable::New(alloc())); setTerminatedBlock(); return true; } bool WarpBuilder::buildIC(BytecodeLocation loc, CacheKind kind, std::initializer_list inputs) { MOZ_ASSERT(loc.opHasIC()); mozilla::DebugOnly numInputs = inputs.size(); MOZ_ASSERT(numInputs == NumInputsForCacheKind(kind)); if (auto* cacheIRSnapshot = getOpSnapshot(loc)) { return TranspileCacheIRToMIR(this, loc, cacheIRSnapshot, inputs); } if (getOpSnapshot(loc)) { for (MDefinition* input : inputs) { input->setImplicitlyUsedUnchecked(); } return buildBailoutForColdIC(loc, kind); } if (const auto* inliningSnapshot = getOpSnapshot(loc)) { // The CallInfo will be initialized by the transpiler. bool ignoresRval = BytecodeIsPopped(loc.toRawBytecode()); CallInfo callInfo(alloc(), /*constructing =*/false, ignoresRval); callInfo.markAsInlined(); if (!TranspileCacheIRToMIR(this, loc, inliningSnapshot->cacheIRSnapshot(), inputs, &callInfo)) { return false; } return buildInlinedCall(loc, inliningSnapshot, callInfo); } // Work around std::initializer_list not defining operator[]. auto getInput = [&](size_t index) -> MDefinition* { MOZ_ASSERT(index < numInputs); return inputs.begin()[index]; }; switch (kind) { case CacheKind::UnaryArith: { MOZ_ASSERT(numInputs == 1); auto* ins = MUnaryCache::New(alloc(), getInput(0)); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::ToPropertyKey: { MOZ_ASSERT(numInputs == 1); auto* ins = MToPropertyKeyCache::New(alloc(), getInput(0)); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::BinaryArith: { MOZ_ASSERT(numInputs == 2); auto* ins = MBinaryCache::New(alloc(), getInput(0), getInput(1), MIRType::Value); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::Compare: { MOZ_ASSERT(numInputs == 2); auto* ins = MBinaryCache::New(alloc(), getInput(0), getInput(1), MIRType::Boolean); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::In: { MOZ_ASSERT(numInputs == 2); auto* ins = MInCache::New(alloc(), getInput(0), getInput(1)); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::HasOwn: { MOZ_ASSERT(numInputs == 2); // Note: the MHasOwnCache constructor takes obj/id instead of id/obj. auto* ins = MHasOwnCache::New(alloc(), getInput(1), getInput(0)); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::CheckPrivateField: { MOZ_ASSERT(numInputs == 2); auto* ins = MCheckPrivateFieldCache::New(alloc(), getInput(0), getInput(1)); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::InstanceOf: { MOZ_ASSERT(numInputs == 2); auto* ins = MInstanceOfCache::New(alloc(), getInput(0), getInput(1)); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::BindName: { MOZ_ASSERT(numInputs == 1); auto* ins = MBindNameCache::New(alloc(), getInput(0)); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::GetIterator: { MOZ_ASSERT(numInputs == 1); auto* ins = MGetIteratorCache::New(alloc(), getInput(0)); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::GetName: { MOZ_ASSERT(numInputs == 1); auto* ins = MGetNameCache::New(alloc(), getInput(0)); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::GetProp: { MOZ_ASSERT(numInputs == 1); PropertyName* name = loc.getPropertyName(script_); MConstant* id = constant(StringValue(name)); MDefinition* val = getInput(0); auto* ins = MGetPropertyCache::New(alloc(), val, id); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::GetElem: { MOZ_ASSERT(numInputs == 2); MDefinition* val = getInput(0); auto* ins = MGetPropertyCache::New(alloc(), val, getInput(1)); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::SetProp: { MOZ_ASSERT(numInputs == 2); PropertyName* name = loc.getPropertyName(script_); MConstant* id = constant(StringValue(name)); bool strict = loc.isStrictSetOp(); auto* ins = MSetPropertyCache::New(alloc(), getInput(0), id, getInput(1), strict); current->add(ins); return resumeAfter(ins, loc); } case CacheKind::SetElem: { MOZ_ASSERT(numInputs == 3); bool strict = loc.isStrictSetOp(); auto* ins = MSetPropertyCache::New(alloc(), getInput(0), getInput(1), getInput(2), strict); current->add(ins); return resumeAfter(ins, loc); } case CacheKind::GetPropSuper: { MOZ_ASSERT(numInputs == 2); PropertyName* name = loc.getPropertyName(script_); MConstant* id = constant(StringValue(name)); auto* ins = MGetPropSuperCache::New(alloc(), getInput(0), getInput(1), id); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::GetElemSuper: { MOZ_ASSERT(numInputs == 3); // Note: CacheIR expects obj/id/receiver but MGetPropSuperCache takes // obj/receiver/id so swap the last two inputs. auto* ins = MGetPropSuperCache::New(alloc(), getInput(0), getInput(2), getInput(1)); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::OptimizeSpreadCall: { MOZ_ASSERT(numInputs == 1); auto* ins = MOptimizeSpreadCallCache::New(alloc(), getInput(0)); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::TypeOf: { // Note: Warp does not have a TypeOf IC, it just inlines the operation. MOZ_ASSERT(numInputs == 1); auto* typeOf = MTypeOf::New(alloc(), getInput(0)); current->add(typeOf); auto* ins = MTypeOfName::New(alloc(), typeOf); current->add(ins); current->push(ins); return true; } case CacheKind::NewObject: { auto* templateConst = constant(NullValue()); MNewObject* ins = MNewObject::NewVM( alloc(), templateConst, gc::Heap::Default, MNewObject::ObjectLiteral); current->add(ins); current->push(ins); return resumeAfter(ins, loc); } case CacheKind::NewArray: { uint32_t length = loc.getNewArrayLength(); MConstant* templateConst = constant(NullValue()); MNewArray* ins = MNewArray::NewVM(alloc(), length, templateConst, gc::Heap::Default); current->add(ins); current->push(ins); return true; } case CacheKind::CloseIter: { MOZ_ASSERT(numInputs == 1); static_assert(sizeof(CompletionKind) == sizeof(uint8_t)); CompletionKind kind = loc.getCompletionKind(); auto* ins = MCloseIterCache::New(alloc(), getInput(0), uint8_t(kind)); current->add(ins); return resumeAfter(ins, loc); } case CacheKind::GetIntrinsic: case CacheKind::ToBool: case CacheKind::Call: // We're currently not using an IC or transpiling CacheIR for these kinds. MOZ_CRASH("Unexpected kind"); } return true; } bool WarpBuilder::buildBailoutForColdIC(BytecodeLocation loc, CacheKind kind) { MOZ_ASSERT(loc.opHasIC()); MBail* bail = MBail::New(alloc(), BailoutKind::FirstExecution); current->add(bail); current->setAlwaysBails(); MIRType resultType; switch (kind) { case CacheKind::UnaryArith: case CacheKind::BinaryArith: case CacheKind::GetName: case CacheKind::GetProp: case CacheKind::GetElem: case CacheKind::GetPropSuper: case CacheKind::GetElemSuper: case CacheKind::GetIntrinsic: case CacheKind::Call: case CacheKind::ToPropertyKey: case CacheKind::OptimizeSpreadCall: resultType = MIRType::Value; break; case CacheKind::BindName: case CacheKind::GetIterator: case CacheKind::NewArray: case CacheKind::NewObject: resultType = MIRType::Object; break; case CacheKind::TypeOf: resultType = MIRType::String; break; case CacheKind::ToBool: case CacheKind::Compare: case CacheKind::In: case CacheKind::HasOwn: case CacheKind::CheckPrivateField: case CacheKind::InstanceOf: resultType = MIRType::Boolean; break; case CacheKind::SetProp: case CacheKind::SetElem: case CacheKind::CloseIter: return true; // No result. } auto* ins = MUnreachableResult::New(alloc(), resultType); current->add(ins); current->push(ins); return true; } class MOZ_RAII AutoAccumulateReturns { MIRGraph& graph_; MIRGraphReturns* prev_; public: AutoAccumulateReturns(MIRGraph& graph, MIRGraphReturns& returns) : graph_(graph) { prev_ = graph_.returnAccumulator(); graph_.setReturnAccumulator(&returns); } ~AutoAccumulateReturns() { graph_.setReturnAccumulator(prev_); } }; bool WarpBuilder::buildInlinedCall(BytecodeLocation loc, const WarpInlinedCall* inlineSnapshot, CallInfo& callInfo) { jsbytecode* pc = loc.toRawBytecode(); if (callInfo.isSetter()) { // build_SetProp pushes the rhs argument onto the stack. Remove it // in preparation for pushCallStack. current->pop(); } callInfo.setImplicitlyUsedUnchecked(); // Capture formals in the outer resume point. if (!callInfo.pushCallStack(current)) { return false; } MResumePoint* outerResumePoint = MResumePoint::New(alloc(), current, pc, callInfo.inliningResumeMode()); if (!outerResumePoint) { return false; } current->setOuterResumePoint(outerResumePoint); // Pop formals again, except leave |callee| on stack for duration of call. callInfo.popCallStack(current); current->push(callInfo.callee()); // Build the graph. CompileInfo* calleeCompileInfo = inlineSnapshot->info(); MIRGraphReturns returns(alloc()); AutoAccumulateReturns aar(graph(), returns); WarpBuilder inlineBuilder(this, inlineSnapshot->scriptSnapshot(), *calleeCompileInfo, &callInfo, outerResumePoint); if (!inlineBuilder.buildInline()) { // Note: Inlining only aborts on OOM. If inlining would fail for // any other reason, we detect it in advance and don't inline. return false; } // We mark scripts as uninlineable in BytecodeAnalysis if we cannot // reach a return statement (without going through a catch/finally). MOZ_ASSERT(!returns.empty()); // Create return block BytecodeLocation postCall = loc.next(); MBasicBlock* prev = current; if (!startNewEntryBlock(prev->stackDepth(), postCall)) { return false; } // Restore previous value of callerResumePoint. current->setCallerResumePoint(callerResumePoint()); current->inheritSlots(prev); // Pop |callee|. current->pop(); // Accumulate return values. MDefinition* returnValue = patchInlinedReturns(calleeCompileInfo, callInfo, returns, current); if (!returnValue) { return false; } current->push(returnValue); // Initialize entry slots if (!current->initEntrySlots(alloc())) { return false; } return true; } MDefinition* WarpBuilder::patchInlinedReturns(CompileInfo* calleeCompileInfo, CallInfo& callInfo, MIRGraphReturns& exits, MBasicBlock* returnBlock) { if (exits.length() == 1) { return patchInlinedReturn(calleeCompileInfo, callInfo, exits[0], returnBlock); } // Accumulate multiple returns with a phi. MPhi* phi = MPhi::New(alloc()); if (!phi->reserveLength(exits.length())) { return nullptr; } for (auto* exit : exits) { MDefinition* rdef = patchInlinedReturn(calleeCompileInfo, callInfo, exit, returnBlock); if (!rdef) { return nullptr; } phi->addInput(rdef); } returnBlock->addPhi(phi); return phi; } MDefinition* WarpBuilder::patchInlinedReturn(CompileInfo* calleeCompileInfo, CallInfo& callInfo, MBasicBlock* exit, MBasicBlock* returnBlock) { // Replace the MReturn in the exit block with an MGoto branching to // the return block. MDefinition* rdef = exit->lastIns()->toReturn()->input(); exit->discardLastIns(); // Constructors must be patched by the caller to always return an object. // Derived class constructors contain extra bytecode to ensure an object // is always returned, so no additional patching is needed. if (callInfo.constructing() && !calleeCompileInfo->isDerivedClassConstructor()) { auto* filter = MReturnFromCtor::New(alloc(), rdef, callInfo.thisArg()); exit->add(filter); rdef = filter; } else if (callInfo.isSetter()) { // Setters return the rhs argument, not whatever value is returned. rdef = callInfo.getArg(0); } exit->end(MGoto::New(alloc(), returnBlock)); if (!returnBlock->addPredecessorWithoutPhis(exit)) { return nullptr; } return rdef; }