你尚未登录,仅允许查看本站部分内容。请登录使用邀请码注册
xtx1130

阅读源码之v8 async/await 0个回复 专栏 @ 框架与库

xtx1130 发布于 6 月前

v8 小菜鸟,对async/await做一个简单的流程上的分析,如有问题还请大神们指正

声明阶段

pic
首先来看这一部分,意义很明了,也就是箭头函数支持对AsyncFunction的定义。

let test = async ()=>{};
test.constructor.name === 'AsyncFunction'//true

接下来,判断完AsyncFunction后返回空指针nullptr,打断箭头函数的逻辑进入下一层逻辑。

解析阶段(词法+语法分析)

由于解析阶段的代码太多,我挑出来一些比较典型的做一下备注,就不进行大段代码截图了,相关源码地址会提供链接供大家查看。
首先async函数返回的是个promise,所以解析的时候是从promise入手的,下面来看一段代码:

Statement* set_promise;
  {
    Expression* create_promise = factory()->NewCallRuntime(
        Context::ASYNC_FUNCTION_PROMISE_CREATE_INDEX,
        new (zone()) ZoneList<Expression*>(0, zone()), kNoSourcePosition);
    Assignment* assign_promise = factory()->NewAssignment(
        Token::ASSIGN, factory()->NewVariableProxy(PromiseVariable()),
        create_promise, kNoSourcePosition);
    set_promise =
        factory()->NewExpressionStatement(assign_promise, kNoSourcePosition);
  }
  result->statements()->Add(set_promise, zone());

这段代码对async函数的promise进行了声明(相关源码点击这里),其中指定了上下文Context为ASYNC_FUNCTION_PROMISE_CREATE_INDEX,下面的Assignment中主要调用了PromiseVariable这个方法,通过查看发现assign_promise主要是对promise中变量的编译路径进行绑定。

Scope* catch_scope = NewHiddenCatchScopeWithParent(scope());

  Expression* promise_reject = BuildRejectPromise(
      factory()->NewVariableProxy(catch_scope->catch_variable()),
      kNoSourcePosition);
  Block* catch_block = IgnoreCompletion(
      factory()->NewReturnStatement(promise_reject, kNoSourcePosition));

  TryStatement* try_catch_statement =
      factory()->NewTryCatchStatementForAsyncAwait(
          inner_block, catch_scope, catch_block, kNoSourcePosition);

这段代码声明了promise的错误处理(相关源码点击这里),通过源码能看出来,promise_reject的表达式放入到了catch_block块中,形成 try{}catch(e){promise_reject(e)}此类性质的错误处理。其实之后还有finally模块,在这里就不做详细介绍了。
下面就是Parser::RewriteAwaitExpression,这里是的对await做parse的地方。首先来关注一下这段代码:

    Expression* await = factory()->NewCallRuntime(
        Context::ASYNC_GENERATOR_AWAIT_CAUGHT, args, nopos);
    do_block->statements()->Add(
        factory()->NewExpressionStatement(await, await_pos), zone());
   // Wrap await to provide a break location between value evaluation and
    // yield.
    Expression* do_expr = factory()->NewDoExpression(
        do_block, AsyncGeneratorAwaitVariable(), nopos);
    return BuildSuspend(generator_object, do_expr, nopos,
                        Suspend::kOnExceptionRethrow, SuspendFlags::kAwait);
  }

第一段代码对await进行了声明,并且赋予了他async的上下文,再看最后它的返回为BuildSuspend,接下来深扒一下BuildSuspend这个函数。通过层层查找,可以从ast.h中发现一点端倪:

public:
    bool is_yield() const { return suspend_type() == SuspendFlags::kYield; }
  bool is_yield_star() const {
    return suspend_type() == SuspendFlags::kYieldStar;
  }
  bool is_await() const { return suspend_type() == SuspendFlags::kAwait; }
  bool is_async_generator() const {
    return generator_type() == SuspendFlags::kAsyncGenerator;
  }
private:
    class OnExceptionField
      : public BitField<OnException, Expression::kNextBitFieldIndex, 1> {};
    class FlagsField
      : public BitField<SuspendFlags, OnExceptionField::kNext,
                        static_cast<int>(SuspendFlags::kBitWidth)> {};

