c_Generator *c_Generator::Clone(ObjectData* obj) { auto thiz = static_cast<c_Generator*>(obj); auto fp = thiz->actRec(); c_Generator* cont = Create(fp, thiz->resumable()->resumeAddr(), thiz->resumable()->resumeOffset()); cont->copyVars(fp); cont->setState(thiz->getState()); cont->m_index = thiz->m_index; cellSet(thiz->m_key, cont->m_key); cellSet(thiz->m_value, cont->m_value); return cont; }
void c_AsyncFunctionWaitHandle::resume() { auto const child = m_children[0].getChild(); assert(getState() == STATE_READY); assert(child->isFinished()); setState(STATE_RUNNING); if (LIKELY(child->isSucceeded())) { // child succeeded, pass the result to the async function g_context->resumeAsyncFunc(resumable(), child, child->getResult()); } else { // child failed, raise the exception inside the async function g_context->resumeAsyncFuncThrow(resumable(), child, child->getException()); } }
void delete_AsyncFunctionWaitHandle(ObjectData* od, const Class*) { auto const waitHandle = static_cast<c_AsyncFunctionWaitHandle*>(od); auto const size = waitHandle->resumable()->size(); auto const base = (char*)(waitHandle + 1) - size; waitHandle->~c_AsyncFunctionWaitHandle(); MM().objFreeLogged(base, size); }
void delete_AsyncGenerator(ObjectData* od, const Class*) { auto const gen = static_cast<c_AsyncGenerator*>(od); auto const size = gen->resumable()->size(); auto const base = (char*)(gen + 1) - size; gen->~c_AsyncGenerator(); MM().objFreeLogged(base, size); }
void delete_Generator(ObjectData* od, const Class*) { auto const cont = static_cast<c_Generator*>(od); auto const size = cont->resumable()->size(); auto const base = (char*)(cont + 1) - size; cont->~c_Generator(); MM().objFreeLogged(base, size); }
static ActRec* getPrevActRec(const ActRec* fp, Offset* prevPc) { if (fp && fp->func() && fp->resumed() && fp->func()->isAsyncFunction()) { c_WaitableWaitHandle* currentWaitHandle = frame_afwh(fp); if (currentWaitHandle->isFinished()) { /* * It's possible in very rare cases (it will return a truncated stack): * 1) async function which WaitHandle is not referenced by anything * else finishes * 2) its return value is an object with destructor * 3) this destructor gets called as part of destruction of the * WaitHandleobject, which happens right before FP is adjusted */ return nullptr; } auto const contextIdx = currentWaitHandle->getContextIdx(); while (currentWaitHandle != nullptr) { auto p = currentWaitHandle->getParentChain().firstInContext(contextIdx); if (p == nullptr) break; if (p->getKind() == c_WaitHandle::Kind::AsyncFunction) { auto wh = p->asAsyncFunction(); *prevPc = wh->resumable()->resumeOffset(); return wh->actRec(); } currentWaitHandle = p; } *prevPc = 0; return AsioSession::Get()->getContext(contextIdx)->getSavedFP(); } return g_context->getPrevVMState(fp, prevPc); }
// Get the line number on which execution will proceed when execution resumes. int c_AsyncFunctionWaitHandle::getLineNumber() { if (isFinished()) { return -1; } always_assert(!isRunning()); return actRec()->func()->unit()->getLineNumber(resumable()->resumeOffset()); }
void c_Generator::suspend(JIT::TCA resumeAddr, Offset resumeOffset, const Cell& value) { assert(getState() == Running); resumable()->setResumeAddr(resumeAddr, resumeOffset); cellSet(make_tv<KindOfInt64>(++m_index), m_key); cellSet(value, m_value); setState(Started); }
// Get the next execution offset Offset c_AsyncFunctionWaitHandle::getNextExecutionOffset() { if (isFinished()) { return InvalidAbsoluteOffset; } always_assert(!isRunning()); return resumable()->resumeOffset(); }
void c_AsyncFunctionWaitHandle::resume() { // may happen if scheduled in multiple contexts if (getState() != STATE_SCHEDULED) { decRefObj(this); return; } assert(getState() == STATE_SCHEDULED); assert(m_child->isFinished()); setState(STATE_RUNNING); if (LIKELY(m_child->isSucceeded())) { // child succeeded, pass the result to the async function g_context->resumeAsyncFunc(resumable(), m_child, m_child->getResult()); } else { // child failed, raise the exception inside the async function g_context->resumeAsyncFuncThrow(resumable(), m_child, m_child->getException()); } }
void c_AsyncFunctionWaitHandle::await(Offset resumeOffset, c_WaitableWaitHandle* child) { // Prepare child for establishing dependency. May throw. prepareChild(child); // Suspend the async function. resumable()->setResumeAddr(nullptr, resumeOffset); // Set up the dependency. setState(STATE_BLOCKED); m_children[0].setChild(child); }
void c_Generator::suspend(JIT::TCA resumeAddr, Offset resumeOffset, const Cell& key, const Cell& value) { assert(getState() == Running); resumable()->setResumeAddr(resumeAddr, resumeOffset); cellSet(key, m_key); cellSet(value, m_value); if (m_key.m_type == KindOfInt64) { int64_t new_index = m_key.m_data.num; m_index = new_index > m_index ? new_index : m_index; } setState(Started); }
c_AsyncGeneratorWaitHandle* AsyncGenerator::await(Offset resumeOffset, c_WaitableWaitHandle* child) { assert(isRunning()); resumable()->setResumeAddr(nullptr, resumeOffset); if (m_waitHandle) { // Resumed execution. m_waitHandle->await(child); return nullptr; } // Eager executon. m_waitHandle = c_AsyncGeneratorWaitHandle::Create(this, child); return m_waitHandle.get(); }
Generator& Generator::operator=(const Generator& other) { auto const fp = other.actRec(); const size_t numSlots = fp->func()->numSlotsInFrame(); const size_t frameSz = Resumable::getFrameSize(numSlots); const size_t genSz = genSize(sizeof(Generator), frameSz); resumable()->initialize<true>(fp, other.resumable()->resumeAddr(), other.resumable()->resumeOffset(), frameSz, genSz); copyVars(fp); setState(other.getState()); m_index = other.m_index; cellSet(other.m_key, m_key); cellSet(other.m_value, m_value); return *this; }
ObjectData* AsyncGenerator::Create(const ActRec* fp, size_t numSlots, jit::TCA resumeAddr, Offset resumeOffset) { assert(fp); assert(!fp->resumed()); assert(fp->func()->isAsyncGenerator()); const size_t frameSz = Resumable::getFrameSize(numSlots); const size_t genSz = genSize(sizeof(AsyncGenerator), frameSz); auto const obj = BaseGenerator::Alloc<AsyncGenerator>(s_class, genSz); auto const genData = new (Native::data<AsyncGenerator>(obj)) AsyncGenerator(); genData->resumable()->initialize<false>(fp, resumeAddr, resumeOffset, frameSz, genSz); genData->setState(State::Created); return obj; }
void c_Generator::yield(Offset resumeOffset, const Cell* key, const Cell& value) { assert(getState() == State::Running); resumable()->setResumeAddr(nullptr, resumeOffset); if (key) { cellSet(*key, m_key); tvRefcountedDecRefNZ(*key); if (m_key.m_type == KindOfInt64) { int64_t new_index = m_key.m_data.num; m_index = new_index > m_index ? new_index : m_index; } } else { cellSet(make_tv<KindOfInt64>(++m_index), m_key); } cellSet(value, m_value); tvRefcountedDecRefNZ(value); setState(State::Started); }
static ActRec* getPrevActRec( const ActRec* fp, Offset* prevPc, folly::small_vector<c_WaitableWaitHandle*, 64>& visitedWHs) { if (fp && fp->func() && fp->resumed() && fp->func()->isAsyncFunction()) { c_WaitableWaitHandle* currentWaitHandle = frame_afwh(fp); if (currentWaitHandle->isFinished()) { /* * It's possible in very rare cases (it will return a truncated stack): * 1) async function which WaitHandle is not referenced by anything * else finishes * 2) its return value is an object with destructor * 3) this destructor gets called as part of destruction of the * WaitHandleobject, which happens right before FP is adjusted */ return nullptr; } auto const contextIdx = currentWaitHandle->getContextIdx(); while (currentWaitHandle != nullptr) { auto p = currentWaitHandle->getParentChain().firstInContext(contextIdx); if (p == nullptr || UNLIKELY(std::find(visitedWHs.begin(), visitedWHs.end(), p) != visitedWHs.end())) { // If the parent exists in our backtrace, it means we have detected a // cycle. Fall back to savedFP in that case. break; } visitedWHs.push_back(p); if (p->getKind() == c_WaitHandle::Kind::AsyncFunction) { auto wh = p->asAsyncFunction(); *prevPc = wh->resumable()->resumeOffset(); return wh->actRec(); } currentWaitHandle = p; } *prevPc = 0; return AsioSession::Get()->getContext(contextIdx)->getSavedFP(); } return g_context->getPrevVMState(fp, prevPc); }
c_StaticWaitHandle* AsyncGenerator::yield(Offset resumeOffset, const Cell* key, const Cell value) { assert(isRunning()); resumable()->setResumeAddr(nullptr, resumeOffset); setState(State::Started); auto keyValueTuple = make_packed_array( key ? Variant(tvAsCVarRef(key), Variant::CellCopy()) : init_null_variant, Variant(tvAsCVarRef(&value), Variant::CellCopy())); auto keyValueTupleTV = make_tv<KindOfArray>(keyValueTuple.detach()); if (m_waitHandle) { // Resumed execution. req::ptr<c_AsyncGeneratorWaitHandle> wh(std::move(m_waitHandle)); wh->ret(*tvAssertCell(&keyValueTupleTV)); return nullptr; } // Eager execution. return c_StaticWaitHandle::CreateSucceeded(keyValueTupleTV); }
void delete_AsyncFunctionWaitHandle(ObjectData* od, const Class*) { auto wh = static_cast<c_AsyncFunctionWaitHandle*>(od); Resumable::Destroy(wh->resumable()->size(), wh); }
void c_AsyncFunctionWaitHandle::suspend(JIT::TCA resumeAddr, Offset resumeOffset, c_WaitableWaitHandle* child) { resumable()->setResumeAddr(resumeAddr, resumeOffset); m_child = child; }
void c_AsyncFunctionWaitHandle::run() { // may happen if scheduled in multiple contexts if (getState() != STATE_SCHEDULED) { return; } try { setState(STATE_RUNNING); // resume async function if (LIKELY(m_child->isSucceeded())) { // child succeeded, pass the result to the async function g_context->resumeAsyncFunc(resumable(), m_child, m_child->getResult()); } else if (m_child->isFailed()) { // child failed, raise the exception inside the async function g_context->resumeAsyncFuncThrow(resumable(), m_child, m_child->getException()); } else { throw FatalErrorException( "Invariant violation: child neither succeeded nor failed"); } retry: // async function reached RetC, which already set m_resultOrException if (isSucceeded()) { m_child = nullptr; markAsSucceeded(); return; } // async function reached AsyncSuspend, which already set m_child assert(!m_child->isFinished()); assert(m_child->instanceof(c_WaitableWaitHandle::classof())); // import child into the current context, detect cross-context cycles try { child()->enterContext(getContextIdx()); } catch (Object& e) { g_context->resumeAsyncFuncThrow(resumable(), m_child, e.get()); goto retry; } // detect cycles if (UNLIKELY(isDescendantOf(child()))) { Object e(createCycleException(child())); g_context->resumeAsyncFuncThrow(resumable(), m_child, e.get()); goto retry; } // on await callback AsioSession* session = AsioSession::Get(); if (UNLIKELY(session->hasOnAsyncFunctionAwaitCallback())) { session->onAsyncFunctionAwait(this, m_child); } // set up dependency setState(STATE_BLOCKED); blockOn(child()); } catch (const Object& exception) { // process exception thrown by the async function m_child = nullptr; markAsFailed(exception); } catch (...) { // process C++ exception m_child = nullptr; markAsFailed(AsioSession::Get()->getAbruptInterruptException()); throw; } }