void TraceBuilder::endInlining() { auto calleeAR = m_fpValue; gen(InlineReturn, calleeAR); useState(std::move(m_inlineSavedStates.back())); m_inlineSavedStates.pop_back(); // See the comment in beginInlining about generator frames. if (m_curFunc->getValFunc()->isGenerator()) { gen(ReDefGeneratorSP, StackOffset(m_spOffset), m_spValue); } else { gen(ReDefSP, StackOffset(m_spOffset), m_fpValue, m_spValue); } }
SSATmp* IRBuilder::preOptimizeCheckStk(IRInstruction* inst) { auto const newType = inst->typeParam(); auto sp = inst->src(0); auto offset = inst->extra<CheckStk>()->offset; auto stkVal = getStackValue(sp, offset); auto const oldType = stkVal.knownType; if (oldType.isBoxed() && newType.isBoxed() && (oldType.not(newType) || newType < oldType)) { /* This CheckStk serves to update the inner type hint for a boxed * value, which requires no runtime work. This depends on the type being * boxed, and constraining it with DataTypeCountness will do it. */ constrainStack(sp, offset, DataTypeCountness); return gen(AssertStk, newType, StackOffset(offset), sp); } if (newType.not(oldType)) { /* This check will always fail. It's probably due to an incorrect * prediction. Generate a Jmp, and return the source because * following instructions may depend on the output of CheckStk * (they'll be DCEd later). Note that we can't use convertToJmp * because the return value isn't nullptr, so the original * instruction won't be inserted into the stream. */ gen(Jmp, inst->taken()); return sp; } if (newType >= oldType) { // The new type isn't better than the old type. return sp; } return nullptr; }
void IRBuilder::startBlock(Block* block) { assert(block); assert(m_savedBlocks.empty()); // No bytecode control flow in exits. if (block->empty()) { if (block != m_curBlock) { if (m_state.compatible(block)) { m_state.pauseBlock(block); } else { m_state.clearCse(); } assert(m_curBlock); auto& prev = m_curBlock->back(); if (!prev.hasEdges()) { gen(Jmp, block); } else if (!prev.isTerminal()) { prev.setNext(block); } m_curBlock = block; m_state.startBlock(m_curBlock); FTRACE(2, "IRBuilder switching to block B{}: {}\n", block->id(), show(m_state)); } } if (sp() == nullptr) { gen(DefSP, StackOffset(spOffset() + evalStack().size() - stackDeficit()), fp()); } }
/* * Insert asserts at various points in the IR. * TODO: t2137231 Insert DbgAssertPtr at points that use or produces a GenPtr */ static void insertAsserts(IRTrace* trace, IRFactory& factory) { forEachTraceBlock(trace, [&](Block* block) { for (auto it = block->begin(), end = block->end(); it != end; ) { IRInstruction& inst = *it; ++it; if (inst.op() == SpillStack) { insertSpillStackAsserts(inst, factory); continue; } if (inst.op() == Call) { SSATmp* sp = inst.dst(); IRInstruction* addr = factory.gen(LdStackAddr, inst.marker(), Type::PtrToGen, StackOffset(0), sp); insertAfter(&inst, addr); insertAfter(addr, factory.gen(DbgAssertPtr, inst.marker(), addr->dst())); continue; } if (!inst.isBlockEnd()) insertRefCountAsserts(inst, factory); } }); }
TEST(JumpOpts, optimizeSideExitCheck) { BCMarker marker = BCMarker::Dummy(); IRUnit unit{0}; Block* entry = unit.entry(); Block* taken = unit.defBlock(); Block* fallthru = unit.defBlock(); // A conditional jump that goes to a "SyncABIRegs; ReqBindJmp" on the taken // edge only can turn into a SideExitJmpSomething. auto fp = unit.gen(DefFP, marker); auto sp = unit.gen(DefSP, marker, StackOffset(0), fp->dst()); auto chk = unit.gen(CheckStk, marker, Type::Int, taken, StackOffset(0), sp->dst()); chk->setNext(fallthru); entry->push_back({fp, sp, chk}); fallthru->push_back(unit.gen(Halt, marker)); auto bcoff = 10; auto sync = unit.gen(SyncABIRegs, marker, fp->dst(), sp->dst()); auto bind = unit.gen(ReqBindJmp, marker, BCOffset(bcoff)); taken->push_back({sync, bind}); optimizeJumps(unit); // This exercises trivial jump optimization too: since the JmpZero gets turned // into a non-branch, the entry block gets coalesced with the Halt block. EXPECT_EQ(nullptr, entry->next()); EXPECT_EQ(nullptr, entry->taken()); EXPECT_EQ(Halt, entry->back().op()); auto const& sideExit = *(--entry->backIter()); EXPECT_EQ(SideExitGuardStk, sideExit.op()); EXPECT_EQ(bcoff, sideExit.extra<SideExitGuardData>()->taken); }
void TraceBuilder::useState(std::unique_ptr<State> state) { m_spValue = state->spValue; m_fpValue = state->fpValue; m_spOffset = state->spOffset; m_curFunc = state->curFunc; m_thisIsAvailable = state->thisAvailable; m_refCountedMemValue = state->refCountedMemValue; m_localValues = std::move(state->localValues); m_localTypes = std::move(state->localTypes); m_callerAvailableValues = std::move(state->callerAvailableValues); // If spValue is null, we merged two different but equivalent values. // Define a new sp using the known-good spOffset. if (!m_spValue) { gen(DefSP, StackOffset(m_spOffset), m_fpValue); } }
/* * Insert a DbgAssertTv instruction for each stack location stored to by * a SpillStack instruction. */ static void insertSpillStackAsserts(IRInstruction& inst, IRFactory* factory) { SSATmp* sp = inst.dst(); auto const vals = inst.srcs().subpiece(2); auto* block = inst.block(); auto pos = block->iteratorTo(&inst); ++pos; for (unsigned i = 0, n = vals.size(); i < n; ++i) { Type t = vals[i]->type(); if (t.subtypeOf(Type::Gen)) { IRInstruction* addr = factory->gen(LdStackAddr, Type::PtrToGen, StackOffset(i), sp); block->insert(pos, addr); IRInstruction* check = factory->gen(DbgAssertPtr, addr->dst()); block->insert(pos, check); } } }
TEST(JumpOpts, optimizeCondTraceExit) { BCMarker marker = BCMarker::Dummy(); IRUnit unit{0}; Block* entry = unit.entry(); Block* taken = unit.defBlock(); Block* fallthru = unit.defBlock(); // A conditional jump that goes to "SyncABIRegs; ReqBindJmp" on both edges // can be coalesced into a ReqBindJmpSomething. auto fp = unit.gen(DefFP, marker); auto sp = unit.gen(DefSP, marker, StackOffset(0), fp->dst()); auto val = unit.gen(Conjure, marker, Type::Bool); auto jmp = unit.gen(JmpZero, marker, taken, val->dst()); jmp->setNext(fallthru); entry->push_back({fp, sp, val, jmp}); auto bcoff1 = 10; auto sync1 = unit.gen(SyncABIRegs, marker, fp->dst(), sp->dst()); auto bind1 = unit.gen(ReqBindJmp, marker, BCOffset(bcoff1)); taken->push_back({sync1, bind1}); auto bcoff2 = 20; auto sync2 = unit.gen(SyncABIRegs, marker, fp->dst(), sp->dst()); auto bind2 = unit.gen(ReqBindJmp, marker, BCOffset(bcoff2)); fallthru->push_back({sync2, bind2}); optimizeJumps(unit); EXPECT_EQ(nullptr, entry->next()); EXPECT_EQ(nullptr, entry->taken()); auto const& back = entry->back(); EXPECT_EQ(ReqBindJmpZero, back.op()); EXPECT_EQ(val->dst(), back.src(0)); auto const* data = back.extra<ReqBindJccData>(); EXPECT_EQ(bcoff1, data->taken); EXPECT_EQ(bcoff2, data->notTaken); }
void TraceBuilder::beginInlining(const Func* target, SSATmp* calleeFP, SSATmp* calleeSP, SSATmp* savedSP, int32_t savedSPOff) { // Saved tracebuilder state will include the "return" fp/sp. // Whatever the current fpValue is is good enough, but we have to be // passed in the StkPtr that represents the stack prior to the // ActRec being allocated. m_spOffset = savedSPOff; m_spValue = savedSP; m_inlineSavedStates.push_back(createState()); /* * Set up the callee state. * * We set m_thisIsAvailable to true on any object method, because we * just don't inline calls to object methods with a null $this. */ m_fpValue = calleeFP; m_spValue = calleeSP; m_thisIsAvailable = target->cls() != nullptr; m_curFunc = cns(target); /* * Keep the outer locals somewhere for isValueAvailable() to know * about their liveness, to help with incref/decref elimination. */ m_callerAvailableValues.insert(m_callerAvailableValues.end(), m_localValues.begin(), m_localValues.end()); m_localValues.clear(); m_localTypes.clear(); m_localValues.resize(target->numLocals(), nullptr); m_localTypes.resize(target->numLocals(), Type::None); gen(ReDefSP, StackOffset(target->numParams()), m_fpValue, m_spValue); }
/* * Insert asserts at various points in the IR. * TODO: t2137231 Insert DbgAssertPtr at points that use or produces a GenPtr */ static void insertAsserts(IRUnit& unit) { postorderWalk(unit, [&](Block* block) { for (auto it = block->begin(), end = block->end(); it != end; ) { IRInstruction& inst = *it; ++it; if (inst.op() == SpillStack) { insertSpillStackAsserts(inst, unit); continue; } if (inst.op() == Call) { SSATmp* sp = inst.dst(); IRInstruction* addr = unit.gen(LdStackAddr, inst.marker(), Type::PtrToGen, StackOffset(0), sp); insertAfter(&inst, addr); insertAfter(addr, unit.gen(DbgAssertPtr, inst.marker(), addr->dst())); continue; } if (!inst.isBlockEnd()) insertRefCountAsserts(inst, unit); } }); }