in hphp/runtime/vm/jit/frame-state.cpp [260:642]
void FrameStateMgr::update(const IRInstruction* inst) {
ITRACE(3, "FrameStateMgr::update processing {}\n", *inst);
Indent _i;
if (auto const taken = inst->taken()) {
// If CheckMROProp fails, we know the bit is false, so propagate
// that to the taken edge.
auto const oldMROProp = cur().mROProp;
if (inst->is(CheckMROProp)) cur().mROProp = TriBool::No;
/*
* TODO(#4323657): we should make this assertion for all non-empty blocks
* (exits in addition to catches). It would fail right now for exit
* traces.
*
* If you hit this assertion: you've created a catch block and then
* modified tracked state, then generated a potentially exception-throwing
* instruction using that catch block as a target. This is not allowed.
*/
if (debug && taken->isCatch()) {
auto const tmp = save(taken);
always_assert_flog(
!tmp,
"catch block B{} had non-matching in state",
taken->id()
);
}
// When we're building the IR, we append a conditional jump after
// generating its target block: see emitJmpCondHelper, where we
// call makeExit() before gen(JmpZero). It doesn't make sense to
// update the target block state at this point, so don't. The
// state doesn't have this problem during optimization passes,
// because we'll always process the jump before the target block.
if (taken->empty()) save(taken);
// Restore the original value since we're going to process the
// next edge now.
cur().mROProp = oldMROProp;
}
auto const killIterLocals = [&](const std::initializer_list<uint32_t>& ids) {
for (auto id : ids) {
setValueAndSyncMBase(loc(id), nullptr, false);
}
};
auto const setFrameCtx = [&] (const SSATmp* fp, SSATmp* ctx) {
for (auto& frame : m_stack) {
if (frame.fpValue != fp) continue;
frame.ctx = ctx;
frame.ctxType = ctx->type();
}
};
switch (inst->op()) {
case BeginInlining: trackBeginInlining(inst); break;
case EndInlining: trackEndInlining(); break;
case InlineCall: trackInlineCall(inst); break;
case StFrameCtx:
setFrameCtx(inst->src(0), inst->src(1));
break;
case Call:
{
assertx(cur().checkMInstrStateDead());
auto const extra = inst->extra<Call>();
// Remove tracked state for the slots for args and the actrec.
uint32_t numCells = kNumActRecCells + extra->numInputs();
for (auto i = uint32_t{0}; i < numCells; ++i) {
setValue(stk(extra->spOffset + i), nullptr);
}
// Mark out parameter locations as being at least InitCell
auto const callee = inst->src(2)->hasConstVal(TFunc)
? inst->src(2)->funcVal() : nullptr;
auto const base = extra->spOffset + numCells;
for (auto i = uint32_t{0}; i < extra->numOut; ++i) {
auto const ty = callee && callee->takesInOutParams()
? irgen::callOutType(callee, i)
: TInitCell;
setType(stk(base + i), ty);
}
// We consider popping an ActRec and args to be synced to memory.
assertx(cur().bcSPOff == inst->marker().bcSPOff());
cur().bcSPOff -= numCells;
}
break;
case ContEnter:
assertx(cur().checkMInstrStateDead());
break;
case DefFP:
case DefFuncEntryFP:
cur().fpValue = inst->dst();
cur().fixupFPValue = inst->dst();
break;
case InitFrame:
/* The last opcode of prologues. Does not modify any defined frame. */
break;
case EnterPrologue:
cur().stublogue = true;
break;
case ExitPrologue:
cur().stublogue = false;
break;
case RetCtrl:
uninitStack();
cur().fpValue = nullptr;
cur().fixupFPValue = nullptr;
break;
case DefFrameRelSP:
case DefRegSP: {
auto const data = inst->extra<DefStackData>();
initStack(inst->dst(), data->irSPOff, data->bcSPOff);
break;
}
case LdMem:
pointerLoad(inst->src(0), inst->dst());
break;
case StMem:
case StMemMeta:
pointerStore(inst->src(0), inst->src(1));
break;
case LdStk: {
auto const offset = inst->extra<LdStk>()->offset;
// Nearly all callers of setValue() for stack slots represent a
// modification of the stack, so it sets stackModified. LdStk is the one
// exception, so we compensate for that here.
auto const oldModified = cur().stackModified;
setValueAndSyncMBase(stk(offset), inst->dst(), true);
cur().stackModified = oldModified;
break;
}
case StStk:
case StStkMeta:
setValueAndSyncMBase(
stk(inst->extra<IRSPRelOffsetData>()->offset),
inst->src(1),
false
);
break;
case CheckType:
case AssertType: {
auto const oldVal = inst->src(0);
auto const newVal = inst->dst();
for (auto& frame : m_stack) {
for (auto& it : frame.locals) {
refineValue(it.second, oldVal, newVal);
}
for (auto& it : frame.stack) {
refineValue(it.second, oldVal, newVal);
}
if (frame.ctx && canonical(frame.ctx) == canonical(inst->src(0))) {
frame.ctx = inst->dst();
frame.ctxType = inst->dst()->type();
}
}
// MInstrState can only be live for the current frame.
refineMBaseValue(oldVal, newVal);
auto const canonOldVal = canonical(oldVal);
if (cur().mTempBase.value &&
canonOldVal == canonical(cur().mTempBase.value)) {
setMTempBase(newVal);
}
if (cur().mbr.ptr && canonOldVal == canonical(cur().mbr.ptr)) {
setMBR(newVal, true);
}
break;
}
case CheckTypeMem:
pointerRefine(inst->src(0), inst->typeParam());
break;
case CheckInitMem:
pointerRefine(inst->src(0), TInitCell);
break;
case AssertLoc:
case CheckLoc: {
auto const id = inst->extra<LocalId>()->locId;
refineTypeAndSyncMBase(
loc(id),
inst->typeParam(),
TypeSource::makeGuard(inst)
);
break;
}
case AssertStk:
case CheckStk:
refineTypeAndSyncMBase(
stk(inst->extra<IRSPRelOffsetData>()->offset),
inst->typeParam(),
TypeSource::makeGuard(inst)
);
break;
case AssertMBase:
refineTypeAndSyncMBase(
Location::MBase{},
inst->typeParam(),
TypeSource::makeGuard(inst)
);
break;
case CheckMBase:
setMBR(inst->src(0), true);
refineTypeAndSyncMBase(
Location::MBase{},
inst->typeParam(),
TypeSource::makeGuard(inst)
);
break;
case StLoc:
case StLocMeta:
setValueAndSyncMBase(
loc(inst->extra<LocalId>()->locId),
inst->src(1),
false
);
break;
case LdLoc: {
auto const id = inst->extra<LdLoc>()->locId;
setValueAndSyncMBase(loc(id), inst->dst(), true);
break;
}
case EndCatch:
/*
* Hitting this means we've messed up with syncing the stack in a catch
* trace. If the stack isn't clean or doesn't match the marker's irSPOff,
* the unwinder won't see what we expect.
*/
always_assert_flog(
inst->extra<EndCatch>()->offset.to<SBInvOffset>(cur().irSPOff) ==
inst->marker().bcSPOff(),
"EndCatch stack didn't seem right:\n"
" spOff: {}\n"
" EndCatch offset: {}\n"
" marker's spOff: {}\n",
cur().irSPOff.offset,
inst->extra<EndCatch>()->offset.offset,
inst->marker().bcSPOff().offset
);
break;
case InterpOne:
case InterpOneCF: {
auto const& extra = *inst->extra<InterpOneData>();
assertx(!extra.smashesAllLocals || extra.nChangedLocals == 0);
if (extra.smashesAllLocals) {
clearLocals();
} else {
auto it = extra.changedLocals;
auto const end = it + extra.nChangedLocals;
for (; it != end; ++it) {
auto& local = *it;
setType(loc(local.id), local.type);
}
}
// Offset of the bytecode stack top relative to the IR stack pointer.
auto const bcSPOff = extra.spOffset;
// Clear tracked information for slots pushed and popped.
for (auto i = uint32_t{0}; i < extra.cellsPopped; ++i) {
setValue(stk(bcSPOff + i), nullptr);
}
for (auto i = uint32_t{0}; i < extra.cellsPushed; ++i) {
setValue(stk(bcSPOff + extra.cellsPopped - 1 - i), nullptr);
}
auto adjustedTop = bcSPOff + extra.cellsPopped - extra.cellsPushed;
switch (extra.opcode) {
case Op::CGetL2:
setType(stk(adjustedTop + 1), inst->typeParam());
break;
default:
// We don't track cells pushed by interp one except the top of the
// stack, aside from the above special cases.
if (inst->hasTypeParam()) {
auto const instrInfo = getInstrInfo(extra.opcode);
if (instrInfo.out & InstrFlags::Stack1) {
setType(stk(adjustedTop), inst->typeParam());
}
}
break;
}
cur().bcSPOff += extra.cellsPushed;
cur().bcSPOff -= extra.cellsPopped;
// Be conservative and drop minstr state
clearMInstr();
break;
}
case IterInit:
case LIterInit:
case IterNext:
case LIterNext: {
auto const& args = inst->extra<IterData>()->args;
assertx(!args.hasKey());
killIterLocals({safe_cast<uint32_t>(args.valId)});
break;
}
case IterInitK:
case LIterInitK:
case IterNextK:
case LIterNextK: {
auto const& args = inst->extra<IterData>()->args;
assertx(args.hasKey());
killIterLocals({safe_cast<uint32_t>(args.keyId),
safe_cast<uint32_t>(args.valId)});
break;
}
case LdMBase:
setMBR(inst->dst(), true);
break;
case StMBase:
setMBR(inst->src(0), false);
break;
case StMROProp:
cur().mROProp = inst->src(0)->hasConstVal()
? yesOrNo(inst->src(0)->boolVal())
: TriBool::Maybe;
break;
case CheckMROProp:
cur().mROProp = TriBool::Yes;
break;
case FinishMemberOp:
clearMInstr();
break;
case CallBuiltin:
// CallBuiltin uses the same memory as mTempBase, so it had better
// be dead.
assertx(cur().checkMInstrStateDead());
break;
case PropX:
case PropDX:
case PropQ:
cur().mROProp |= TriBool::Yes;
if (!inst->src(2)->isA(TNullptr)) {
assertx(inst->src(2)->isA(TPtrToMISTemp));
pointerStore(inst->src(2), nullptr);
}
break;
default:
// Use precise Minstr effects if we can
if (hasMInstrBaseEffects(*inst)) {
handleMInstr(inst);
} else {
// Handle everything else conservatively according to its memory
// effects
handleConservatively(inst);
}
break;
}
assertx(checkInvariants());
}