/** * instrNumPops() returns the number of values consumed from the stack * for a given push/pop instruction. For peek/poke instructions, this * function returns 0. */ int instrNumPops(PC pc) { static const int32_t numberOfPops[] = { #define NOV 0 #define ONE(...) 1 #define TWO(...) 2 #define THREE(...) 3 #define FOUR(...) 4 #define MMANY -1 #define C_MMANY -2 #define V_MMANY -2 #define R_MMANY -2 #define MFINAL -3 #define FMANY -3 #define CVMANY -3 #define CVUMANY -3 #define CMANY -3 #define SMANY -1 #define IDX_A -4 #define O(name, imm, pop, push, flags) pop, OPCODES #undef NOV #undef ONE #undef TWO #undef THREE #undef FOUR #undef MMANY #undef C_MMANY #undef V_MMANY #undef R_MMANY #undef MFINAL #undef FMANY #undef CVMANY #undef CVUMANY #undef CMANY #undef SMANY #undef IDX_A #undef O }; int n = numberOfPops[size_t(peek_op(pc))]; // For most instructions, we know how many values are popped based // solely on the opcode if (n >= 0) return n; // BaseSC and BaseSL remove an A that may be on the top of the stack or one // element below the top, depending on the second immediate. if (n == -4) return getImm(pc, 1).u_IVA + 1; // FCall, NewPackedArray, and final member operations specify how many values // are popped in their first immediate if (n == -3) return getImm(pc, 0).u_IVA; // For instructions with vector immediates, we have to scan the // contents of the vector immediate to determine how many values // are popped assert(n == -1 || n == -2); ImmVector iv = getImmVector(pc); // Count the number of values on the stack accounted for by the // ImmVector's location and members int k = iv.numStackValues(); // If this instruction also takes a RHS, count that too if (n == -2) ++k; return k; }
/** * instrNumPops() returns the number of values consumed from the stack * for a given push/pop instruction. For peek/poke instructions, this * function returns 0. */ int instrNumPops(PC pc) { static const int32_t numberOfPops[] = { #define NOV 0 #define ONE(...) 1 #define TWO(...) 2 #define THREE(...) 3 #define FOUR(...) 4 #define MFINAL -3 #define F_MFINAL -6 #define C_MFINAL -5 #define V_MFINAL C_MFINAL #define FMANY -3 #define UFMANY -4 #define CVUMANY -3 #define CMANY -3 #define SMANY -1 #define O(name, imm, pop, push, flags) pop, OPCODES #undef NOV #undef ONE #undef TWO #undef THREE #undef FOUR #undef MFINAL #undef F_MFINAL #undef C_MFINAL #undef V_MFINAL #undef FMANY #undef UFMANY #undef CVUMANY #undef CMANY #undef SMANY #undef O }; auto const op = peek_op(pc); int n = numberOfPops[size_t(op)]; // For most instructions, we know how many values are popped based // solely on the opcode if (n >= 0) return n; // FCall, NewPackedArray, and some final member operations specify how many // values are popped in their first immediate if (n == -3) return getImm(pc, 0).u_IVA; // FCallM, FCallDM, and FCallUnpackM pop uninit values from the stack and // push multiple returned values. if (n == -4) return getImm(pc, 0).u_IVA + getImm(pc, 1).u_IVA - 1; // FPassM final operations have paramId as imm 0 and stackCount as imm1 if (n == -6) return getImm(pc, 1).u_IVA; // Other final member operations pop their first immediate + 1 if (n == -5) return getImm(pc, 0).u_IVA + 1; // For instructions with vector immediates, we have to scan the contents of // the vector immediate to determine how many values are popped assertx(n == -1); ImmVector iv = getImmVector(pc); int k = iv.numStackValues(); return k; }
/** * instrNumPops() returns the number of values consumed from the stack * for a given push/pop instruction. For peek/poke instructions, this * function returns 0. */ int instrNumPops(PC pc) { static const int32_t numberOfPops[] = { #define NOV 0 #define ONE(...) 1 #define TWO(...) 2 #define THREE(...) 3 #define FOUR(...) 4 #define MFINAL -3 #define F_MFINAL -6 #define C_MFINAL -5 #define V_MFINAL C_MFINAL #define FMANY -3 #define CVUMANY -3 #define CMANY -3 #define SMANY -1 #define IDX_A -4 #define O(name, imm, pop, push, flags) pop, OPCODES #undef NOV #undef ONE #undef TWO #undef THREE #undef FOUR #undef MFINAL #undef F_MFINAL #undef C_MFINAL #undef V_MFINAL #undef FMANY #undef CVUMANY #undef CMANY #undef SMANY #undef IDX_A #undef O }; auto const op = peek_op(pc); int n = numberOfPops[size_t(op)]; // For most instructions, we know how many values are popped based // solely on the opcode if (n >= 0) return n; // BaseSC and BaseSL remove an A that may be on the top of the stack or one // element below the top, depending on the second immediate. if (n == -4) return getImm(pc, 1).u_IVA + 1; // FCall, NewPackedArray, and some final member operations specify how many // values are popped in their first immediate if (n == -3) return getImm(pc, 0).u_IVA; // FPassM final operations have paramId as imm 0 and stackCount as imm1 if (n == -6) return getImm(pc, 1).u_IVA; // Other final member operations pop their first immediate + 1 if (n == -5) return getImm(pc, 0).u_IVA + 1; // For instructions with vector immediates, we have to scan the contents of // the vector immediate to determine how many values are popped assert(n == -1); ImmVector iv = getImmVector(pc); int k = iv.numStackValues(); return k; }
/** * instrNumPops() returns the number of values consumed from the stack * for a given push/pop instruction. For peek/poke instructions, this * function returns 0. */ int instrNumPops(PC pc) { static const int32_t numberOfPops[] = { #define NOV 0 #define ONE(...) 1 #define TWO(...) 2 #define THREE(...) 3 #define FOUR(...) 4 #define FIVE(...) 5 #define MFINAL -3 #define C_MFINAL -5 #define V_MFINAL C_MFINAL #define CVMANY -3 #define CVUMANY -3 #define FCALL -4 #define CMANY -3 #define SMANY -1 #define O(name, imm, pop, push, flags) pop, OPCODES #undef NOV #undef ONE #undef TWO #undef THREE #undef FOUR #undef FIVE #undef MFINAL #undef C_MFINAL #undef V_MFINAL #undef CVMANY #undef CVUMANY #undef FCALL #undef CMANY #undef SMANY #undef O }; auto const op = peek_op(pc); int n = numberOfPops[size_t(op)]; // For most instructions, we know how many values are popped based // solely on the opcode if (n >= 0) return n; // FCallAwait, NewPackedArray, and some final member operations specify how // many values are popped in their first immediate if (n == -3) return getImm(pc, 0).u_IVA; // FCall pops numArgs, unpack and (numRets - 1) uninit values if (n == -4) { auto const fca = getImm(pc, 0).u_FCA; return fca.numArgs + (fca.hasUnpack ? 1 : 0) + fca.numRets - 1; } // Other final member operations pop their first immediate + 1 if (n == -5) return getImm(pc, 0).u_IVA + 1; // For instructions with vector immediates, we have to scan the contents of // the vector immediate to determine how many values are popped assertx(n == -1); ImmVector iv = getImmVector(pc); int k = iv.numStackValues(); return k; }
/** * instrNumPushes() returns the number of values pushed onto the stack * for a given push/pop instruction. For peek/poke instructions or * InsertMid instructions, this function returns 0. */ int instrNumPushes(PC pc) { static const int8_t numberOfPushes[] = { #define NOV 0 #define ONE(...) 1 #define TWO(...) 2 #define THREE(...) 3 #define FOUR(...) 4 #define INS_1(...) 0 #define CMANY -1 #define O(name, imm, pop, push, flags) push, OPCODES #undef NOV #undef ONE #undef TWO #undef THREE #undef FOUR #undef INS_1 #undef CMANY #undef O }; auto const op = peek_op(pc); int n = numberOfPushes[size_t(op)]; // The FCallM call flavors push a tuple of arguments onto the stack if (n == -1) return getImm(pc, 1).u_IVA; return n; }
/** * instrNumPushes() returns the number of values pushed onto the stack * for a given push/pop instruction. For peek/poke instructions or * InsertMid instructions, this function returns 0. */ int instrNumPushes(PC pc) { static const int8_t numberOfPushes[] = { #define NOV 0 #define ONE(...) 1 #define TWO(...) 2 #define THREE(...) 3 #define FOUR(...) 4 #define INS_1(...) 0 #define INS_2(...) 0 #define IDX_A -1 #define O(name, imm, pop, push, flags) push, OPCODES #undef NOV #undef ONE #undef TWO #undef THREE #undef FOUR #undef INS_1 #undef INS_2 #undef IDX_A #undef O }; auto const op = peek_op(pc); auto const pushes = numberOfPushes[size_t(op)]; // BaseSC and BaseSL may push back a C that was on top of the A they removed. if (pushes == -1) return getImm(pc, 1).u_IVA; return pushes; }
void printInstruction(const INSTRUCTION * const i) { DISASSEMBLY d; FlushDecoded(&d); d.Address = (DWORD)(i->data); DWORD ilen = 0; Decode(&d, (char *)(i->data), &ilen); printf("[%2d] ", i->index); printDisassembly(d); printf("size:\t\t%d\n", i->totalSize); if (i->regReads) { printf("reads:\t\t"); for (BYTE reg = 0; reg < 8; reg++) { if (GET_READS(i, reg)) printf ("%s ", REG[2][reg]); } printf("\n"); } if (i->regWrites) { printf("writes:\t\t"); for (BYTE reg = 0; reg < 8; reg++) { if (GET_WRITES(i, reg)) printf ("%s ", REG[2][reg]); } printf("\n"); } if (i->freeRegs) { printf("free:\t\t"); for (BYTE reg = 0; reg < 8; reg++) { if (IS_FREE_REG(i,reg)) printf ("%s ", REG[2][reg]); } printf("\n"); } if (i->flags) printf("flags:\t\t0x%08X\n", i->flags); printf("offsets:\tOPCODE:%d, MODRM:%d, SIB:%d, DISP:%d:0x%08X, IMM:%d:0x%08X\n", OFFSET_TO_OPCODE(i), OFFSET_TO_MODRM(i), OFFSET_TO_SIB(i), OFFSET_TO_DISP(i), getDisp(i), OFFSET_TO_IMM(i), getImm(i)); if (i->jmp) printf("jumps to:\t%d\n", i->jmp->index); if (i->directVA) printf("refers to:\t0x%08X\n", *((DWORD *)(i->data + i->directVA))); }
/** * instrNumPops() returns the number of values consumed from the stack * for a given push/pop instruction. For peek/poke instructions, this * function returns 0. */ int instrNumPops(const Op* opcode) { static const int8_t numberOfPops[] = { #define NOV 0 #define ONE(...) 1 #define TWO(...) 2 #define THREE(...) 3 #define FOUR(...) 4 #define MMANY -1 #define C_MMANY -2 #define V_MMANY -2 #define R_MMANY -2 #define FMANY -3 #define CVMANY -3 #define CVUMANY -3 #define CMANY -3 #define SMANY -1 #define O(name, imm, pop, push, flags) pop, OPCODES #undef NOV #undef ONE #undef TWO #undef THREE #undef FOUR #undef MMANY #undef C_MMANY #undef V_MMANY #undef R_MMANY #undef FMANY #undef CVMANY #undef CVUMANY #undef CMANY #undef SMANY #undef O }; int n = numberOfPops[uint8_t(*opcode)]; // For most instructions, we know how many values are popped based // solely on the opcode if (n >= 0) return n; // FCall and NewPackedArray specify how many values are popped in their // first immediate if (n == -3) return getImm(opcode, 0).u_IVA; // For instructions with vector immediates, we have to scan the // contents of the vector immediate to determine how many values // are popped assert(n == -1 || n == -2); ImmVector iv = getImmVector(opcode); // Count the number of values on the stack accounted for by the // ImmVector's location and members int k = iv.numStackValues(); // If this instruction also takes a RHS, count that too if (n == -2) ++k; return k; }
int instrSpToArDelta(const Op* opcode) { // This function should only be called for instructions that read // the current FPI assert(instrReadsCurrentFpi(*opcode)); // The delta from sp to ar is equal to the number of values on the stack // that will be consumed by this instruction (numPops) plus the number of // parameters pushed onto the stack so far that are not being consumed by // this instruction (numExtra). For the FPass* instructions, numExtra will // be equal to the first immediate argument (param id). For the FCall // instructions, numExtra will be 0 because all of the parameters on the // stack are already accounted for by numPops. int numPops = instrNumPops(opcode); int numExtra = isFCallStar(*opcode) ? 0 : getImm(opcode, 0).u_IVA; return numPops + numExtra; }
RegionDescPtr selectCalleeRegion(const SrcKey& sk, const Func* callee, const irgen::IRGS& irgs, InliningDecider& inl, int32_t maxBCInstrs) { auto const op = sk.pc(); auto const numArgs = getImm(op, 0).u_IVA; auto const& fpi = irgs.irb->fs().fpiStack(); assertx(!fpi.empty()); auto const ctx = fpi.back().ctxType; std::vector<Type> argTypes; for (int i = numArgs - 1; i >= 0; --i) { // DataTypeGeneric is used because we're just passing the locals into the // callee. It's up to the callee to constrain further if needed. auto type = irgen::publicTopType(irgs, BCSPRelOffset{i}); // If we don't have sufficient type information to inline the region return // early if (!(type <= TCell) && !(type <= TBoxedCell) && !(type <= TCls)) { return nullptr; } argTypes.push_back(type); } const auto mode = RuntimeOption::EvalInlineRegionMode; if (mode == "tracelet" || mode == "both") { auto region = selectCalleeTracelet( callee, numArgs, ctx, argTypes, maxBCInstrs ); auto const maxCost = RuntimeOption::EvalHHIRInliningMaxVasmCost; if (region && inl.shouldInline(sk, callee, *region, maxCost)) return region; if (mode == "tracelet") return nullptr; } if (profData()) { auto region = selectCalleeCFG(callee, numArgs, ctx, argTypes, maxBCInstrs); auto const maxCost = RuntimeOption::EvalHHIRInliningMaxVasmCost; if (region && inl.shouldInline(sk, callee, *region, maxCost)) return region; } return nullptr; }
static void recordActRecPush(const SrcKey& sk, const Unit* unit, const FPIEnt* fpi, const StringData* name, const StringData* clsName, bool staticCall) { // sk is the address of a FPush* of the function whose static name // is name. The boundaries of FPI regions are such that we can't quite // find the FCall that matches this FuncD without decoding forward to // the end; this is not ideal, but is hopefully affordable at translation // time. ASSERT(name->isStatic()); ASSERT(sk.offset() == fpi->m_fpushOff); SrcKey fcall; SrcKey next(sk); next.advance(unit); do { if (*unit->at(next.offset()) == OpFCall) { // Remember the last FCall in the region; the region might end // with UnboxR, e.g. fcall = next; } next.advance(unit); } while (next.offset() <= fpi->m_fcallOff); ASSERT(*unit->at(fcall.offset()) == OpFCall); if (clsName) { const Class* cls = Unit::lookupClass(clsName); bool magic = false; const Func* func = lookupImmutableMethod(cls, name, magic, staticCall); if (func) { recordFunc(fcall, func); } return; } const Func* func = Unit::lookupFunc(name); if (func && func->isNameBindingImmutable(unit)) { // this will never go into a call cache, so we dont need to // encode the args. it will be used in OpFCall below to // set the i->funcd. recordFunc(fcall, func); } else { // It's not enough to remember the function name; we also need to encode // the number of arguments and current flag disposition. int numArgs = getImm(unit->at(sk.offset()), 0).u_IVA; recordNameAndArgs(fcall, name, numArgs); } }
static void recordActRecPush(NormalizedInstruction& i, const Unit* unit, const StringData* name, const StringData* clsName, bool staticCall) { const SrcKey& sk = i.source; FTRACE(2, "annotation: recordActRecPush: {}@{} {}{}{} ({}static)\n", unit->filepath()->data(), sk.offset(), clsName ? clsName->data() : "", clsName ? "::" : "", name, !staticCall ? "non" : ""); SrcKey next(sk); next.advance(unit); const FPIEnt *fpi = curFunc()->findFPI(next.offset()); assert(fpi); assert(name->isStatic()); assert(sk.offset() == fpi->m_fpushOff); SrcKey fcall = sk; fcall.m_offset = fpi->m_fcallOff; assert(isFCallStar(*unit->at(fcall.offset()))); if (clsName) { const Class* cls = Unit::lookupUniqueClass(clsName); bool magic = false; const Func* func = lookupImmutableMethod(cls, name, magic, staticCall); if (func) { recordFunc(i, fcall, func); } return; } const Func* func = Unit::lookupFunc(name); if (func && func->isNameBindingImmutable(unit)) { // this will never go into a call cache, so we dont need to // encode the args. it will be used in OpFCall below to // set the i->funcd. recordFunc(i, fcall, func); } else { // It's not enough to remember the function name; we also need to encode // the number of arguments and current flag disposition. int numArgs = getImm(unit->at(sk.offset()), 0).u_IVA; recordNameAndArgs(fcall, name, numArgs); } }
int64_t getStackPopped(PC pc) { auto const op = peek_op(pc); switch (op) { case Op::FCall: return getImm(pc, 0).u_IVA + kNumActRecCells; case Op::FCallD: return getImm(pc, 0).u_IVA + kNumActRecCells; case Op::FCallAwait: return getImm(pc, 0).u_IVA + kNumActRecCells; case Op::FCallArray: return kNumActRecCells + 1; case Op::QueryM: case Op::VGetM: case Op::IncDecM: case Op::UnsetM: case Op::NewPackedArray: case Op::NewVecArray: case Op::NewKeysetArray: case Op::ConcatN: case Op::FCallBuiltin: case Op::CreateCl: return getImm(pc, 0).u_IVA; case Op::FPassM: // imm[0] is argument index return getImm(pc, 1).u_IVA; case Op::SetM: case Op::SetOpM: case Op::BindM: return getImm(pc, 0).u_IVA + 1; case Op::NewStructArray: return getImmVector(pc).size(); case Op::BaseSC: case Op::BaseSL: return getImm(pc, 1).u_IVA + 1; default: break; } uint64_t mask = getInstrInfo(op).in; int64_t count = 0; // All instructions with these properties are handled above assertx((mask & (StackN | BStackN)) == 0); return count + countOperands(mask); }
/** * instrNumPops() returns the number of values consumed from the stack * for a given push/pop instruction. For peek/poke instructions, this * function returns 0. */ int instrNumPops(const Opcode* opcode) { static const int8_t numberOfPops[] = { #define NOV 0 #define ONE(...) 1 #define TWO(...) 2 #define THREE(...) 3 #define LMANY(...) -1 #define C_LMANY(...) -2 #define V_LMANY(...) -2 #define FMANY -3 #define O(name, imm, pop, push, flags) pop, OPCODES #undef NOV #undef ONE #undef TWO #undef THREE #undef LMANY #undef C_LMANY #undef V_LMANY #undef FMANY #undef O }; int n = numberOfPops[*opcode]; // For most instructions, we know how many values are popped based // solely on the opcode if (n >= 0) return n; // FCall specifies how many values are popped in its first immediate if (n == -3) return getImm(opcode, 0).u_IVA; // For instructions with vector immediates, we have to scan the // contents of the vector immediate to determine how many values // are popped ASSERT(n == -1 || n == -2); ImmVector iv = getImmVector(opcode); // Count the number of values on the stack accounted for by the // ImmVector's location and members int k = iv.numStackValues(); // If this instruction also takes a RHS, count that too if (n == -2) ++k; return k; }
/* * Check that we don't have any missing or extra arguments. */ bool checkNumArgs(SrcKey callSK, const Func* callee, Annotations& annotations) { assertx(callSK.op() == Op::FCall); assertx(callee); auto refuse = [&] (const char* why) { return traceRefusal(callSK, callee, why, annotations); }; auto pc = callSK.pc(); auto const fca = getImm(pc, 0).u_FCA; auto const numParams = callee->numParams(); if (fca.numArgs > numParams) { return refuse("callee called with too many arguments"); } if (fca.hasUnpack) { return refuse("callee called with variadic arguments"); } if (fca.numRets != 1) { return refuse("callee with multiple returns"); } // It's okay if we passed fewer arguments than there are parameters as long // as the gap can be filled in by DV funclets. for (auto i = fca.numArgs; i < numParams; ++i) { auto const& param = callee->params()[i]; if (!param.hasDefaultValue() && (i < numParams - 1 || !callee->hasVariadicCaptureParam())) { return refuse("callee called with too few arguments"); } } return true; }
int64_t getStackPushed(PC pc) { auto const op = peek_op(pc); if (op == Op::BaseSC || op == Op::BaseSL) return getImm(pc, 1).u_IVA; return countOperands(getInstrInfo(op).out); }
bool IRTranslator::tryTranslateSingletonInline(const NormalizedInstruction& i, const Func* funcd) { using Atom = BCPattern::Atom; using Captures = BCPattern::CaptureVec; if (!funcd) return false; // Make sure we have an acceptable FPush and non-null callee. assert(i.op() == Op::FPushFuncD || i.op() == Op::FPushClsMethodD); auto fcall = i.nextSk(); // Check if the next instruction is an acceptable FCall. if ((fcall.op() != Op::FCall && fcall.op() != Op::FCallD) || funcd->isResumable() || funcd->isReturnRef()) { return false; } // First, check for the static local singleton pattern... // Lambda to check if CGetL and StaticLocInit refer to the same local. auto has_same_local = [] (PC pc, const Captures& captures) { if (captures.size() == 0) return false; auto cgetl = (const Op*)pc; auto sli = (const Op*)captures[0]; assert(*cgetl == Op::CGetL); assert(*sli == Op::StaticLocInit); return (getImm(sli, 0).u_IVA == getImm(cgetl, 0).u_IVA); }; auto cgetl = Atom(Op::CGetL).onlyif(has_same_local); auto retc = Atom(Op::RetC); // Look for a static local singleton pattern. auto result = BCPattern { Atom(Op::Null), Atom(Op::StaticLocInit).capture(), Atom(Op::IsTypeL), Atom::alt( Atom(Op::JmpZ).taken({cgetl, retc}), Atom::seq(Atom(Op::JmpNZ), cgetl, retc) ) }.ignore( {Op::AssertRATL, Op::AssertRATStk} ).matchAnchored(funcd); if (result.found()) { try { hhbcTrans().emitSingletonSLoc( funcd, (const Op*)result.getCapture(0) ); } catch (const FailedIRGen& e) { return false; } catch (const FailedCodeGen& e) { return false; } TRACE(1, "[singleton-sloc] %s <- %s\n", funcd->fullName()->data(), fcall.func()->fullName()->data()); return true; } // Not found; check for the static property pattern. // Factory for String atoms that are required to match another captured // String opcode. auto same_string_as = [&] (int i) { return Atom(Op::String).onlyif([=] (PC pc, const Captures& captures) { auto string1 = (const Op*)pc; auto string2 = (const Op*)captures[i]; assert(*string1 == Op::String); assert(*string2 == Op::String); auto const unit = funcd->unit(); auto sd1 = unit->lookupLitstrId(getImmPtr(string1, 0)->u_SA); auto sd2 = unit->lookupLitstrId(getImmPtr(string2, 0)->u_SA); return (sd1 && sd1 == sd2); }); }; auto stringProp = same_string_as(0); auto stringCls = same_string_as(1); auto agetc = Atom(Op::AGetC); auto cgets = Atom(Op::CGetS); // Look for a class static singleton pattern. result = BCPattern { Atom(Op::String).capture(), Atom(Op::String).capture(), Atom(Op::AGetC), Atom(Op::CGetS), Atom(Op::IsTypeC), Atom::alt( Atom(Op::JmpZ).taken({stringProp, stringCls, agetc, cgets, retc}), Atom::seq(Atom(Op::JmpNZ), stringProp, stringCls, agetc, cgets, retc) ) }.ignore( {Op::AssertRATL, Op::AssertRATStk} ).matchAnchored(funcd); if (result.found()) { try { hhbcTrans().emitSingletonSProp( funcd, (const Op*)result.getCapture(1), (const Op*)result.getCapture(0) ); } catch (const FailedIRGen& e) { return false; } catch (const FailedCodeGen& e) { return false; } TRACE(1, "[singleton-sprop] %s <- %s\n", funcd->fullName()->data(), fcall.func()->fullName()->data()); return true; } return false; }