void c_Continuation::copyContinuationVars(ActRec* fp) { // For functions that contain only named locals, we can copy TVs // right to the local space. static const StringData* thisStr = s_this.get(); bool skipThis; if (fp->hasVarEnv()) { Stats::inc(Stats::Cont_CreateVerySlow); Array definedVariables = fp->getVarEnv()->getDefinedVariables(); skipThis = definedVariables.exists(s_this, true); for (ArrayIter iter(definedVariables); !iter.end(); iter.next()) { dupContVar(iter.first().getStringData(), const_cast<TypedValue *>(iter.secondRef().asTypedValue())); } } else { const Func *genFunc = actRec()->m_func; skipThis = genFunc->lookupVarId(thisStr) != kInvalidId; for (Id i = 0; i < genFunc->numNamedLocals(); ++i) { dupContVar(genFunc->localVarName(i), frame_local(fp, i)); } } // If $this is used as a local inside the body and is not provided // by our containing environment, just prefill it here instead of // using InitThisLoc inside the body if (!skipThis && fp->hasThis()) { Id id = actRec()->m_func->lookupVarId(thisStr); if (id != kInvalidId) { tvAsVariant(frame_local(actRec(), id)) = fp->getThis(); } } }
String c_Continuation::t_getorigfuncname() { INSTANCE_METHOD_INJECTION_BUILTIN(Continuation, Continuation::getorigfuncname); String called_class; if (hhvm) { if (actRec()->hasThis()) { called_class = actRec()->getThis()->getVMClass()->name()->data(); } else if (actRec()->hasClass()) { called_class = actRec()->getClass()->name()->data(); } } else { called_class = getCalledClass(); } if (called_class.size() == 0) { return m_origFuncName; } /* Replace the class name in m_origFuncName with the LSB class. This produces more useful traces. */ size_t method_pos = m_origFuncName.find("::"); if (method_pos != std::string::npos) { return concat3(called_class, "::", m_origFuncName.substr(method_pos+2)); } else { return m_origFuncName; } }
// Get the filename in which execution will proceed when execution resumes. String c_AsyncFunctionWaitHandle::getFileName() { if (actRec()->func()->originalFilename()) { return actRec()->func()->originalFilename()->data(); } else { return actRec()->func()->unit()->filepath()->data(); } }
c_AsyncFunctionWaitHandle::~c_AsyncFunctionWaitHandle() { if (LIKELY(isFinished())) { return; } assert(!isRunning()); frame_free_locals_inl_no_hook<false>(actRec(), actRec()->func()->numLocals()); decRefObj(m_children[0].getChild()); }
c_AsyncFunctionWaitHandle::~c_AsyncFunctionWaitHandle() { if (LIKELY(isFinished())) { assert(!m_child); return; } frame_free_locals_inl_no_hook<false>(actRec(), actRec()->func()->numLocals()); if (m_child) { decRefObj(m_child); } }
String c_Continuation::t_getcalledclass() { String called_class; if (actRec()->hasThis()) { called_class = actRec()->getThis()->getVMClass()->name()->data(); } else if (actRec()->hasClass()) { called_class = actRec()->getClass()->name()->data(); } else { called_class = String(""); } return called_class; }
// A AsyncSuspend is used in the codegen for an async function to setup // a Continuation and return a wait handle so execution can continue // later. We have just completed a AsyncSuspend, so the new // Continuation is available, and it can predict where execution will // resume. void CmdNext::stepAfterAsyncSuspend() { auto topObj = g_context->getStack().topTV()->m_data.pobj; assert(topObj->instanceof(c_AsyncFunctionWaitHandle::classof())); auto wh = static_cast<c_AsyncFunctionWaitHandle*>(topObj); auto func = wh->actRec()->m_func; Offset nextInst = wh->getNextExecutionOffset(); assert(nextInst != InvalidAbsoluteOffset); m_stepContTag = wh->actRec(); TRACE(2, "CmdNext: patch for cont step after AsyncSuspend at '%s' offset %d\n", func->fullName()->data(), nextInst); m_stepCont = StepDestination(func->unit(), nextInst); }
String c_Generator::t_getcalledclass() { String called_class; if (actRec()->hasThis()) { called_class = actRec()->getThis()->getVMClass()->name()->data(); } else if (actRec()->hasClass()) { called_class = actRec()->getClass()->name()->data(); } else { called_class = empty_string(); } return called_class; }
// An Await opcode is used in the codegen for an async function to suspend // execution until the given wait handle is finished. In eager execution, // the state is suspended into a new AsyncFunctionWaitHandle object so that // the execution can continue later. We have just completed an Await, so // the new AsyncFunctionWaitHandle is now available, and it can predict // where execution will resume. void CmdNext::stepAfterAwait() { auto topObj = vmsp()->m_data.pobj; assertx(topObj->instanceof(c_AsyncFunctionWaitHandle::classof())); auto wh = static_cast<c_AsyncFunctionWaitHandle*>(topObj); auto func = wh->actRec()->func(); Offset nextInst = wh->getNextExecutionOffset(); assertx(nextInst != InvalidAbsoluteOffset); m_stepResumableId = wh->actRec(); TRACE(2, "CmdNext: patch for cont step after Await at '%s' offset %d\n", func->fullName()->data(), nextInst); m_stepResumable = StepDestination(func->unit(), nextInst); }
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); }
c_AsyncFunctionWaitHandle* c_AsyncFunctionWaitHandle::Create(const ActRec* fp, size_t numSlots, jit::TCA resumeAddr, Offset resumeOffset, c_WaitableWaitHandle* child) { assert(fp); assert(!fp->resumed()); assert(fp->func()->isAsyncFunction()); assert(child); assert(child->instanceof(c_WaitableWaitHandle::classof())); assert(!child->isFinished()); constexpr const size_t objSize = sizeof(c_AsyncFunctionWaitHandle); void* obj = Resumable::Create<false, objSize, mayUseVV>(fp, numSlots, resumeAddr, resumeOffset); auto const waitHandle = new (obj) c_AsyncFunctionWaitHandle(); assert(waitHandle->hasExactlyOneRef()); waitHandle->actRec()->setReturnVMExit(); assert(waitHandle->noDestruct()); waitHandle->initialize(child); return waitHandle; }
c_AsyncFunctionWaitHandle* c_AsyncFunctionWaitHandle::Create(const ActRec* fp, size_t numSlots, jit::TCA resumeAddr, Offset resumeOffset, c_WaitableWaitHandle* child) { assert(fp); assert(!fp->resumed()); assert(fp->func()->isAsyncFunction()); assert(child); assert(child->instanceof(c_WaitableWaitHandle::classof())); assert(!child->isFinished()); const size_t frameSize = Resumable::getFrameSize(numSlots); const size_t totalSize = sizeof(ResumableNode) + frameSize + sizeof(Resumable) + sizeof(c_AsyncFunctionWaitHandle); auto const resumable = Resumable::Create(frameSize, totalSize); resumable->initialize<false, mayUseVV>(fp, resumeAddr, resumeOffset, frameSize, totalSize); auto const waitHandle = new (resumable + 1) c_AsyncFunctionWaitHandle(); assert(waitHandle->hasExactlyOneRef()); waitHandle->actRec()->setReturnVMExit(); assert(waitHandle->noDestruct()); waitHandle->initialize(child); return waitHandle; }
String c_Continuation::t_getorigfuncname() { const Func* origFunc = actRec()->func()->getGeneratorOrigFunc(); auto const origName = origFunc->isClosureBody() ? s__closure_.get() : origFunc->name(); assert(origName->isStatic()); return String(const_cast<StringData*>(origName)); }
// 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()); }
bool CmdVariable::onServer(DebuggerProxy &proxy) { if (m_type == KindOfVariableAsync) { //we only do variable inspection on continuation wait handles auto frame = getWaitHandleAtAsyncStackPosition(m_frame); if (frame != nullptr) { auto fp = frame->actRec(); if (fp != nullptr) { m_variables = getVariables(fp); } } } else if (m_frame < 0) { m_variables = g_context->m_globalVarEnv->getDefinedVariables(); m_global = true; } else { m_variables = g_context->getLocalDefinedVariables(m_frame); m_global = g_context->getVarEnv(m_frame) == g_context->m_globalVarEnv; } if (m_global) { m_variables.remove(s_GLOBALS); } if (m_version == 1) { // Remove the values before sending to client. ArrayInit ret(m_variables->size(), ArrayInit::Map{}); Variant v; for (ArrayIter iter(m_variables); iter; ++iter) { ret.add(iter.first().toString(), v); } m_variables = ret.toArray(); m_version = 2; } else if (m_version == 2) { // Remove entries that do not match a non empty m_varName. if (!m_varName.empty()) { ArrayInit ret(1, ArrayInit::Map{}); ret.add(m_varName, m_variables[m_varName]); m_variables = ret.toArray(); } // Remove entries whose name or contents do not match a non empty m_filter if (!m_filter.empty()) { ArrayInit ret(m_variables.size(), ArrayInit::Map{}); for (ArrayIter iter(m_variables); iter; ++iter) { String name = iter.first().toString(); if (name.find(m_filter, 0, false) < 0) { String fullvalue = DebuggerClient::FormatVariable(iter.second(), -1); if (fullvalue.find(m_filter, 0, false) < 0) { continue; } } ret.add(name, iter.second()); } m_variables = ret.toArray(); } } return proxy.sendToClient(this); }
c_Continuation::~c_Continuation() { ActRec* ar = actRec(); if (ar->hasVarEnv()) { ar->getVarEnv()->detach(ar); } else { frame_free_locals_inl(ar, ar->m_func->numLocals()); } }
c_Continuation::c_Continuation(const ObjectStaticCallbacks *cb) : ExtObjectData(cb), #ifndef HHVM LABEL_INIT, #endif m_index(-1LL), m_value(Variant::nullInit), m_received(Variant::nullInit), m_done(false), m_running(false), m_should_throw(false), m_isMethod(false), m_callInfo(NULL) #ifdef HHVM , LABEL_INIT #endif { } #undef LABEL_INIT c_Continuation::~c_Continuation() { if (hhvm) { VM::ActRec* ar = actRec(); // The first local is the object itself, and it wasn't increffed at creation // time (see createContinuation()). Overwrite its type to exempt it from // refcounting here. TypedValue* contLocal = frame_local(ar, 0); ASSERT(contLocal->m_data.pobj == this); contLocal->m_type = KindOfNull; if (ar->hasVarEnv()) { VM::VarEnv::destroy(ar->getVarEnv()); } else { frame_free_locals_inl(ar, m_vmFunc->numLocals()); } } } void c_Continuation::t___construct( int64 func, int64 extra, bool isMethod, CStrRef origFuncName, CVarRef obj, CArrRef args) { INSTANCE_METHOD_INJECTION_BUILTIN(Continuation, Continuation::__construct); if (hhvm) { m_vmFunc = (VM::Func*) extra; ASSERT(m_vmFunc); } else { m_callInfo = (const CallInfo*) func; ASSERT(m_callInfo); } m_isMethod = isMethod; m_origFuncName = origFuncName; if (!obj.isNull()) { m_obj = obj.toObject(); ASSERT(!m_obj.isNull()); } else { ASSERT(m_obj.isNull()); } m_args = args; }
c_Continuation *c_Continuation::clone() { const Func *origFunc = m_origFunc; const Func *genFunc = actRec()->m_func; ActRec *fp = g_vmContext->getFP(); c_Continuation* cont = origFunc->isMethod() ? g_vmContext->createContMeth(origFunc, genFunc, fp->getThisOrClass()) : g_vmContext->createContFunc(origFunc, genFunc); cont->copyContinuationVars(actRec()); cont->o_subclassData.u16 = o_subclassData.u16; cont->m_label = m_label; cont->m_index = m_index; cont->m_key = m_key; cont->m_value = m_value; return cont; }
c_Continuation::~c_Continuation() { tvRefcountedDecRef(m_key); tvRefcountedDecRef(m_value); // Free locals, but don't trigger the EventHook for FunctionExit // since the continuation function has already been exited. We // don't want redundant calls. ActRec* ar = actRec(); frame_free_locals_inl_no_hook<false>(ar, ar->m_func->numLocals()); }
void c_Continuation::suspend(Offset offset, const Cell& key, const Cell& value) { assert(actRec()->func()->contains(offset)); m_offset = offset; 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; } }
c_Continuation *c_Continuation::Clone(ObjectData* obj) { auto thiz = static_cast<c_Continuation*>(obj); const Func *origFunc = thiz->m_origFunc; const Func *genFunc = thiz->actRec()->m_func; ActRec *fp = g_vmContext->getFP(); c_Continuation* cont = origFunc->isMethod() ? g_vmContext->createContMeth(origFunc, genFunc, fp->getThisOrClass()) : g_vmContext->createContFunc(origFunc, genFunc); cont->copyContinuationVars(thiz->actRec()); cont->o_subclassData.u16 = thiz->o_subclassData.u16; cont->m_label = thiz->m_label; cont->m_index = thiz->m_index; cont->m_key = thiz->m_key; cont->m_value = thiz->m_value; return cont; }
String c_AsyncFunctionWaitHandle::getName() { switch (getState()) { case STATE_BLOCKED: case STATE_READY: case STATE_RUNNING: { auto func = actRec()->func(); if (!func->cls() || func->cls()->attrs() & AttrNoOverride || actRec()->localsDecRefd()) { auto name = func->fullName(); if (func->isClosureBody()) { const char* p = strchr(name->data(), ':'); if (p) { return concat(String(name->data(), p + 1 - name->data(), CopyString), s__closure_); } else { return s__closure_; } } return String{const_cast<StringData*>(name)}; } auto const cls = actRec()->hasThis() ? actRec()->getThis()->getVMClass() : actRec()->getClass(); if (cls == func->cls() && !func->isClosureBody()) { return String{const_cast<StringData*>(func->fullName())}; } StrNR funcName(func->isClosureBody() ? s__closure_.get() : func->name()); return concat3(cls->nameStr(), "::", funcName); } default: raise_fatal_error( "Invariant violation: encountered unexpected state"); } }
AsyncGenerator::~AsyncGenerator() { if (LIKELY(getState() == State::Done)) { return; } assert(!isRunning()); // Free locals, but don't trigger the EventHook for FunctionReturn since // the generator has already been exited. We don't want redundant calls. ActRec* ar = actRec(); frame_free_locals_inl_no_hook(ar, ar->m_func->numLocals()); }
c_Continuation::~c_Continuation() { ActRec* ar = actRec(); if (ar->hasVarEnv()) { ar->getVarEnv()->detach(ar); } else { // Free locals, but don't trigger the EventHook for FunctionExit // since the continuation function has already been exited. We // don't want redundant calls. frame_free_locals_inl_no_hook<false>(ar, ar->m_func->numLocals()); } }
String c_AsyncFunctionWaitHandle::getName() { switch (getState()) { case STATE_BLOCKED: case STATE_SCHEDULED: case STATE_RUNNING: { String funcName; if (actRec()->func()->isClosureBody()) { // Can we do better than this? funcName = s__closure_; } else { funcName = actRec()->func()->name()->data(); } String clsName; if (actRec()->hasThis()) { clsName = actRec()->getThis()->getVMClass()->name()->data(); } else if (actRec()->hasClass()) { clsName = actRec()->getClass()->name()->data(); } else { return funcName; } return concat3(clsName, "::", funcName); } default: throw FatalErrorException( "Invariant violation: encountered unexpected state"); } }
void c_Continuation::dupContVar(const StringData* name, TypedValue* src) { ActRec *fp = actRec(); Id destId = fp->m_func->lookupVarId(name); if (destId != kInvalidId) { // Copy the value of the local to the cont object. tvDupFlattenVars(src, frame_local(fp, destId)); } else { if (!fp->hasVarEnv()) { fp->setVarEnv(VarEnv::createLocal(fp)); } fp->getVarEnv()->setWithRef(name, src); } }
Generator::~Generator() { if (LIKELY(getState() == State::Done)) { return; } assert(getState() != State::Running); tvRefcountedDecRef(m_key); tvRefcountedDecRef(m_value); // Free locals, but don't trigger the EventHook for FunctionReturn since // the generator has already been exited. We don't want redundant calls. ActRec* ar = actRec(); frame_free_locals_inl_no_hook<false>(ar, ar->func()->numLocals()); }
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; }
c_Generator::~c_Generator() { if (LIKELY(getState() == Done)) { return; } tvRefcountedDecRef(m_key); tvRefcountedDecRef(m_value); // Free locals, but don't trigger the EventHook for FunctionReturn // since the generator has already been exited. We // don't want redundant calls. ActRec* ar = actRec(); frame_free_locals_inl_no_hook<false>(ar, ar->m_func->numLocals()); }
String c_AsyncFunctionWaitHandle::getName() { switch (getState()) { case STATE_BLOCKED: case STATE_READY: case STATE_RUNNING: { auto func = actRec()->func(); if (!actRec()->getThisOrClass() || func->cls()->attrs() & AttrNoOverride) { auto name = func->fullName(); if (func->isClosureBody()) { const char* p = strchr(name->data(), ':'); if (p) { return concat(String(name->data(), p + 1 - name->data(), CopyString), s__closure_); } else { return s__closure_; } } return String{const_cast<StringData*>(name)}; } String funcName; if (actRec()->func()->isClosureBody()) { // Can we do better than this? funcName = s__closure_; } else { funcName = const_cast<StringData*>(actRec()->func()->name()); } String clsName; if (actRec()->hasThis()) { clsName = const_cast<StringData*>(actRec()->getThis()-> getVMClass()->name()); } else if (actRec()->hasClass()) { clsName = const_cast<StringData*>(actRec()->getClass()->name()); } else { return funcName; } return concat3(clsName, "::", funcName); } default: raise_fatal_error( "Invariant violation: encountered unexpected state"); } }