void emitContEnter(HTS& env) { auto const returnOffset = nextBcOff(env); assert(curClass(env)); assert(curClass(env)->classof(c_AsyncGenerator::classof()) || curClass(env)->classof(c_Generator::classof())); assert(curFunc(env)->contains(returnOffset)); // Load generator's FP and resume address. auto const genObj = ldThis(env); auto const genFp = gen(env, LdContActRec, genObj); auto resumeAddr = gen(env, LdContResumeAddr, genObj); // Make sure function enter hook is called if needed. auto const exitSlow = makeExitSlow(env); gen(env, CheckSurpriseFlags, exitSlow); // Exit to interpreter if resume address is not known. resumeAddr = gen(env, CheckNonNull, exitSlow, resumeAddr); // Sync stack. auto const stack = spillStack(env); // Enter generator. auto returnBcOffset = returnOffset - curFunc(env)->base(); gen(env, ContEnter, stack, fp(env), genFp, resumeAddr, cns(env, returnBcOffset)); }
void emitContEnter(IRGS& env) { auto const returnOffset = nextBcOff(env); assertx(curClass(env)); assertx(curClass(env)->classof(c_AsyncGenerator::classof()) || curClass(env)->classof(c_Generator::classof())); assertx(curFunc(env)->contains(returnOffset)); auto isAsync = curClass(env)->classof(c_AsyncGenerator::classof()); // Load generator's FP and resume address. auto const genObj = ldThis(env); auto const genFp = gen(env, LdContActRec, IsAsyncData(isAsync), genObj); auto resumeAddr = gen(env, LdContResumeAddr, IsAsyncData(isAsync), genObj); // Make sure function enter hook is called if needed. auto const exitSlow = makeExitSlow(env); gen(env, CheckSurpriseFlags, exitSlow, fp(env)); // Exit to interpreter if resume address is not known. resumeAddr = gen(env, CheckNonNull, exitSlow, resumeAddr); spillStack(env); env.irb->exceptionStackBoundary(); auto returnBcOffset = returnOffset - curFunc(env)->base(); gen( env, ContEnter, ContEnterData { offsetFromIRSP(env, BCSPOffset{0}), returnBcOffset }, sp(env), fp(env), genFp, resumeAddr ); }
void emitContCurrent(HTS& env) { assert(curClass(env)); auto const cont = ldThis(env); gen(env, ContStartedCheck, makeExitSlow(env), cont); auto const offset = cns(env, offsetof(c_Generator, m_value)); auto const value = gen(env, LdContField, Type::Cell, cont, offset); pushIncRef(env, value); }
void emitContCheck(HTS& env, int32_t checkStarted) { assert(curClass(env)); assert(curClass(env)->classof(c_AsyncGenerator::classof()) || curClass(env)->classof(c_Generator::classof())); auto const cont = ldThis(env); gen(env, ContPreNext, makeExitSlow(env), cont, cns(env, static_cast<bool>(checkStarted))); }
void emitContCurrent(IRGS& env) { assertx(curClass(env)); auto const cont = ldThis(env); gen(env, ContStartedCheck, IsAsyncData(false), makeExitSlow(env), cont); auto const offset = cns(env, offsetof(Generator, m_value) - Generator::objectOff()); auto const value = gen(env, LdContField, TCell, cont, offset); pushIncRef(env, value); }
void emitCGetG(IRGS& env) { auto const exit = makeExitSlow(env); auto const name = topC(env); if (!name->isA(TStr)) PUNT(CGetG-NonStrName); auto const ptr = gen(env, LdGblAddr, exit, name); destroyName(env, name); pushIncRef( env, gen(env, LdMem, TCell, gen(env, UnboxPtr, ptr)) ); }
void emitBareThis(HTS& env, BareThisOp subop) { if (!curClass(env)) { interpOne(env, Type::InitNull, 0); // will raise notice and push null return; } auto const ctx = gen(env, LdCtx, fp(env)); if (subop == BareThisOp::NeverNull) { env.irb->setThisAvailable(); } else { gen(env, CheckCtxThis, makeExitSlow(env), ctx); } pushIncRef(env, gen(env, CastCtxThis, ctx)); }
void emitInitThisLoc(HTS& env, int32_t id) { if (!curClass(env)) { // Do nothing if this is null return; } auto const ldrefExit = makeExit(env); auto const oldLoc = ldLoc(env, id, ldrefExit, DataTypeCountness); auto const ctx = gen(env, LdCtx, fp(env)); gen(env, CheckCtxThis, makeExitSlow(env), ctx); auto const this_ = gen(env, CastCtxThis, ctx); gen(env, IncRef, this_); stLocRaw(env, id, fp(env), this_); gen(env, DecRef, oldLoc); }
void emitWHResult(IRGS& env) { assertx(topC(env)->isA(TObj)); auto const exitSlow = makeExitSlow(env); auto const child = popC(env); // In most conditions, this will be optimized out by the simplifier. // We already need to setup a side-exit for the !succeeded case. gen(env, JmpZero, exitSlow, gen(env, IsWaitHandle, child)); static_assert( c_WaitHandle::STATE_SUCCEEDED == 0, "we test state for non-zero, success must be zero" ); gen(env, JmpNZero, exitSlow, gen(env, LdWHState, child)); auto const res = gen(env, LdWHResult, TInitCell, child); gen(env, IncRef, res); gen(env, DecRef, child); push(env, res); }
void emitAwait(HTS& env, int32_t numIters) { auto const resumeOffset = nextBcOff(env); assert(curFunc(env)->isAsync()); if (curFunc(env)->isAsyncGenerator()) PUNT(Await-AsyncGenerator); auto const exitSlow = makeExitSlow(env); if (!topC(env)->isA(Type::Obj)) PUNT(Await-NonObject); auto const child = popC(env); gen(env, JmpZero, exitSlow, gen(env, IsWaitHandle, child)); // cns() would ODR-use these auto const kSucceeded = c_WaitHandle::STATE_SUCCEEDED; auto const kFailed = c_WaitHandle::STATE_FAILED; auto const state = gen(env, LdWHState, child); auto const failed = gen(env, EqInt, state, cns(env, kFailed)); gen(env, JmpNZero, exitSlow, failed); env.irb->ifThenElse( [&] (Block* taken) { auto const succeeded = gen(env, EqInt, state, cns(env, kSucceeded)); gen(env, JmpNZero, taken, succeeded); }, [&] { // Next: the wait handle is not finished, we need to suspend if (resumed(env)) { implAwaitR(env, child, resumeOffset); } else { implAwaitE(env, child, resumeOffset, numIters); } }, [&] { // Taken: retrieve the result from the wait handle auto const res = gen(env, LdWHResult, child); gen(env, IncRef, res); gen(env, DecRef, child); push(env, res); } ); }
void surpriseCheck(HTS& env, Offset relOffset) { if (relOffset < 0) { auto const exit = makeExitSlow(env); gen(env, CheckSurpriseFlags, exit); } }
void emitAwait(IRGS& env, int32_t numIters) { auto const resumeOffset = nextBcOff(env); assertx(curFunc(env)->isAsync()); if (curFunc(env)->isAsyncGenerator()) PUNT(Await-AsyncGenerator); auto const exitSlow = makeExitSlow(env); if (!topC(env)->isA(TObj)) PUNT(Await-NonObject); auto const child = popC(env); gen(env, JmpZero, exitSlow, gen(env, IsWaitHandle, child)); // cns() would ODR-use these auto const kSucceeded = c_WaitHandle::STATE_SUCCEEDED; auto const kFailed = c_WaitHandle::STATE_FAILED; auto const state = gen(env, LdWHState, child); /* * HHBBC may have proven something about the inner type of this wait handle. * * So, we may have an assertion on the type of the top of the stack after * this instruction. We know the next bytecode instruction is reachable from * fallthrough on the Await, so if it is an AssertRATStk 0, anything coming * out of the wait handle must be a subtype of that type, so this is a safe * and conservative way to do this optimization (even if our successor * bytecode offset is a jump target from things we aren't thinking about * here). */ auto const knownTy = [&] { auto pc = curUnit(env)->at(resumeOffset); if (*reinterpret_cast<const Op*>(pc) != Op::AssertRATStk) return TInitCell; ++pc; auto const stkLoc = decodeVariableSizeImm(&pc); if (stkLoc != 0) return TInitCell; auto const rat = decodeRAT(curUnit(env), pc); auto const ty = ratToAssertType(env, rat); return ty ? *ty : TInitCell; }(); ifThenElse( env, [&] (Block* taken) { auto const succeeded = gen(env, EqInt, state, cns(env, kSucceeded)); gen(env, JmpNZero, taken, succeeded); }, [&] { // Next: the wait handle is not finished, we need to suspend auto const failed = gen(env, EqInt, state, cns(env, kFailed)); gen(env, JmpNZero, exitSlow, failed); if (resumed(env)) { implAwaitR(env, child, resumeOffset); } else { implAwaitE(env, child, resumeOffset, numIters); } }, [&] { // Taken: retrieve the result from the wait handle auto const res = gen(env, LdWHResult, knownTy, child); gen(env, IncRef, res); gen(env, DecRef, child); push(env, res); } ); }