/* * Attempts to begin inlining, and returns whether or not it successed. * * When doing gen-time inlining, we set up a series of IR instructions * that looks like this: * * fp0 = DefFP * sp = DefSP<offset> * * // ... normal stuff happens ... * * // FPI region: * SpillFrame sp, ... * // ... probably some StStks due to argument expressions * fp2 = DefInlineFP<func,retBC,retSP,off> sp * * // ... callee body ... * * InlineReturn fp2 * * In DCE we attempt to remove the InlineReturn and DefInlineFP instructions if * they aren't needed. */ bool beginInlining(IRGS& env, unsigned numParams, const Func* target, Offset returnBcOffset) { auto const& fpiStack = env.irb->fpiStack(); assertx(!fpiStack.empty() && "Inlining does not support calls with the FPush* in a different Tracelet"); assertx(returnBcOffset >= 0 && "returnBcOffset before beginning of caller"); assertx(curFunc(env)->base() + returnBcOffset < curFunc(env)->past() && "returnBcOffset past end of caller"); FTRACE(1, "[[[ begin inlining: {}\n", target->fullName()->data()); SSATmp** params = (SSATmp**)alloca(sizeof(SSATmp*) * numParams); for (unsigned i = 0; i < numParams; ++i) { params[numParams - i - 1] = popF(env); } auto const prevSP = fpiStack.front().returnSP; auto const prevSPOff = fpiStack.front().returnSPOff; spillStack(env); auto const calleeSP = sp(env); always_assert_flog( prevSP == calleeSP, "FPI stack pointer and callee stack pointer didn't match in beginInlining" ); auto const& info = fpiStack.front(); always_assert(!isFPushCuf(info.fpushOpc) && !info.interp); auto ctx = [&] { if (info.ctx || isFPushFunc(info.fpushOpc)) { return info.ctx; } constexpr int32_t adjust = offsetof(ActRec, m_r) - offsetof(ActRec, m_this); IRSPOffset ctxOff{invSPOff(env) - info.returnSPOff - adjust}; return gen(env, LdStk, TCtx, IRSPOffsetData{ctxOff}, sp(env)); }(); DefInlineFPData data; data.target = target; data.retBCOff = returnBcOffset; data.fromFPushCtor = isFPushCtor(info.fpushOpc); data.ctx = ctx; data.retSPOff = prevSPOff; data.spOffset = offsetFromIRSP(env, BCSPOffset{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); env.inlineLevel++; updateMarker(env); auto const calleeFP = gen(env, DefInlineFP, data, calleeSP, fp(env)); for (unsigned i = 0; i < numParams; ++i) { stLocRaw(env, i, calleeFP, params[i]); } const bool hasVariadicArg = target->hasVariadicCaptureParam(); for (unsigned i = numParams; i < target->numLocals() - hasVariadicArg; ++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, TUninit)); } if (hasVariadicArg) { auto argNum = target->numLocals() - 1; always_assert(numParams <= argNum); stLocRaw(env, argNum, calleeFP, cns(env, staticEmptyArray())); } return true; }
/** * Discard the current frame, assuming that a PHP exception given in * phpException argument, or C++ exception (phpException == nullptr) * is being thrown. Returns an exception to propagate, or nulltpr * if the VM execution should be resumed. */ ObjectData* tearDownFrame(ActRec*& fp, Stack& stack, PC& pc, ObjectData* phpException) { auto const func = fp->func(); auto const curOp = peek_op(pc); auto const prevFp = fp->sfp(); auto const soff = fp->m_soff; ITRACE(1, "tearDownFrame: {} ({})\n", func->fullName()->data(), func->unit()->filepath()->data()); ITRACE(1, " fp {} prevFp {}\n", implicit_cast<void*>(fp), implicit_cast<void*>(prevFp)); // When throwing from a constructor, we normally want to avoid running the // destructor on an object that hasn't been fully constructed yet. But if // we're unwinding through the constructor's RetC, the constructor has // logically finished and we're unwinding for some internal reason (timeout // or user profiler, most likely). More importantly, fp->m_this may have // already been destructed and/or overwritten due to sharing space with // fp->m_r. if (curOp != OpRetC && fp->hasThis() && fp->getThis()->getVMClass()->getCtor() == func && fp->getThis()->getVMClass()->getDtor()) { /* * Looks like an FPushCtor call, but it could still have been called * directly. Check the fpi region to be sure. */ Offset prevPc; auto outer = g_context->getPrevVMState(fp, &prevPc); if (outer) { auto fe = outer->func()->findPrecedingFPI(prevPc); if (fe && isFPushCtor(outer->func()->unit()->getOp(fe->m_fpushOff))) { fp->getThis()->setNoDestruct(); } } } auto const decRefLocals = [&] { /* * It is possible that locals have already been decref'd. * * Here's why: * * - If a destructor for any of these things throws a php * exception, it's swallowed at the dtor boundary and we keep * running php. * * - If the destructor for any of these things throws a fatal, * it's swallowed, and we set surprise flags to throw a fatal * from now on. * * - If the second case happened and we have to run another * destructor, its enter hook will throw, but it will be * swallowed again. * * - Finally, the exit hook for the returning function can * throw, but this happens last so everything is destructed. * * - When that happens, exit hook sets localsDecRefd flag. */ if (!fp->localsDecRefd()) { try { // Note that we must convert locals and the $this to // uninit/zero during unwind. This is because a backtrace // from another destructing object during this unwind may try // to read them. frame_free_locals_unwind(fp, func->numLocals(), phpException); } catch (...) {} } }; if (LIKELY(!fp->resumed())) { decRefLocals(); if (UNLIKELY(func->isAsyncFunction()) && phpException && !fp->isFCallAwait()) { // If in an eagerly executed async function, wrap the user exception // into a failed StaticWaitHandle and return it to the caller. auto const waitHandle = c_StaticWaitHandle::CreateFailed(phpException); phpException = nullptr; stack.ndiscard(func->numSlotsInFrame()); stack.ret(); assert(stack.topTV() == &fp->m_r); cellCopy(make_tv<KindOfObject>(waitHandle), fp->m_r); } else { // Free ActRec. stack.ndiscard(func->numSlotsInFrame()); stack.discardAR(); } } else if (func->isAsyncFunction()) { auto const waitHandle = frame_afwh(fp); if (phpException) { // Handle exception thrown by async function. decRefLocals(); waitHandle->fail(phpException); phpException = nullptr; } else if (waitHandle->isRunning()) { // Let the C++ exception propagate. If the current frame represents async // function that is running, mark it as abruptly interrupted. Some opcodes // like Await may change state of the async function just before exit hook // decides to throw C++ exception. decRefLocals(); waitHandle->failCpp(); } } else if (func->isAsyncGenerator()) { auto const gen = frame_async_generator(fp); if (phpException) { // Handle exception thrown by async generator. decRefLocals(); auto eagerResult = gen->fail(phpException); phpException = nullptr; if (eagerResult) { stack.pushObjectNoRc(eagerResult); } } else if (gen->isEagerlyExecuted() || gen->getWaitHandle()->isRunning()) { // Fail the async generator and let the C++ exception propagate. decRefLocals(); gen->failCpp(); } } else if (func->isNonAsyncGenerator()) { // Mark the generator as finished. decRefLocals(); frame_generator(fp)->fail(); } else { not_reached(); } /* * At the final ActRec in this nesting level. */ if (UNLIKELY(!prevFp)) { pc = nullptr; fp = nullptr; return phpException; } assert(stack.isValidAddress(reinterpret_cast<uintptr_t>(prevFp)) || prevFp->resumed()); auto const prevOff = soff + prevFp->func()->base(); pc = prevFp->func()->unit()->at(prevOff); fp = prevFp; return phpException; }