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 emitCreateCont(IRGS& env) { auto const resumeOffset = nextBcOff(env); assertx(!resumed(env)); assertx(curFunc(env)->isGenerator()); if (curFunc(env)->isAsyncGenerator()) PUNT(CreateCont-AsyncGenerator); // Create the Generator object. CreateCont takes care of copying local // variables and iterators. auto const func = curFunc(env); auto const resumeSk = SrcKey(func, resumeOffset, true); auto const bind_data = LdBindAddrData { resumeSk, invSPOff(env) + 1 }; auto const resumeAddr = gen(env, LdBindAddr, bind_data); auto const cont = gen(env, CreateCont, fp(env), cns(env, func->numSlotsInFrame()), resumeAddr, cns(env, resumeOffset)); // The suspend hook will decref the newly created generator if it throws. auto const contAR = gen(env, LdContActRec, IsAsyncData(curFunc(env)->isAsync()), cont); suspendHookE(env, fp(env), contAR, cont); // Grab caller info from ActRec, free ActRec, store the return value // and return control to the caller. gen(env, StRetVal, fp(env), cont); auto const ret_data = RetCtrlData { offsetToReturnSlot(env), false }; gen(env, RetCtrl, ret_data, sp(env), fp(env)); }
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 emitCreateCont(HTS& env) { auto const resumeOffset = nextBcOff(env); assert(!resumed(env)); assert(curFunc(env)->isGenerator()); if (curFunc(env)->isAsyncGenerator()) PUNT(CreateCont-AsyncGenerator); // Create the Generator object. CreateCont takes care of copying local // variables and iterators. auto const func = curFunc(env); auto const resumeSk = SrcKey(func, resumeOffset, true); auto const resumeAddr = gen(env, LdBindAddr, LdBindAddrData(resumeSk)); auto const cont = gen(env, CreateCont, fp(env), cns(env, func->numSlotsInFrame()), resumeAddr, cns(env, resumeOffset)); // The suspend hook will decref the newly created generator if it throws. auto const contAR = gen(env, LdContActRec, cont); suspendHookE(env, fp(env), contAR); // Grab caller info from ActRec, free ActRec, store the return value // and return control to the caller. gen(env, StRetVal, fp(env), cont); auto const retAddr = gen(env, LdRetAddr, fp(env)); auto const stack = gen(env, RetAdjustStack, fp(env)); auto const frame = gen(env, FreeActRec, fp(env)); gen(env, RetCtrl, RetCtrlData(false), stack, frame, retAddr); }
Block* makeExitSlow(IRGS& env) { auto const exit = env.unit.defBlock(Block::Hint::Unlikely); BlockPusher bp(*env.irb, makeMarker(env, bcOff(env)), exit); interpOne(env, *env.currentNormalizedInstruction); // If it changes the PC, InterpOneCF will get us to the new location. if (!opcodeChangesPC(env.currentNormalizedInstruction->op())) { gen(env, Jmp, makeExit(env, nextBcOff(env))); } return exit; }
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 emitYieldK(IRGS& env) { auto const resumeOffset = nextBcOff(env); assertx(resumed(env)); assertx(curFunc(env)->isGenerator()); if (curFunc(env)->isAsyncGenerator()) PUNT(YieldK-AsyncGenerator); yieldImpl(env, resumeOffset); auto const newKey = popC(env); auto const oldKey = gen(env, LdContArKey, TCell, fp(env)); gen(env, StContArKey, fp(env), newKey); gen(env, DecRef, oldKey); auto const keyType = newKey->type(); if (keyType <= TInt) { gen(env, ContArUpdateIdx, fp(env), newKey); } yieldReturnControl(env); }
void emitYield(IRGS& env) { auto const resumeOffset = nextBcOff(env); assertx(resumed(env)); assertx(curFunc(env)->isGenerator()); if (curFunc(env)->isAsyncGenerator()) PUNT(Yield-AsyncGenerator); yieldImpl(env, resumeOffset); // take a fast path if this generator has no yield k => v; if (curFunc(env)->isPairGenerator()) { auto const newIdx = gen(env, ContArIncIdx, fp(env)); auto const oldKey = gen(env, LdContArKey, TCell, fp(env)); gen(env, StContArKey, fp(env), newIdx); gen(env, DecRef, oldKey); } else { // we're guaranteed that the key is an int gen(env, ContArIncKey, fp(env)); } yieldReturnControl(env); }
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); } ); }