void ValueRep::dump(PrintStream& out) const { out.print(m_kind); switch (m_kind) { case WarmAny: case ColdAny: case LateColdAny: case SomeRegister: case SomeRegisterWithClobber: case SomeEarlyRegister: return; case LateRegister: case Register: out.print("(", reg(), ")"); return; case Stack: out.print("(", offsetFromFP(), ")"); return; case StackArgument: out.print("(", offsetFromSP(), ")"); return; case Constant: out.print("(", value(), ")"); return; } RELEASE_ASSERT_NOT_REACHED(); }
Block* makeExitOpt(HTS& env, TransID transId) { assert(!isInlining(env)); auto const targetBcOff = bcOff(env); auto const exit = env.unit.defBlock(Block::Hint::Unlikely); BlockPusher blockPusher(*env.irb, makeMarker(env, targetBcOff), exit); spillStack(env); gen(env, AdjustSP, StackOffset { offsetFromSP(env, 0) }, sp(env)); gen(env, ReqRetranslateOpt, ReqRetransOptData{transId, SrcKey{curSrcKey(env), targetBcOff}}, sp(env)); return exit; }
void emitSSwitch(HTS& env, const ImmVector& iv) { const int numCases = iv.size() - 1; /* * We use a fast path translation with a hashtable if none of the * cases are numeric strings and if the input is actually a string. * * Otherwise we do a linear search through the cases calling string * conversion routines. */ const bool fastPath = topC(env)->isA(Type::Str) && std::none_of(iv.strvec(), iv.strvec() + numCases, [&](const StrVecItem& item) { return curUnit(env)->lookupLitstrId(item.str)->isNumeric(); } ); auto const testVal = popC(env); std::vector<LdSSwitchData::Elm> cases(numCases); for (int i = 0; i < numCases; ++i) { auto const& kv = iv.strvec()[i]; cases[i].str = curUnit(env)->lookupLitstrId(kv.str); cases[i].dest = bcOff(env) + kv.dest; } LdSSwitchData data; data.numCases = numCases; data.cases = &cases[0]; data.defaultOff = bcOff(env) + iv.strvec()[iv.size() - 1].dest; auto const dest = gen(env, fastPath ? LdSSwitchDestFast : LdSSwitchDestSlow, data, testVal); gen(env, DecRef, testVal); gen(env, AdjustSP, StackOffset { offsetFromSP(env, 0) }, sp(env)); gen(env, JmpSSwitchDest, dest, sp(env)); }
void emitNewStructArray(HTS& env, const ImmVector& immVec) { auto const numArgs = immVec.size(); auto const ids = immVec.vec32(); // The NewPackedArray opcode's helper needs array values passed to it // via the stack. We use spillStack() to flush the eval stack and // obtain a pointer to the topmost item; if over-flushing becomes // a problem then we should refactor the NewPackedArray opcode to // take its values directly as SSA operands. spillStack(env); NewStructData extra; extra.offset = offsetFromSP(env, 0); extra.numKeys = numArgs; extra.keys = new (env.unit.arena()) StringData*[numArgs]; for (auto i = size_t{0}; i < numArgs; ++i) { extra.keys[i] = curUnit(env)->lookupLitstrId(ids[i]); } discard(env, numArgs); push(env, gen(env, NewStructArray, extra, sp(env))); }
void emitNewPackedArray(HTS& env, int32_t numArgs) { if (numArgs > kPackedCapCodeThreshold) { PUNT(NewPackedArray-UnrealisticallyHuge); } auto const array = gen( env, AllocPackedArray, PackedArrayData { static_cast<uint32_t>(numArgs) } ); static constexpr auto kMaxUnrolledInitArray = 8; if (numArgs > kMaxUnrolledInitArray) { spillStack(env); gen( env, InitPackedArrayLoop, InitPackedArrayLoopData { offsetFromSP(env, 0), static_cast<uint32_t>(numArgs) }, array, sp(env) ); discard(env, numArgs); push(env, array); return; } for (int i = 0; i < numArgs; ++i) { gen( env, InitPackedArray, IndexData { static_cast<uint32_t>(numArgs - i - 1) }, array, popC(env) ); } push(env, array); }
void emitSwitch(HTS& env, const ImmVector& iv, int64_t base, int32_t bounded) { int nTargets = bounded ? iv.size() - 2 : iv.size(); SSATmp* const switchVal = popC(env); Type type = switchVal->type(); assert(IMPLIES(!(type <= Type::Int), bounded)); assert(IMPLIES(bounded, iv.size() > 2)); SSATmp* index; SSATmp* ssabase = cns(env, base); SSATmp* ssatargets = cns(env, nTargets); Offset defaultOff = bcOff(env) + iv.vec32()[iv.size() - 1]; Offset zeroOff = 0; if (base <= 0 && (base + nTargets) > 0) { zeroOff = bcOff(env) + iv.vec32()[0 - base]; } else { zeroOff = defaultOff; } if (type <= Type::Null) { gen(env, Jmp, makeExit(env, zeroOff)); return; } if (type <= Type::Bool) { Offset nonZeroOff = bcOff(env) + iv.vec32()[iv.size() - 2]; gen(env, JmpNZero, makeExit(env, nonZeroOff), switchVal); gen(env, Jmp, makeExit(env, zeroOff)); return; } if (type <= Type::Int) { // No special treatment needed index = switchVal; } else if (type <= Type::Dbl) { // switch(Double|String|Obj)Helper do bounds-checking for us, so // we need to make sure the default case is in the jump table, // and don't emit our own bounds-checking code bounded = false; index = gen(env, LdSwitchDblIndex, switchVal, ssabase, ssatargets); } else if (type <= Type::Str) { bounded = false; index = gen(env, LdSwitchStrIndex, switchVal, ssabase, ssatargets); } else if (type <= Type::Obj) { // switchObjHelper can throw exceptions and reenter the VM so we use the // catch block here. bounded = false; index = gen(env, LdSwitchObjIndex, switchVal, ssabase, ssatargets); } else if (type <= Type::Arr) { gen(env, DecRef, switchVal); gen(env, Jmp, makeExit(env, defaultOff)); return; } else { PUNT(Switch-UnknownType); } std::vector<Offset> targets(iv.size()); for (int i = 0; i < iv.size(); i++) { targets[i] = bcOff(env) + iv.vec32()[i]; } JmpSwitchData data; data.base = base; data.bounded = bounded; data.cases = iv.size(); data.defaultOff = defaultOff; data.targets = &targets[0]; spillStack(env); gen(env, AdjustSP, StackOffset { offsetFromSP(env, 0) }, sp(env)); gen(env, JmpSwitchDest, data, index, sp(env)); }
/* * When doing gen-time inlining, we set up a series of IR instructions * that looks like this: * * fp0 = DefFP * sp0 = DefSP<offset> * * // ... normal stuff happens ... * // sp_pre = some SpillStack, or maybe the DefSP * * // FPI region: * sp1 = SpillStack sp_pre, ... * sp2 = SpillFrame sp1, ... * // ... possibly more spillstacks due to argument expressions * sp3 = SpillStack sp2, -argCount * fp2 = DefInlineFP<func,retBC,retSP,off> sp2 sp1 * sp4 = ReDefSP<spOffset,spansCall> sp1 fp2 * * // ... callee body ... * * = InlineReturn fp2 * * [ sp5 = ResetSP<spOffset> fp0 ] * * The rest of the code then depends on sp5, and not any of the StkPtr * tree going through the callee body. The sp5 tmp has the same view * of the stack as sp1 did, which represents what the stack looks like * before the return address is pushed but after the activation record * is popped. * * In DCE we attempt to remove the SpillFrame, InlineReturn, and * DefInlineFP instructions if they aren't needed. * * ReDefSP takes sp1, the stack pointer from before the inlined frame. * This SSATmp may be used for determining stack types in the * simplifier, or stack values if the inlined body doesn't contain a * call---these instructions both take an extradata `spansCall' which * is true iff a Call occured anywhere between the the definition of * its first argument and itself. */ void beginInlining(HTS& env, unsigned numParams, const Func* target, Offset returnBcOffset) { assert(!env.fpiStack.empty() && "Inlining does not support calls with the FPush* in a different Tracelet"); assert(returnBcOffset >= 0 && "returnBcOffset before beginning of caller"); assert(curFunc(env)->base() + returnBcOffset < curFunc(env)->past() && "returnBcOffset past end of caller"); FTRACE(1, "[[[ begin inlining: {}\n", target->fullName()->data()); SSATmp* params[numParams]; for (unsigned i = 0; i < numParams; ++i) { params[numParams - i - 1] = popF(env); } auto const prevSP = env.fpiStack.top().returnSP; auto const prevSPOff = env.fpiStack.top().returnSPOff; spillStack(env); auto const calleeSP = sp(env); always_assert_flog( env.fpiStack.top().spillFrame != nullptr, "Couldn't find SpillFrame for inlined call on sp {}." " Was the FPush instruction interpreted?\n{}", *calleeSP->inst(), env.irb->unit() ); auto const sframe = env.fpiStack.top().spillFrame; DefInlineFPData data; data.target = target; data.retBCOff = returnBcOffset; data.fromFPushCtor = sframe->extra<ActRecInfo>()->isFromFPushCtor(); data.ctx = sframe->src(2); data.retSPOff = prevSPOff; data.spOffset = offsetFromSP(env, 0); // Push state and update the marker before emitting any instructions so // they're all given markers in the callee. auto const key = SrcKey { target, target->getEntryForNumArgs(numParams), false }; env.bcStateStack.emplace_back(key); updateMarker(env); auto const calleeFP = gen(env, DefInlineFP, data, calleeSP, prevSP, fp(env)); gen(env, ReDefSP, StackOffset{target->numLocals()}, fp(env)); for (unsigned i = 0; i < numParams; ++i) { stLocRaw(env, i, calleeFP, params[i]); } for (unsigned i = numParams; i < target->numLocals(); ++i) { /* * Here we need to be generating hopefully-dead stores to initialize * non-parameter locals to KindOfUninit in case we have to leave the trace. */ stLocRaw(env, i, calleeFP, cns(env, Type::Uninit)); } env.fpiActiveStack.push(std::make_pair(env.fpiStack.top().returnSP, env.fpiStack.top().returnSPOff)); env.fpiStack.pop(); }