Home | History | Annotate | Download | only in builtins
      1 // Copyright 2017 the V8 project authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "src/builtins/builtins-async.h"
      6 #include "src/builtins/builtins-utils.h"
      7 #include "src/builtins/builtins.h"
      8 #include "src/code-stub-assembler.h"
      9 #include "src/objects-inl.h"
     10 
     11 namespace v8 {
     12 namespace internal {
     13 
     14 typedef compiler::Node Node;
     15 typedef CodeStubAssembler::ParameterMode ParameterMode;
     16 typedef compiler::CodeAssemblerState CodeAssemblerState;
     17 
     18 class AsyncFunctionBuiltinsAssembler : public AsyncBuiltinsAssembler {
     19  public:
     20   explicit AsyncFunctionBuiltinsAssembler(CodeAssemblerState* state)
     21       : AsyncBuiltinsAssembler(state) {}
     22 
     23  protected:
     24   void AsyncFunctionAwait(Node* const context, Node* const generator,
     25                           Node* const awaited, Node* const outer_promise,
     26                           const bool is_predicted_as_caught);
     27 
     28   void AsyncFunctionAwaitResumeClosure(
     29       Node* const context, Node* const sent_value,
     30       JSGeneratorObject::ResumeMode resume_mode);
     31 };
     32 
     33 namespace {
     34 
     35 // Describe fields of Context associated with AsyncFunctionAwait resume
     36 // closures.
     37 // TODO(jgruber): Refactor to reuse code for upcoming async-generators.
     38 class AwaitContext {
     39  public:
     40   enum Fields { kGeneratorSlot = Context::MIN_CONTEXT_SLOTS, kLength };
     41 };
     42 
     43 }  // anonymous namespace
     44 
     45 void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResumeClosure(
     46     Node* context, Node* sent_value,
     47     JSGeneratorObject::ResumeMode resume_mode) {
     48   DCHECK(resume_mode == JSGeneratorObject::kNext ||
     49          resume_mode == JSGeneratorObject::kThrow);
     50 
     51   Node* const generator =
     52       LoadContextElement(context, AwaitContext::kGeneratorSlot);
     53   CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE));
     54 
     55   // Inline version of GeneratorPrototypeNext / GeneratorPrototypeReturn with
     56   // unnecessary runtime checks removed.
     57   // TODO(jgruber): Refactor to reuse code from builtins-generator.cc.
     58 
     59   // Ensure that the generator is neither closed nor running.
     60   CSA_SLOW_ASSERT(
     61       this,
     62       SmiGreaterThan(
     63           LoadObjectField(generator, JSGeneratorObject::kContinuationOffset),
     64           SmiConstant(JSGeneratorObject::kGeneratorClosed)));
     65 
     66   // Resume the {receiver} using our trampoline.
     67   Callable callable = CodeFactory::ResumeGenerator(isolate());
     68   CallStub(callable, context, sent_value, generator, SmiConstant(resume_mode));
     69 
     70   // The resulting Promise is a throwaway, so it doesn't matter what it
     71   // resolves to. What is important is that we don't end up keeping the
     72   // whole chain of intermediate Promises alive by returning the return value
     73   // of ResumeGenerator, as that would create a memory leak.
     74 }
     75 
     76 TF_BUILTIN(AsyncFunctionAwaitRejectClosure, AsyncFunctionBuiltinsAssembler) {
     77   CSA_ASSERT_JS_ARGC_EQ(this, 1);
     78   Node* const sentError = Parameter(1);
     79   Node* const context = Parameter(4);
     80 
     81   AsyncFunctionAwaitResumeClosure(context, sentError,
     82                                   JSGeneratorObject::kThrow);
     83   Return(UndefinedConstant());
     84 }
     85 
     86 TF_BUILTIN(AsyncFunctionAwaitResolveClosure, AsyncFunctionBuiltinsAssembler) {
     87   CSA_ASSERT_JS_ARGC_EQ(this, 1);
     88   Node* const sentValue = Parameter(1);
     89   Node* const context = Parameter(4);
     90 
     91   AsyncFunctionAwaitResumeClosure(context, sentValue, JSGeneratorObject::kNext);
     92   Return(UndefinedConstant());
     93 }
     94 
     95 // ES#abstract-ops-async-function-await
     96 // AsyncFunctionAwait ( value )
     97 // Shared logic for the core of await. The parser desugars
     98 //   await awaited
     99 // into
    100 //   yield AsyncFunctionAwait{Caught,Uncaught}(.generator, awaited, .promise)
    101 // The 'awaited' parameter is the value; the generator stands in
    102 // for the asyncContext, and .promise is the larger promise under
    103 // construction by the enclosing async function.
    104 void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait(
    105     Node* const context, Node* const generator, Node* const awaited,
    106     Node* const outer_promise, const bool is_predicted_as_caught) {
    107   CSA_SLOW_ASSERT(this, HasInstanceType(generator, JS_GENERATOR_OBJECT_TYPE));
    108   CSA_SLOW_ASSERT(this, HasInstanceType(outer_promise, JS_PROMISE_TYPE));
    109 
    110   NodeGenerator1 create_closure_context = [&](Node* native_context) -> Node* {
    111     Node* const context =
    112         CreatePromiseContext(native_context, AwaitContext::kLength);
    113     StoreContextElementNoWriteBarrier(context, AwaitContext::kGeneratorSlot,
    114                                       generator);
    115     return context;
    116   };
    117 
    118   // TODO(jgruber): AsyncBuiltinsAssembler::Await currently does not reuse
    119   // the awaited promise if it is already a promise. Reuse is non-spec compliant
    120   // but part of our old behavior gives us a couple of percent
    121   // performance boost.
    122   // TODO(jgruber): Use a faster specialized version of
    123   // InternalPerformPromiseThen.
    124 
    125   Node* const result = Await(
    126       context, generator, awaited, outer_promise, create_closure_context,
    127       Context::ASYNC_FUNCTION_AWAIT_RESOLVE_SHARED_FUN,
    128       Context::ASYNC_FUNCTION_AWAIT_REJECT_SHARED_FUN, is_predicted_as_caught);
    129 
    130   Return(result);
    131 }
    132 
    133 // Called by the parser from the desugaring of 'await' when catch
    134 // prediction indicates that there is a locally surrounding catch block.
    135 TF_BUILTIN(AsyncFunctionAwaitCaught, AsyncFunctionBuiltinsAssembler) {
    136   CSA_ASSERT_JS_ARGC_EQ(this, 3);
    137   Node* const generator = Parameter(1);
    138   Node* const awaited = Parameter(2);
    139   Node* const outer_promise = Parameter(3);
    140   Node* const context = Parameter(6);
    141 
    142   static const bool kIsPredictedAsCaught = true;
    143 
    144   AsyncFunctionAwait(context, generator, awaited, outer_promise,
    145                      kIsPredictedAsCaught);
    146 }
    147 
    148 // Called by the parser from the desugaring of 'await' when catch
    149 // prediction indicates no locally surrounding catch block.
    150 TF_BUILTIN(AsyncFunctionAwaitUncaught, AsyncFunctionBuiltinsAssembler) {
    151   CSA_ASSERT_JS_ARGC_EQ(this, 3);
    152   Node* const generator = Parameter(1);
    153   Node* const awaited = Parameter(2);
    154   Node* const outer_promise = Parameter(3);
    155   Node* const context = Parameter(6);
    156 
    157   static const bool kIsPredictedAsCaught = false;
    158 
    159   AsyncFunctionAwait(context, generator, awaited, outer_promise,
    160                      kIsPredictedAsCaught);
    161 }
    162 
    163 TF_BUILTIN(AsyncFunctionPromiseCreate, AsyncFunctionBuiltinsAssembler) {
    164   CSA_ASSERT_JS_ARGC_EQ(this, 0);
    165   Node* const context = Parameter(3);
    166 
    167   Node* const promise = AllocateAndInitJSPromise(context);
    168 
    169   Label if_is_debug_active(this, Label::kDeferred);
    170   GotoIf(IsDebugActive(), &if_is_debug_active);
    171 
    172   // Early exit if debug is not active.
    173   Return(promise);
    174 
    175   Bind(&if_is_debug_active);
    176   {
    177     // Push the Promise under construction in an async function on
    178     // the catch prediction stack to handle exceptions thrown before
    179     // the first await.
    180     // Assign ID and create a recurring task to save stack for future
    181     // resumptions from await.
    182     CallRuntime(Runtime::kDebugAsyncFunctionPromiseCreated, context, promise);
    183     Return(promise);
    184   }
    185 }
    186 
    187 TF_BUILTIN(AsyncFunctionPromiseRelease, AsyncFunctionBuiltinsAssembler) {
    188   CSA_ASSERT_JS_ARGC_EQ(this, 1);
    189   Node* const promise = Parameter(1);
    190   Node* const context = Parameter(4);
    191 
    192   Label if_is_debug_active(this, Label::kDeferred);
    193   GotoIf(IsDebugActive(), &if_is_debug_active);
    194 
    195   // Early exit if debug is not active.
    196   Return(UndefinedConstant());
    197 
    198   Bind(&if_is_debug_active);
    199   {
    200     // Pop the Promise under construction in an async function on
    201     // from catch prediction stack.
    202     CallRuntime(Runtime::kDebugPopPromise, context);
    203     Return(promise);
    204   }
    205 }
    206 
    207 }  // namespace internal
    208 }  // namespace v8
    209