这里就是暂停函数的class声明了,从上面的几个判断中可以看出v8对yield和await分出了四种不同的flag,并且在其私有声明中包含了ast,而BuildSuspend返回的便是这个class的实例。
然后接下来的重点就在于,对iterator的解析中都做了一个判断,请看

if (type == IteratorType::kAsync) {
      call = RewriteAwaitExpression(call, nopos);
    }

在这里解释一下,如果是asyncGenerator形式的,IteratorType为kAsync,如果是yieldGenerator形式的,IteratorType为kNormal,通过这个判断,重写Iterator的 return、next Block块。

中间代码生成

中间代码生成主要在文件runtime-generator.cc以及runtime-promise.cc中。
首先我们来看runtime-generator.cc。

RUNTIME_FUNCTION(Runtime_CreateJSGeneratorObject) {
  HandleScope scope(isolate);
  //此处省略。。。

  Handle<JSGeneratorObject> generator =
      isolate->factory()->NewJSGeneratorObject(function);
  generator->set_function(*function);
  generator->set_context(isolate->context());
  generator->set_receiver(*receiver);
  generator->set_register_file(*register_file);
  generator->set_continuation(JSGeneratorObject::kGeneratorExecuting);
  return *generator;
}

这里创建出来的JSGeneratorObject存储了相关的函数、上下文等信息,为什么用generator来表示,是因为async内部的运行原理类似generator,只不过吧Iterator.next()这个过程写到了内部而外部又包装了一层promise,通过翻注释也可以发现,字节码生成阶段,这两块暂时还是分开的,分别调用的builtims-async-function-gen.ccbuiltins-generator-gen.cc。继续往下面翻,发现下面有几个有意思的返回:

return generator->await_input_or_debug_pos();
return isolate->heap()->undefined_value();
return Smi::FromInt(generator->resume_mode());

返回的大致类型有三种,generator表示的意思是还在async函数内部,并没有跳出函数,isolate大家应该都很熟悉了,这是一个v8的VM,也就是已经跳出了async函数,用于外部promise的resolve和reject,第三种返回则是返回值经过small integer(smi)的转换再进行的返回,主要用于builtims-async-function-gen.cc以及builtims-async-gen.cc。下面我们来到非常关键的builtims-async-function-gen.cc。

void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResumeClosure(
    Node* context, Node* sent_value,
    JSGeneratorObject::ResumeMode resume_mode) {
  DCHECK(resume_mode == JSGeneratorObject::kNext ||
         resume_mode == JSGeneratorObject::kThrow);
  Node* const generator =
      LoadContextElement(context, AwaitContext::kGeneratorSlot);
  CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE));
  CSA_SLOW_ASSERT(
      this,
      SmiGreaterThan(
          LoadObjectField(generator, JSGeneratorObject::kContinuationOffset),
          SmiConstant(JSGeneratorObject::kGeneratorClosed)));

  Callable callable = CodeFactory::ResumeGenerator(isolate());
  CallStub(callable, context, sent_value, generator, SmiConstant(resume_mode),
           SmiConstant(static_cast<int>(SuspendFlags::kGeneratorAwait)));
}

这个函数主要作用就是对async进行汇编(通过class名也能看出来),通过这个函数,我们可以看出来async的stub汇编代码段决定因素有:resume_mode以及SuspendFlags。resume_mode在async Generator中分为两种情况kNext和kThrow而SuspendFlags个人感觉主要是为了区分开await(SuspendFlags::kGeneratorAwait)和yield(SuspendFlags::kGeneratorYield)。之后在下面进行built in 的时候,可以发现:

Node* const sentError = Parameter(Descriptor::kSentError);
AsyncFunctionAwaitResumeClosure(context, sentError, JSGeneratorObject::kThrow);
//此处省略。。。。
Node* const sentValue = Parameter(Descriptor::kSentValue);
AsyncFunctionAwaitResumeClosure(context, sentValue, JSGeneratorObject::kNext);

sent_value的可能性只有两种,去Turbofan中溯源,最终溯源的结果为这两类分别为await的reject和reslove返回的值,然后继续传递到AsyncFunctionAwaitResumeClosure中,这样,整个async的流程基本就清晰了。

by:小菜

等待第一条回复
登录后回复,如无账号,请使用邀请码注册