OffsetSet instrSuccOffsets(Op* opc, const Unit* unit) { OffsetSet succBcOffs; Op* bcStart = (Op*)(unit->entry()); if (!instrIsControlFlow(*opc)) { Offset succOff = opc + instrLen(opc) - bcStart; succBcOffs.insert(succOff); return succBcOffs; } if (instrAllowsFallThru(*opc)) { Offset succOff = opc + instrLen(opc) - bcStart; succBcOffs.insert(succOff); } if (isSwitch(*opc)) { foreachSwitchTarget(opc, [&](Offset& offset) { succBcOffs.insert(offset + opc - bcStart); }); } else { Offset target = instrJumpTarget(bcStart, opc - bcStart); if (target != InvalidAbsoluteOffset) { succBcOffs.insert(target); } } return succBcOffs; }
OffsetSet instrSuccOffsets(PC opc, const Unit* unit) { OffsetSet succBcOffs; auto const bcStart = unit->entry(); auto const op = peek_op(opc); if (!instrIsControlFlow(op)) { Offset succOff = opc + instrLen(opc) - bcStart; succBcOffs.insert(succOff); return succBcOffs; } if (instrAllowsFallThru(op)) { Offset succOff = opc + instrLen(opc) - bcStart; succBcOffs.insert(succOff); } if (isSwitch(op)) { foreachSwitchTarget(opc, [&](Offset offset) { succBcOffs.insert(offset + opc - bcStart); }); } else { Offset target = instrJumpTarget(bcStart, opc - bcStart); if (target != InvalidAbsoluteOffset) { succBcOffs.insert(target); } } return succBcOffs; }
void IRTranslator::translateBranchOp(const NormalizedInstruction& i) { auto const op = i.op(); assert(op == OpJmpZ || op == OpJmpNZ); Offset takenOffset = i.offset() + i.imm[0].u_BA; Offset fallthruOffset = i.offset() + instrLen((Op*)(i.pc())); auto jmpFlags = instrJmpFlags(i); if (i.nextOffset == takenOffset) { always_assert(RuntimeOption::EvalJitPGORegionSelector == "hottrace"); // invert the branch if (op == OpJmpZ) { HHIR_EMIT(JmpNZ, fallthruOffset, jmpFlags); } else { HHIR_EMIT(JmpZ, fallthruOffset, jmpFlags); } return; } if (op == OpJmpZ) { HHIR_EMIT(JmpZ, takenOffset, jmpFlags); } else { HHIR_EMIT(JmpNZ, takenOffset, jmpFlags); } }
void IRTranslator::translateBranchOp(const NormalizedInstruction& i) { auto const op = i.op(); assert(op == OpJmpZ || op == OpJmpNZ); Offset takenOffset = i.offset() + i.imm[0].u_BA; Offset fallthruOffset = i.offset() + instrLen((Op*)(i.pc())); assert(i.breaksTracelet || i.nextOffset == takenOffset || i.nextOffset == fallthruOffset); assert(!i.includeBothPaths || !i.breaksTracelet); if (i.breaksTracelet || i.nextOffset == fallthruOffset) { if (op == OpJmpZ) { HHIR_EMIT(JmpZ, takenOffset, fallthruOffset, i.includeBothPaths); } else { HHIR_EMIT(JmpNZ, takenOffset, fallthruOffset, i.includeBothPaths); } return; } assert(i.nextOffset == takenOffset); // invert the branch if (op == OpJmpZ) { HHIR_EMIT(JmpNZ, fallthruOffset, takenOffset, i.includeBothPaths); } else { HHIR_EMIT(JmpZ, fallthruOffset, takenOffset, i.includeBothPaths); } }
void printInstr(const Unit* unit, PC pc) { std::cout << " " << std::setw(4) << (pc - unit->entry()) << ":" << (isCF(pc) ? "C":" ") << (isTF(pc) ? "T":" ") << (isFF(pc) ? "F":" ") << std::setw(3) << instrLen(pc) << " " << instrToString(pc, unit) << std::endl; }
void printInstr(const Unit* unit, PC pc) { Opcode* op = (Opcode*)pc; std::cout << " " << std::setw(4) << (pc - unit->entry()) << ":" << (isCF(pc) ? "C":" ") << (isTF(pc) ? "T":" ") << (isFF(pc) ? "F":" ") << std::setw(3) << instrLen(op) << " " << instrToString(op, unit) << std::endl; }
// Place internal breakpoints to get out of the current function. This may place // multiple internal breakpoints, and it may place them more than one frame up. // Some instructions can cause PHP to be invoked without an explicit call. A set // which causes a destructor to run, a iteration init which causes an object's // next() method to run, a RetC which causes destructors to run, etc. This // recgonizes such cases and ensures we have internal breakpoints to cover the // destination(s) of such instructions. void CmdFlowControl::setupStepOuts() { // Existing step outs should be cleaned up before making new ones. assert(!hasStepOuts()); auto fp = g_context->getFP(); if (!fp) return; // No place to step out to! Offset returnOffset; bool fromVMEntry; while (!hasStepOuts()) { fp = g_context->getPrevVMState(fp, &returnOffset, nullptr, &fromVMEntry); // If we've run off the top of the stack, just return having setup no // step outs. This will cause cmds like Next and Out to just let the program // run, which is appropriate. if (!fp) break; Unit* returnUnit = fp->m_func->unit(); PC returnPC = returnUnit->at(returnOffset); TRACE(2, "CmdFlowControl::setupStepOuts: at '%s' offset %d opcode %s\n", fp->m_func->fullName()->data(), returnOffset, opcodeToName(*reinterpret_cast<const Op*>(returnPC))); // Don't step out to generated functions, keep looking. if (fp->m_func->line1() == 0) continue; if (fromVMEntry) { TRACE(2, "CmdFlowControl::setupStepOuts: VM entry\n"); // We only execute this for opcodes which invoke more PHP, and that does // not include switches. Thus, we'll have at most two destinations. assert(!isSwitch(*reinterpret_cast<const Op*>(returnPC)) && (numSuccs(reinterpret_cast<const Op*>(returnPC)) <= 2)); // Set an internal breakpoint after the instruction if it can fall thru. if (instrAllowsFallThru(*reinterpret_cast<const Op*>(returnPC))) { Offset nextOffset = returnOffset + instrLen((Op*)returnPC); TRACE(2, "CmdFlowControl: step out to '%s' offset %d (fall-thru)\n", fp->m_func->fullName()->data(), nextOffset); m_stepOut1 = StepDestination(returnUnit, nextOffset); } // Set an internal breakpoint at the target of a control flow instruction. // A good example of a control flow op that invokes PHP is IterNext. if (instrIsControlFlow(*reinterpret_cast<const Op*>(returnPC))) { Offset target = instrJumpTarget(reinterpret_cast<const Op*>(returnPC), 0); if (target != InvalidAbsoluteOffset) { Offset targetOffset = returnOffset + target; TRACE(2, "CmdFlowControl: step out to '%s' offset %d (jump target)\n", fp->m_func->fullName()->data(), targetOffset); m_stepOut2 = StepDestination(returnUnit, targetOffset); } } // If we have no place to step out to, then unwind another frame and try // again. The most common case that leads here is Ret*, which does not // fall-thru and has no encoded target. } else { TRACE(2, "CmdFlowControl: step out to '%s' offset %d\n", fp->m_func->fullName()->data(), returnOffset); m_stepOut1 = StepDestination(returnUnit, returnOffset); } } }
int PCFilter::addRanges(const Unit* unit, const OffsetRangeVec& offsets) { int counter = 0; for (auto range = offsets.cbegin(); range != offsets.cend(); ++range) { for (PC pc = unit->at(range->m_base); pc < unit->at(range->m_past); pc += instrLen((Opcode*)pc)) { addPC(pc); counter++; } } return counter; }
int PCFilter::addRanges(const Unit* unit, const OffsetRangeVec& offsets) { int counter = 0; for (OffsetRangeVec::const_iterator it = offsets.begin(); it != offsets.end(); ++it) { for (PC pc = unit->at(it->m_base); pc < unit->at(it->m_past); pc += instrLen((Opcode*)pc)) { addPC(pc); counter++; } } return counter; }
// Ensure we interpret all code at the given offsets. This sets up a guard for // each piece of tranlated code to ensure we punt ot the interpreter when the // debugger is attached. static void blacklistRangesInJit(const Unit* unit, const OffsetRangeVec& offsets) { for (OffsetRangeVec::const_iterator it = offsets.begin(); it != offsets.end(); ++it) { for (PC pc = unit->at(it->m_base); pc < unit->at(it->m_past); pc += instrLen((Opcode*)pc)) { transl()->addDbgBLPC(pc); } } if (!transl()->addDbgGuards(unit)) { Logger::Warning("Failed to set breakpoints in Jitted code"); } }
/* * This function returns the offset of instruction i's branch target. * This is normally the offset corresponding to the branch being * taken. However, if i does not break a trace and it's followed in * the trace by the instruction in the taken branch, then this * function returns the offset of the i's fall-through instruction. * In that case, the invertCond output argument is set to true; * otherwise it's set to false. */ static Offset getBranchTarget(const NormalizedInstruction& i, bool& invertCond) { assert(instrJumpOffset((Op*)(i.pc())) != nullptr); Offset targetOffset = i.offset() + i.imm[1].u_BA; invertCond = false; if (!i.endsRegion && i.nextOffset == targetOffset) { invertCond = true; Offset fallthruOffset = i.offset() + instrLen((Op*)i.pc()); targetOffset = fallthruOffset; } return targetOffset; }
// Adds a range of PCs to the filter given a collection of offset ranges. // Omit PCs which have opcodes that don't pass the given opcode filter. void PCFilter::addRanges(const Unit* unit, const OffsetRangeVec& offsets, OpcodeFilter isOpcodeAllowed) { for (auto range = offsets.cbegin(); range != offsets.cend(); ++range) { TRACE(3, "\toffsets [%d, %d)\n", range->m_base, range->m_past); for (PC pc = unit->at(range->m_base); pc < unit->at(range->m_past); pc += instrLen(pc)) { if (isOpcodeAllowed(*pc)) { TRACE(3, "\t\tpc %p\n", pc); addPC(pc); } else { TRACE(3, "\t\tpc %p -- skipping (offset %d)\n", pc, unit->offsetOf(pc)); } } } }
// Removes a range of PCs to the filter given a collection of offset ranges. // Omit PCs which have opcodes that don't pass the given opcode filter. void PCFilter::removeRanges(const Unit* unit, const OffsetRangeVec& offsets, OpcodeFilter isOpcodeAllowed) { for (auto range = offsets.cbegin(); range != offsets.cend(); ++range) { TRACE(3, "\toffsets [%d, %d) (remove)\n", range->m_base, range->m_past); for (PC pc = unit->at(range->m_base); pc < unit->at(range->m_past); pc += instrLen((Op*) pc)) { if (isOpcodeAllowed(*reinterpret_cast<const Op*>(pc))) { TRACE(3, "\t\tpc %p (remove)\n", pc); removePC(pc); } else { TRACE(3, "\t\tpc %p -- skipping (offset %d) (remove)\n", pc, unit->offsetOf(pc)); } } } }
// Ensure we interpret all code at the given offsets. This sets up a guard for // each piece of translated code to ensure we punt to the interpreter when the // debugger is attached. static void blacklistRangesInJit(const Unit* unit, const OffsetRangeVec& offsets) { for (OffsetRangeVec::const_iterator it = offsets.begin(); it != offsets.end(); ++it) { for (PC pc = unit->at(it->m_base); pc < unit->at(it->m_past); pc += instrLen((Opcode*)pc)) { transl()->addDbgBLPC(pc); } } if (!transl()->addDbgGuards(unit)) { Logger::Warning("Failed to set breakpoints in Jitted code"); } // In this case, we may be setting a breakpoint in a tracelet which could // already be jitted, and present on the stack. Make sure we don't return // to it so we have a chance to honor breakpoints. g_vmContext->preventReturnsToTC(); }
bool Translator::isSrcKeyInBL(SrcKey sk) { auto unit = sk.unit(); if (unit->isInterpretOnly()) return true; Lock l(m_dbgBlacklistLock); if (m_dbgBLSrcKey.find(sk) != m_dbgBLSrcKey.end()) { return true; } // Loop until the end of the basic block inclusively. This is useful for // function exit breakpoints, which are implemented by blacklisting the RetC // opcodes. PC pc = nullptr; do { pc = (pc == nullptr) ? unit->at(sk.offset()) : pc + instrLen(pc); if (m_dbgBLPC.checkPC(pc)) { m_dbgBLSrcKey.insert(sk); return true; } } while (!opcodeBreaksBB(peek_op(pc))); return false; }
Offset ProfData::transStopBcOff(TransID id) const { Unit* unit = m_transRecs[id]->func()->unit(); Offset lastBcOff = transLastBcOff(id); return lastBcOff + instrLen((Op*)(unit->at(lastBcOff))); }
void cgCall(IRLS& env, const IRInstruction* inst) { auto const sp = srcLoc(env, inst, 0).reg(); auto const fp = srcLoc(env, inst, 1).reg(); auto const extra = inst->extra<Call>(); auto const callee = extra->callee; auto const argc = extra->numParams; auto& v = vmain(env); auto& vc = vcold(env); auto const catchBlock = label(env, inst->taken()); auto const calleeSP = sp[cellsToBytes(extra->spOffset.offset)]; auto const calleeAR = calleeSP + cellsToBytes(argc); v << store{fp, calleeAR + AROFF(m_sfp)}; v << storeli{safe_cast<int32_t>(extra->after), calleeAR + AROFF(m_soff)}; if (extra->fcallAwait) { // This clobbers any flags that might have already been set on the callee // AR (e.g., by SpillFrame), but this is okay because there should never be // any conflicts; see the documentation in act-rec.h. auto const imm = static_cast<int32_t>( ActRec::encodeNumArgsAndFlags(argc, ActRec::Flags::IsFCallAwait) ); v << storeli{imm, calleeAR + AROFF(m_numArgsAndFlags)}; } auto const isNativeImplCall = callee && callee->builtinFuncPtr() && !callee->nativeFuncPtr() && argc == callee->numParams(); if (isNativeImplCall) { // The assumption here is that for builtins, the generated func contains // only a single opcode (NativeImpl), and there are no non-argument locals. if (do_assert) { assertx(argc == callee->numLocals()); assertx(callee->numIterators() == 0); auto addr = callee->getEntry(); while (peek_op(addr) == Op::AssertRATL) { addr += instrLen(addr); } assertx(peek_op(addr) == Op::NativeImpl); assertx(addr + instrLen(addr) == callee->unit()->entry() + callee->past()); } v << store{v.cns(mcg->ustubs().retHelper), calleeAR + AROFF(m_savedRip)}; if (callee->attrs() & AttrMayUseVV) { v << storeqi{0, calleeAR + AROFF(m_invName)}; } v << lea{calleeAR, rvmfp()}; emitCheckSurpriseFlagsEnter(v, vc, fp, Fixup(0, argc), catchBlock); auto const builtinFuncPtr = callee->builtinFuncPtr(); TRACE(2, "Calling builtin preClass %p func %p\n", callee->preClass(), builtinFuncPtr); // We sometimes call this while curFunc() isn't really the builtin, so make // sure to record the sync point as if we are inside the builtin. if (FixupMap::eagerRecord(callee)) { auto const syncSP = v.makeReg(); v << lea{calleeSP, syncSP}; emitEagerSyncPoint(v, callee->getEntry(), rvmtl(), rvmfp(), syncSP); } // Call the native implementation. This will free the locals for us in the // normal case. In the case where an exception is thrown, the VM unwinder // will handle it for us. auto const done = v.makeBlock(); v << vinvoke{CallSpec::direct(builtinFuncPtr), v.makeVcallArgs({{rvmfp()}}), v.makeTuple({}), {done, catchBlock}, Fixup(0, argc)}; env.catch_calls[inst->taken()] = CatchCall::CPP; v = done; // The native implementation already put the return value on the stack for // us, and handled cleaning up the arguments. We have to update the frame // pointer and the stack pointer, and load the return value into the return // register so the trace we are returning to has it where it expects. // TODO(#1273094): We should probably modify the actual builtins to return // values via registers using the C ABI and do a reg-to-reg move. loadTV(v, inst->dst(), dstLoc(env, inst, 0), rvmfp()[AROFF(m_r)], true); v << load{rvmfp()[AROFF(m_sfp)], rvmfp()}; emitRB(v, Trace::RBTypeFuncExit, callee->fullName()->data()); return; } v << lea{calleeAR, rvmfp()}; if (RuntimeOption::EvalHHIRGenerateAsserts) { v << syncvmsp{v.cns(0x42)}; constexpr uint64_t kUninitializedRIP = 0xba5eba11acc01ade; emitImmStoreq(v, kUninitializedRIP, rvmfp()[AROFF(m_savedRip)]); } // Emit a smashable call that initially calls a recyclable service request // stub. The stub and the eventual targets take rvmfp() as an argument, // pointing to the callee ActRec. auto const target = callee ? mcg->ustubs().immutableBindCallStub : mcg->ustubs().bindCallStub; auto const done = v.makeBlock(); v << callphp{target, php_call_regs(), {{done, catchBlock}}}; env.catch_calls[inst->taken()] = CatchCall::PHP; v = done; auto const dst = dstLoc(env, inst, 0); v << defvmret{dst.reg(0), dst.reg(1)}; }
void print_func_body(Output& out, const FuncInfo& finfo) { auto const func = finfo.func; auto lblIter = begin(finfo.labels); auto const lblStop = end(finfo.labels); auto ehIter = begin(finfo.ehStarts); auto const ehStop = end(finfo.ehStarts); auto bcIter = func->unit()->at(func->base()); auto const bcStop = func->unit()->at(func->past()); min_priority_queue<Offset> ehEnds; while (bcIter != bcStop) { auto const pop = reinterpret_cast<const Op*>(bcIter); auto const off = func->unit()->offsetOf(pop); // First, close any protected EH regions that are past-the-end at // this offset. while (!ehEnds.empty() && ehEnds.top() == off) { ehEnds.pop(); out.dec_indent(); out.fmtln("}}"); } // Next, open any new protected regions that start at this offset. for (; ehIter != ehStop && ehIter->first == off; ++ehIter) { auto const info = finfo.ehInfo.find(ehIter->second); always_assert(info != end(finfo.ehInfo)); match<void>( info->second, [&] (const EHCatch& catches) { out.indent(); out.fmt(".try_catch"); for (auto& kv : catches.blocks) { out.fmt(" ({} {})", kv.first, kv.second); } out.fmt(" {{"); out.nl(); }, [&] (const EHFault& fault) { out.fmtln(".try_fault {} {{", fault.label); } ); out.inc_indent(); ehEnds.push(ehIter->second->m_past); } // Then, print labels if we have any. This order keeps the labels // from dangling on weird sides of .try_fault or .try_catch // braces. while (lblIter != lblStop && lblIter->first < off) ++lblIter; if (lblIter != lblStop && lblIter->first == off) { out.dec_indent(); out.fmtln("{}:", lblIter->second); out.inc_indent(); } print_instr(out, finfo, bcIter); bcIter += instrLen(reinterpret_cast<const Op*>(bcIter)); } }
void print_instr(Output& out, const FuncInfo& finfo, PC pc) { auto const startPc = pc; auto rel_label = [&] (Offset off) { auto const tgt = startPc - finfo.unit->at(0) + off; return jmp_label(finfo, tgt); }; auto print_minstr = [&] { auto const immVec = ImmVector::createFromStream(pc); pc += immVec.size() + sizeof(int32_t) + sizeof(int32_t); auto vec = immVec.vec(); auto const lcode = static_cast<LocationCode>(*vec++); out.fmt(" <{}", locationCodeString(lcode)); if (numLocationCodeImms(lcode)) { always_assert(numLocationCodeImms(lcode) == 1); out.fmt(":${}", loc_name(finfo, decodeVariableSizeImm(&vec))); } while (vec < pc) { auto const mcode = static_cast<MemberCode>(*vec++); out.fmt(" {}", memberCodeString(mcode)); auto const imm = [&] { return decodeMemberCodeImm(&vec, mcode); }; switch (memberCodeImmType(mcode)) { case MCodeImm::None: break; case MCodeImm::Local: out.fmt(":${}", loc_name(finfo, imm())); break; case MCodeImm::String: out.fmt(":{}", escaped(finfo.unit->lookupLitstrId(imm()))); break; case MCodeImm::Int: out.fmt(":{}", imm()); break; } } assert(vec == pc); out.fmt(">"); }; auto print_switch = [&] { auto const vecLen = decode<int32_t>(pc); out.fmt(" <"); for (auto i = int32_t{0}; i < vecLen; ++i) { auto const off = decode<Offset>(pc); FTRACE(1, "sw label: {}\n", off); out.fmt("{}{}", i != 0 ? " " : "", rel_label(off)); } out.fmt(">"); }; auto print_sswitch = [&] { auto const vecLen = decode<int32_t>(pc); out.fmt(" <"); for (auto i = int32_t{0}; i < vecLen; ++i) { auto const strId = decode<Id>(pc); auto const offset = decode<Offset>(pc); out.fmt("{}{}:{}", i != 0 ? " " : "", strId == -1 ? "-" : escaped(finfo.unit->lookupLitstrId(strId)), rel_label(offset) ); } out.fmt(">"); }; auto print_itertab = [&] { auto const vecLen = decode<int32_t>(pc); out.fmt(" <"); for (auto i = int32_t{0}; i < vecLen; ++i) { auto const kind = static_cast<IterKind>(decode<int32_t>(pc)); auto const id = decode<int32_t>(pc); auto const kindStr = [&]() -> const char* { switch (kind) { case KindOfIter: return "(Iter)"; case KindOfMIter: return "(MIter)"; case KindOfCIter: return "(CIter)"; } not_reached(); }(); out.fmt("{}{} {}", i != 0 ? ", " : "", kindStr, id); } out.fmt(">"); }; auto print_stringvec = [&] { auto const vecLen = decode<int32_t>(pc); out.fmt(" <"); for (auto i = uint32_t{0}; i < vecLen; ++i) { auto const str = finfo.unit->lookupLitstrId(decode<int32_t>(pc)); out.fmt("{}{}", i != 0 ? " " : "", escaped(str)); } out.fmt(">"); }; #define IMM_MA print_minstr(); #define IMM_BLA print_switch(); #define IMM_SLA print_sswitch(); #define IMM_ILA print_itertab(); #define IMM_IVA out.fmt(" {}", decodeVariableSizeImm(&pc)); #define IMM_I64A out.fmt(" {}", decode<int64_t>(pc)); #define IMM_LA out.fmt(" ${}", loc_name(finfo, decodeVariableSizeImm(&pc))); #define IMM_IA out.fmt(" {}", decodeVariableSizeImm(&pc)); #define IMM_DA out.fmt(" {}", decode<double>(pc)); #define IMM_SA out.fmt(" {}", \ escaped(finfo.unit->lookupLitstrId(decode<Id>(pc)))); #define IMM_AA out.fmt(" @A_{}", decode<Id>(pc)); #define IMM_BA out.fmt(" {}", rel_label(decode<Offset>(pc))); #define IMM_OA(ty) out.fmt(" {}", \ subopToName(static_cast<ty>(decode<uint8_t>(pc)))); #define IMM_VSA print_stringvec(); #define IMM_NA #define IMM_ONE(x) IMM_##x #define IMM_TWO(x,y) IMM_ONE(x) IMM_ONE(y) #define IMM_THREE(x,y,z) IMM_TWO(x,y) IMM_ONE(z) #define IMM_FOUR(x,y,z,l) IMM_THREE(x,y,z) IMM_ONE(l) out.indent(); #define O(opcode, imms, ...) \ case Op::opcode: \ ++pc; \ out.fmt("{}", #opcode); \ IMM_##imms \ break; switch (*reinterpret_cast<const Op*>(pc)) { OPCODES } #undef O assert(pc == startPc + instrLen(reinterpret_cast<const Op*>(startPc))); #undef IMM_NA #undef IMM_ONE #undef IMM_TWO #undef IMM_THREE #undef IMM_FOUR #undef IMM_MA #undef IMM_BLA #undef IMM_SLA #undef IMM_ILA #undef IMM_IVA #undef IMM_I64A #undef IMM_LA #undef IMM_IA #undef IMM_DA #undef IMM_SA #undef IMM_AA #undef IMM_BA #undef IMM_OA #undef IMM_VSA out.nl(); }
FuncInfo find_func_info(const Func* func) { auto finfo = FuncInfo(func->unit(), func); auto label_num = uint32_t{0}; auto gen_label = [&] (const char* kind) { return folly::format("{}{}", kind, label_num++).str(); }; auto add_target = [&] (const char* kind, Offset off) -> std::string { auto it = finfo.labels.find(off); if (it != end(finfo.labels)) return it->second; auto const label = gen_label(kind); finfo.labels[off] = label; return label; }; auto find_jump_targets = [&] { auto it = func->unit()->at(func->base()); auto const stop = func->unit()->at(func->past()); auto const bcBase = reinterpret_cast<const Op*>(func->unit()->at(0)); for (; it != stop; it += instrLen(reinterpret_cast<const Op*>(it))) { auto const pop = reinterpret_cast<const Op*>(it); auto const off = func->unit()->offsetOf(pop); if (isSwitch(*pop)) { foreachSwitchTarget(pop, [&] (Offset off) { add_target("L", pop - bcBase + off); }); continue; } auto const target = instrJumpTarget(bcBase, off); if (target != InvalidAbsoluteOffset) { add_target("L", target); continue; } } }; auto find_eh_entries = [&] { for (auto& eh : func->ehtab()) { finfo.ehInfo[&eh] = [&]() -> EHInfo { switch (eh.m_type) { case EHEnt::Type::Catch: { auto catches = EHCatch {}; for (auto& kv : eh.m_catches) { auto const clsName = func->unit()->lookupLitstrId(kv.first); catches.blocks[clsName->data()] = add_target("C", kv.second); } return catches; } case EHEnt::Type::Fault: return EHFault { add_target("F", eh.m_fault) }; } not_reached(); }(); finfo.ehStarts.emplace_back(eh.m_base, &eh); } }; auto find_dv_entries = [&] { for (auto i = uint32_t{0}; i < func->numParams(); ++i) { auto& param = func->params()[i]; if (param.hasDefaultValue()) { add_target("DV", func->params()[i].funcletOff()); } } }; find_jump_targets(); find_eh_entries(); find_dv_entries(); return finfo; }
void print_instr(Output& out, const FuncInfo& finfo, PC pc) { auto const startPc = pc; auto rel_label = [&] (Offset off) { auto const tgt = startPc - finfo.unit->at(0) + off; return jmp_label(finfo, tgt); }; auto print_switch = [&] { auto const vecLen = decode<int32_t>(pc); out.fmt(" <"); for (auto i = int32_t{0}; i < vecLen; ++i) { auto const off = decode<Offset>(pc); FTRACE(1, "sw label: {}\n", off); out.fmt("{}{}", i != 0 ? " " : "", rel_label(off)); } out.fmt(">"); }; auto print_sswitch = [&] { auto const vecLen = decode<int32_t>(pc); out.fmt(" <"); for (auto i = int32_t{0}; i < vecLen; ++i) { auto const strId = decode<Id>(pc); auto const offset = decode<Offset>(pc); out.fmt("{}{}:{}", i != 0 ? " " : "", strId == -1 ? "-" : escaped(finfo.unit->lookupLitstrId(strId)), rel_label(offset) ); } out.fmt(">"); }; auto print_itertab = [&] { auto const vecLen = decode<int32_t>(pc); out.fmt(" <"); for (auto i = int32_t{0}; i < vecLen; ++i) { auto const kind = static_cast<IterKind>(decode<int32_t>(pc)); auto const id = decode<int32_t>(pc); auto const kindStr = [&]() -> const char* { switch (kind) { case KindOfIter: return "(Iter)"; case KindOfMIter: return "(MIter)"; case KindOfCIter: return "(CIter)"; } not_reached(); }(); out.fmt("{}{} {}", i != 0 ? ", " : "", kindStr, id); } out.fmt(">"); }; auto print_stringvec = [&] { auto const vecLen = decode<int32_t>(pc); out.fmt(" <"); for (auto i = uint32_t{0}; i < vecLen; ++i) { auto const str = finfo.unit->lookupLitstrId(decode<int32_t>(pc)); out.fmt("{}{}", i != 0 ? " " : "", escaped(str)); } out.fmt(">"); }; #define IMM_BLA print_switch(); #define IMM_SLA print_sswitch(); #define IMM_ILA print_itertab(); #define IMM_IVA out.fmt(" {}", decodeVariableSizeImm(&pc)); #define IMM_I64A out.fmt(" {}", decode<int64_t>(pc)); #define IMM_LA out.fmt(" {}", loc_name(finfo, decodeVariableSizeImm(&pc))); #define IMM_IA out.fmt(" {}", decodeVariableSizeImm(&pc)); #define IMM_DA out.fmt(" {}", decode<double>(pc)); #define IMM_SA out.fmt(" {}", \ escaped(finfo.unit->lookupLitstrId(decode<Id>(pc)))); #define IMM_RATA out.fmt(" {}", show(decodeRAT(finfo.unit, pc))); #define IMM_AA out.fmt(" @A_{}", decode<Id>(pc)); #define IMM_BA out.fmt(" {}", rel_label(decode<Offset>(pc))); #define IMM_OA(ty) out.fmt(" {}", \ subopToName(static_cast<ty>(decode<uint8_t>(pc)))); #define IMM_VSA print_stringvec(); #define IMM_KA out.fmt(" {}", show(decode_member_key(pc, finfo.unit))); #define IMM_NA #define IMM_ONE(x) IMM_##x #define IMM_TWO(x,y) IMM_ONE(x) IMM_ONE(y) #define IMM_THREE(x,y,z) IMM_TWO(x,y) IMM_ONE(z) #define IMM_FOUR(x,y,z,l) IMM_THREE(x,y,z) IMM_ONE(l) out.indent(); #define O(opcode, imms, ...) \ case Op::opcode: \ ++pc; \ out.fmt("{}", #opcode); \ IMM_##imms \ break; switch (peek_op(pc)) { OPCODES } #undef O assert(pc == startPc + instrLen(startPc)); #undef IMM_NA #undef IMM_ONE #undef IMM_TWO #undef IMM_THREE #undef IMM_FOUR #undef IMM_BLA #undef IMM_SLA #undef IMM_ILA #undef IMM_IVA #undef IMM_I64A #undef IMM_LA #undef IMM_IA #undef IMM_DA #undef IMM_SA #undef IMM_RATA #undef IMM_AA #undef IMM_BA #undef IMM_OA #undef IMM_VSA #undef IMM_KA out.nl(); }
std::string show(const IRGS& irgs) { std::ostringstream out; auto header = [&](const std::string& str) { out << folly::format("+{:-^102}+\n", str); }; const int32_t frameCells = irgen::resumed(irgs) ? 0 : irgen::curFunc(irgs)->numSlotsInFrame(); auto const stackDepth = irgs.irb->syncedSpLevel().offset - frameCells; assertx(stackDepth >= 0); auto spOffset = stackDepth; auto elem = [&](const std::string& str) { out << folly::format("| {:<100} |\n", folly::format("{:>2}: {}", stackDepth - spOffset, str)); assertx(spOffset > 0); --spOffset; }; auto fpi = irgen::curFunc(irgs)->findFPI(irgen::bcOff(irgs)); auto checkFpi = [&]() { if (fpi && spOffset + frameCells == fpi->m_fpOff) { auto fpushOff = fpi->m_fpushOff; auto after = fpushOff + instrLen(irgen::curUnit(irgs)->at(fpushOff)); std::ostringstream msg; msg << "ActRec from "; irgen::curUnit(irgs)->prettyPrint( msg, Unit::PrintOpts().range(fpushOff, after) .noLineNumbers() .indent(0) .noFuncs() ); auto msgStr = msg.str(); assertx(msgStr.back() == '\n'); msgStr.erase(msgStr.size() - 1); for (unsigned i = 0; i < kNumActRecCells; ++i) elem(msgStr); fpi = fpi->m_parentIndex != -1 ? &irgen::curFunc(irgs)->fpitab()[fpi->m_parentIndex] : nullptr; return true; } return false; }; header(folly::format(" {} stack element(s): ", stackDepth).str()); for (auto i = 0; spOffset > 0; ) { assertx(i < irgen::curFunc(irgs)->maxStackCells()); if (checkFpi()) { i += kNumActRecCells; continue; } auto const stkTy = irgs.irb->stackType( irgen::offsetFromIRSP(irgs, BCSPOffset{i}), DataTypeGeneric ); auto const stkVal = irgs.irb->stackValue( irgen::offsetFromIRSP(irgs, BCSPOffset{i}), DataTypeGeneric ); std::string elemStr; if (stkTy == TStkElem) { elemStr = "unknown"; } else if (stkVal) { elemStr = stkVal->inst()->toString(); } else { elemStr = stkTy.toString(); } auto const predicted = irgen::predictedTypeFromStack(irgs, BCSPOffset{i}); if (predicted < stkTy) { elemStr += folly::sformat(" (predict: {})", predicted); } elem(elemStr); ++i; } header(""); out << "\n"; header(folly::format(" {} local(s) ", irgen::curFunc(irgs)->numLocals()).str()); for (unsigned i = 0; i < irgen::curFunc(irgs)->numLocals(); ++i) { auto const localValue = irgs.irb->localValue(i, DataTypeGeneric); auto const localTy = localValue ? localValue->type() : irgs.irb->localType(i, DataTypeGeneric); auto str = localValue ? localValue->inst()->toString() : localTy.toString(); auto const predicted = irgs.irb->predictedLocalType(i); if (predicted < localTy) str += folly::sformat(" (predict: {})", predicted); if (localTy <= TBoxedCell) { auto const pred = irgs.irb->predictedInnerType(i); if (pred != TBottom) { str += folly::sformat(" (predict inner: {})", pred.toString()); } } out << folly::format("| {:<100} |\n", folly::format("{:>2}: {}", i, str)); } header(""); return out.str(); }
void Unit::prettyPrint(std::ostream &out, size_t startOffset, size_t stopOffset) const { std::map<Offset,const Func*> funcMap; for (FuncRange fr(funcs()); !fr.empty();) { const Func* f = fr.popFront(); funcMap[f->base()] = f; } for (PreClassPtrVec::const_iterator it = m_preClasses.begin(); it != m_preClasses.end(); ++it) { Func* const* methods = (*it)->methods(); size_t const numMethods = (*it)->numMethods(); for (size_t i = 0; i < numMethods; ++i) { funcMap[methods[i]->base()] = methods[i]; } } std::map<Offset,const Func*>::const_iterator funcIt = funcMap.lower_bound(startOffset); const uchar* it = &m_bc[startOffset]; int prevLineNum = -1; MetaHandle metaHand; while (it < &m_bc[stopOffset]) { ASSERT(funcIt == funcMap.end() || funcIt->first >= offsetOf(it)); if (funcIt != funcMap.end() && funcIt->first == offsetOf(it)) { out.put('\n'); funcIt->second->prettyPrint(out); ++funcIt; } int lineNum = getLineNumber(offsetOf(it)); if (lineNum != prevLineNum) { out << " // line " << lineNum << std::endl; prevLineNum = lineNum; } out << " " << std::setw(4) << (it - m_bc) << ": "; out << instrToString((Opcode*)it, (Unit*)this); if (metaHand.findMeta(this, offsetOf(it))) { out << " #"; Unit::MetaInfo info; while (metaHand.nextArg(info)) { int arg = info.m_arg & ~MetaInfo::VectorArg; const char *argKind = info.m_arg & MetaInfo::VectorArg ? "M" : ""; switch (info.m_kind) { case Unit::MetaInfo::DataType: out << " i" << argKind << arg << ":t=" << (int)info.m_data; break; case Unit::MetaInfo::String: { const StringData* sd = this->lookupLitstrId(info.m_data); out << " i" << argKind << arg << ":s=" << std::string(sd->data(), sd->size()); break; } case Unit::MetaInfo::Class: { const StringData* sd = this->lookupLitstrId(info.m_data); out << " i" << argKind << arg << ":c=" << sd->data(); break; } case Unit::MetaInfo::NopOut: out << " Nop"; break; case Unit::MetaInfo::GuardedThis: out << " GuardedThis"; break; case Unit::MetaInfo::None: ASSERT(false); break; } } } out << std::endl; it += instrLen((Opcode*)it); } }
size_t OfflineX86Code::printBCMapping(BCMappingInfo bcMappingInfo, size_t currBC, TCA ip) { TransBCMapping curr, next; TCA x86Start, x86Stop; auto const& bcMap = bcMappingInfo.bcMapping; curr = next = TransBCMapping { MD5(), 0, 0, 0, 0 }; x86Start = x86Stop = 0; // Account for the sentinel. size_t mappingSize = bcMap.size() - 1; // Starting from currBC, find the next bytecode with a non-empty x86 range // that could potentially correspond to instruction ip. for (; currBC < mappingSize; ++currBC) { curr = bcMap[currBC]; next = bcMap[currBC + 1]; switch (bcMappingInfo.tcRegion) { case TCRHot: case TCRMain: case TCRProfile: x86Start = curr.aStart; x86Stop = next.aStart; break; case TCRCold: x86Start = curr.acoldStart; x86Stop = next.acoldStart; break; case TCRFrozen: x86Start = curr.afrozenStart; x86Stop = next.afrozenStart; break; default: error("printBCMapping: unexpected TCRegion"); } always_assert(x86Start <= x86Stop); if (x86Start >= ip && x86Start < x86Stop) break; } if (currBC < mappingSize && x86Start == ip) { if (auto currUnit = g_repo->getUnit(curr.md5)) { auto bcPast = curr.bcStart + instrLen(currUnit->at(curr.bcStart)); currUnit->prettyPrint(std::cout, Unit::PrintOpts().range(curr.bcStart, bcPast)); } else { std::cout << folly::format( "<<< couldn't find unit {} to print bytecode at offset {} >>>\n", curr.md5, curr.bcStart); } currBC++; } return currBC; }