From 705d2ac5cd815daabbd6bd014e469d8c8e95643b Mon Sep 17 00:00:00 2001 From: Mirek Klimos Date: Mon, 5 Aug 2013 20:04:50 -0700 Subject: [PATCH] Eager execution of async function. Currently, a call to an async function returns a (Continuation)WaitHandle object but the excution of the function body doesn't start before await (or join()) on the WaitHandle is called. This changes it to start the execution immediately and the control is returned to the caller only after it's blocked. Differential Revision: D946914 --- hphp/compiler/analysis/emitter.cpp | 186 ++++++++++++++---- hphp/compiler/analysis/emitter.h | 4 + hphp/compiler/expression/closure_expression.h | 4 + hphp/doc/bytecode.specification | 8 +- hphp/doc/ir.specification | 7 +- .../ext/asio/continuation_wait_handle.cpp | 13 ++ hphp/runtime/vm/bytecode.cpp | 32 +++ hphp/runtime/vm/hhbc.h | 1 + hphp/runtime/vm/jit/code-gen.cpp | 11 ++ hphp/runtime/vm/jit/hhbc-translator.cpp | 72 ++++++- hphp/runtime/vm/jit/hhbc-translator.h | 1 + hphp/runtime/vm/jit/ir-translator.cpp | 4 + hphp/runtime/vm/jit/ir.h | 2 + hphp/runtime/vm/jit/translator-instrs.h | 1 + hphp/runtime/vm/jit/translator.cpp | 5 +- ...ync_stack.php => async_stack.php.disabled} | 0 ...eak_async.php => break_async.php.disabled} | 0 ..._async.php => flow_gen_async.php.disabled} | 0 hphp/test/slow/async/iterator.php.expect | 4 +- 19 files changed, 313 insertions(+), 42 deletions(-) rename hphp/test/quick/debugger/{async_stack.php => async_stack.php.disabled} (100%) rename hphp/test/quick/debugger/{break_async.php => break_async.php.disabled} (100%) rename hphp/test/quick/debugger/{flow_gen_async.php => flow_gen_async.php.disabled} (100%) diff --git a/hphp/compiler/analysis/emitter.cpp b/hphp/compiler/analysis/emitter.cpp index 75cb58307..aa2647fdc 100644 --- a/hphp/compiler/analysis/emitter.cpp +++ b/hphp/compiler/analysis/emitter.cpp @@ -2213,6 +2213,25 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) { case Statement::KindOfReturnStatement: { ReturnStatementPtr r(static_pointer_cast(node)); + + // if returning from (outer) async function, + // wrap the result into StaticResultWaitHandle + if (node->getFunctionScope()->isAsync() && + !m_curFunc->isGenerator()) { + if (visit(r->getRetExp())) { + emitConvertToCell(e); + } else { + e.Null(); + } + Id tempLocal = emitSetUnnamedL(e); + Offset start = m_ue.bcPos(); + emitFreePendingIters(e); + emitCreateStaticWaitHandle(e, "StaticResultWaitHandle", + [&]() { emitPushAndFreeUnnamedL(e, tempLocal, start); }); + e.RetC(); + return false; + } + bool retV = false; if (visit(r->getRetExp())) { if (r->getRetExp()->getContext() & Expression::RefValue) { @@ -3854,6 +3873,21 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) { } } + // We're still at the closure definition site. Emit code to instantiate + // the new anonymous class, with the use variables as arguments. + ExpressionListPtr valuesList(ce->getClosureValues()); + for (int i = 0; i < useCount; ++i) { + emitBuiltinCallArg(e, (*valuesList)[i], i, useVars[i].second); + } + + if (node->getFunctionScope()->isAsync() && m_curFunc->isGenerator()) { + // Closure definition in the body of async function. The closure + // body was already emitted, so we just create the object here. + assert(ce->getClosureClassName()); + e.CreateCl(useCount, ce->getClosureClassName()); + return true; + } + // The parser generated a unique name for the function, // use that for the class std::string clsName = ce->getClosureFunction()->getOriginalName(); @@ -3867,13 +3901,6 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) { StringData* className = StringData::GetStaticString(clsName); - // We're still at the closure definition site. Emit code to instantiate - // the new anonymous class, with the use variables as arguments. - ExpressionListPtr valuesList(ce->getClosureValues()); - for (int i = 0; i < useCount; ++i) { - emitBuiltinCallArg(e, (*valuesList)[i], i, useVars[i].second); - } - if (Option::WholeProgram) { int my_id; { @@ -3892,14 +3919,15 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) { } } - e.CreateCl(useCount, className); + ce->setClosureClassName(StringData::GetStaticString(clsName)); + e.CreateCl(useCount, ce->getClosureClassName()); // From here on out, we're creating a new class to hold the closure. const static StringData* parentName = StringData::GetStaticString("Closure"); const Location* sLoc = ce->getLocation().get(); PreClassEmitter* pce = m_ue.newPreClassEmitter( - className, PreClass::AlwaysHoistable); + ce->getClosureClassName(), PreClass::AlwaysHoistable); pce->init(sLoc->line0, sLoc->line1, m_ue.bcPos(), AttrUnique | AttrPersistent, parentName, nullptr); @@ -3983,13 +4011,11 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) { visit(expr); emitConvertToCell(e); - int64_t normalLabel = 2 * await->getLabel(); - int64_t exceptLabel = normalLabel - 1; - // if expr is null, just continue + Label awaitNull; e.Dup(); e.IsNullC(); - e.JmpNZ(m_yieldLabels[normalLabel]); + e.JmpNZ(awaitNull); // if the type of expr is not WaitHandle (can be just Awaitable), // call getWaitHandle() method. @@ -4001,22 +4027,42 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) { } assert(m_evalStack.size() == 1); - e.ContSuspend(normalLabel); + // suspend if it is not finished + Label finished; + e.Dup(); // keep wait handle on the stack + emitConstMethodCallNoParams(e, "isFinished"); + e.JmpNZ(finished); - // emit return label for raise() - assert(m_evalStack.size() == 0); - e.Null(); - m_yieldLabels[exceptLabel].set(e); - - // throw received exception on the stack - e.Throw(); - - // emit return label for next()/send() - e.Null(); - m_yieldLabels[normalLabel].set(e); - - // continue with the received result on the stack + // the work is not yet finished, we had to suspend + int64_t normalLabel = 2 * await->getLabel(); + int64_t exceptLabel = normalLabel - 1; + if (m_curFunc->isGenerator()) { + // suspend continuation + e.ContSuspend(normalLabel); + e.Null(); + m_yieldLabels[exceptLabel].set(e); + e.Throw(); + e.Null(); + } else { + // create new continuation and return it's wait handle + auto meth = static_pointer_cast( + node->getFunctionScope()->getStmt()); + const StringData* nameStr = + StringData::GetStaticString(meth->getGeneratorName()); + e.CreateAsync(nameStr, normalLabel, m_pendingIters.size()); + emitConstMethodCallNoParams(e, "getWaitHandle"); + e.RetC(); + } + // emit code to continue without suspend + finished.set(e); + emitConstMethodCallNoParams(e, "join"); assert(m_evalStack.size() == 1); + + // resume here next time + if (m_curFunc->isGenerator()) { + m_yieldLabels[normalLabel].set(e); + } + awaitNull.set(e); return true; } } @@ -4372,6 +4418,10 @@ Id EmitterVisitor::emitVisitAndSetUnnamedL(Emitter& e, ExpressionPtr exp) { visit(exp); emitConvertToCell(e); + return emitSetUnnamedL(e); +} + +Id EmitterVisitor::emitSetUnnamedL(Emitter& e) { // HACK: emitVirtualLocal would pollute m_evalStack before visiting exp, // YieldExpression won't be happy Id tempLocal = m_curFunc->allocUnnamedLocal(); @@ -5365,8 +5415,9 @@ static Attr buildMethodAttrs(MethodStatementPtr meth, FuncEmitter* fe, attrs = attrs | AttrAllowOverride; } - if (meth->hasCallToGetArgs() || - (!fe->hasGeneratorAsBody() && funcScope->mayUseVV())) { + // if hasCallToGetArgs() or if mayUseVV and is not 'create generator' function + if (meth->hasCallToGetArgs() || (funcScope->mayUseVV() && + (!funcScope->isGenerator() || fe->isGenerator()))) { attrs = attrs | AttrMayUseVV; } @@ -5479,14 +5530,27 @@ void EmitterVisitor::emitPostponedMeths() { top_fes.push_back(fe); } - FunctionScopePtr funcScope = meth->getFunctionScope(); - if (funcScope->isGenerator() || funcScope->isAsync()) { + auto funcScope = meth->getFunctionScope(); + if (funcScope->isGenerator()) { // emit the outer 'create generator' function m_curFunc = fe; fe->setHasGeneratorAsBody(true); emitMethodMetadata(meth, p.m_closureUseVars, p.m_top); emitGeneratorCreate(meth); + // emit the generator body + m_curFunc = createFuncEmitterForGeneratorBody(meth, fe, top_fes); + if (m_curFunc) { + emitMethodMetadata(meth, p.m_closureUseVars, m_curFunc->top()); + emitGeneratorBody(meth); + } + } else if (funcScope->isAsync()) { + // emit the outer function (which creates continuation if blocked) + m_curFunc = fe; + fe->setHasGeneratorAsBody(true); + emitMethodMetadata(meth, p.m_closureUseVars, p.m_top); + emitAsyncMethod(meth); + // emit the generator body m_curFunc = createFuncEmitterForGeneratorBody(meth, fe, top_fes); if (m_curFunc) { @@ -5638,8 +5702,9 @@ void EmitterVisitor::emitMethodMetadata(MethodStatementPtr meth, fe->allocVarId(s_continuationVarArgsLocal); } - // assign ids to local variables (not in generator create) - if (!fe->hasGeneratorAsBody()) { + // assign ids to local variables (not in 'create generator' method) + if (!meth->getFunctionScope()->isGenerator() || + fe->isGenerator()) { assignLocalVariableIds(meth->getFunctionScope()); } @@ -5748,8 +5813,7 @@ void EmitterVisitor::emitMethodPrologue(Emitter& e, MethodStatementPtr meth) { if (funcScope->needsLocalThis() && !funcScope->isStatic() && - !funcScope->isGenerator() && - !funcScope->isAsync()) { + !funcScope->isGenerator()) { assert(!m_curFunc->top()); static const StringData* thisStr = StringData::GetStaticString("this"); Id thisId = m_curFunc->lookupVarId(thisStr); @@ -5793,6 +5857,58 @@ void EmitterVisitor::emitMethod(MethodStatementPtr meth) { emitMethodDVInitializers(e, meth, topOfBody); } +void EmitterVisitor::emitAsyncMethod(MethodStatementPtr meth) { + Emitter e(meth, m_ue, *this); + Label topOfBody(e); + emitMethodPrologue(e, meth); + emitSetFuncGetArgs(e); + + // emit method body + Offset start = m_ue.bcPos(); + visit(meth->getStmts()); + assert(m_evalStack.size() == 0); + + // if the current position is reachable, emit code to return null + if (currentPositionIsReachable()) { + emitCreateStaticWaitHandle(e, "StaticResultWaitHandle", + [&](){ e.Null(); }); + e.RetC(); + } + + // wrap the whole body into a try-catch block + Offset end = m_ue.bcPos(); + ExnHandlerRegion* r = new ExnHandlerRegion(start, end); + m_exnHandlers.push_back(r); + + Label* label = new Label(e); + StringData* excLit = StringData::GetStaticString("Exception"); + r->m_names.insert(excLit); + r->m_catchLabels.push_back(std::pair(excLit, label)); + + // catch block + emitCreateStaticWaitHandle(e, "StaticExceptionWaitHandle", + [&](){ e.Catch(); }); + e.RetC(); + + FuncFinisher ff(this, e, m_curFunc); + + emitMethodDVInitializers(e, meth, topOfBody); +} + +void EmitterVisitor::emitCreateStaticWaitHandle(Emitter& e, std::string cls, + std::function emitParam) { + StringData* createLit = StringData::GetStaticString("create"); + StringData* clsLit = StringData::GetStaticString(cls); + { + FPIRegionRecorder fpi(this, m_ue, m_evalStack, m_ue.bcPos()); + e.FPushClsMethodD(1, createLit, clsLit); + emitParam(); + e.FPassC(0); + } + e.FCall(1); + emitConvertToCell(e); +} + FuncEmitter* EmitterVisitor::createFuncEmitterForGeneratorBody( MethodStatementPtr meth, FuncEmitter* fe, diff --git a/hphp/compiler/analysis/emitter.h b/hphp/compiler/analysis/emitter.h index 00c9cc93f..e0303079f 100644 --- a/hphp/compiler/analysis/emitter.h +++ b/hphp/compiler/analysis/emitter.h @@ -599,6 +599,7 @@ public: void emitResolveClsBase(Emitter& e, int pos); void emitClsIfSPropBase(Emitter& e); Id emitVisitAndSetUnnamedL(Emitter& e, ExpressionPtr exp); + Id emitSetUnnamedL(Emitter& e); void emitPushAndFreeUnnamedL(Emitter& e, Id tempLocal, Offset start); void emitContinuationSwitch(Emitter& e, int ncase); DataType analyzeSwitch(SwitchStatementPtr s, SwitchState& state); @@ -644,9 +645,12 @@ public: MethodStatementPtr meth, FuncEmitter* fe, vector& top_fes); + void emitAsyncMethod(MethodStatementPtr meth); void emitGeneratorCreate(MethodStatementPtr meth); void emitGeneratorBody(MethodStatementPtr meth); void emitConstMethodCallNoParams(Emitter& e, string name); + void emitCreateStaticWaitHandle(Emitter& e, std::string cls, + std::function emitParam); void emitSetFuncGetArgs(Emitter& e); void emitMethodDVInitializers(Emitter& e, MethodStatementPtr& meth, diff --git a/hphp/compiler/expression/closure_expression.h b/hphp/compiler/expression/closure_expression.h index 617440600..214ee9330 100644 --- a/hphp/compiler/expression/closure_expression.h +++ b/hphp/compiler/expression/closure_expression.h @@ -40,12 +40,16 @@ public: FunctionStatementPtr getClosureFunction() { return m_func; } ExpressionListPtr getClosureVariables() { return m_vars; } ExpressionListPtr getClosureValues() { return m_values; } + StringData* getClosureClassName() { return m_closureClassName; } + void setClosureClassName(StringData* value) { m_closureClassName = value; } bool hasStaticLocals(); + private: FunctionStatementPtr m_func; ExpressionListPtr m_vars; ExpressionListPtr m_values; + StringData* m_closureClassName; static TypePtr s_ClosureType; diff --git a/hphp/doc/bytecode.specification b/hphp/doc/bytecode.specification index a54d7dee3..aebde9419 100644 --- a/hphp/doc/bytecode.specification +++ b/hphp/doc/bytecode.specification @@ -3741,6 +3741,13 @@ CreateCont [] -> [C] Continuation will store a reference to the function named by the string immediate to be used as its body. +CreateAsync