// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/builtins/builtins-async-gen.h"
#include "src/builtins/builtins-utils-gen.h"
#include "src/builtins/builtins.h"
#include "src/codegen/code-stub-assembler.h"
#include "src/objects/js-generator.h"
#include "src/objects/js-promise.h"
#include "src/objects/objects-inl.h"

namespace v8 {
namespace internal {

class AsyncFunctionBuiltinsAssembler : public AsyncBuiltinsAssembler {
 public:
  explicit AsyncFunctionBuiltinsAssembler(compiler::CodeAssemblerState* state)
      : AsyncBuiltinsAssembler(state) {}

 protected:
  template <typename Descriptor>
  void AsyncFunctionAwait(const bool is_predicted_as_caught);

  void AsyncFunctionAwaitResumeClosure(
      const TNode<Context> context, const TNode<Object> sent_value,
      JSGeneratorObject::ResumeMode resume_mode);
};

void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwaitResumeClosure(
    TNode<Context> context, TNode<Object> sent_value,
    JSGeneratorObject::ResumeMode resume_mode) {
  DCHECK(resume_mode == JSGeneratorObject::kNext ||
         resume_mode == JSGeneratorObject::kThrow);

  TNode<JSAsyncFunctionObject> async_function_object =
      CAST(LoadContextElement(context, Context::EXTENSION_INDEX));

  // Push the promise for the {async_function_object} back onto the catch
  // prediction stack to handle exceptions thrown after resuming from the
  // await properly.
  Label if_instrumentation(this, Label::kDeferred),
      if_instrumentation_done(this);
  Branch(IsDebugActive(), &if_instrumentation, &if_instrumentation_done);
  BIND(&if_instrumentation);
  {
    TNode<JSPromise> promise = LoadObjectField<JSPromise>(
        async_function_object, JSAsyncFunctionObject::kPromiseOffset);
    CallRuntime(Runtime::kDebugAsyncFunctionResumed, context, promise);
    Goto(&if_instrumentation_done);
  }
  BIND(&if_instrumentation_done);

  // Inline version of GeneratorPrototypeNext / GeneratorPrototypeReturn with
  // unnecessary runtime checks removed.

  // Ensure that the {async_function_object} is neither closed nor running.
  CSA_SLOW_ASSERT(
      this, SmiGreaterThan(
                LoadObjectField<Smi>(async_function_object,
                                     JSGeneratorObject::kContinuationOffset),
                SmiConstant(JSGeneratorObject::kGeneratorClosed)));

  // Remember the {resume_mode} for the {async_function_object}.
  StoreObjectFieldNoWriteBarrier(async_function_object,
                                 JSGeneratorObject::kResumeModeOffset,
                                 SmiConstant(resume_mode));

  // Resume the {receiver} using our trampoline.
  Callable callable = CodeFactory::ResumeGenerator(isolate());
  CallStub(callable, context, sent_value, async_function_object);

  // The resulting Promise is a throwaway, so it doesn't matter what it
  // resolves to. What is important is that we don't end up keeping the
  // whole chain of intermediate Promises alive by returning the return value
  // of ResumeGenerator, as that would create a memory leak.
}

TF_BUILTIN(AsyncFunctionEnter, AsyncFunctionBuiltinsAssembler) {
  auto closure = Parameter<JSFunction>(Descriptor::kClosure);
  auto receiver = Parameter<Object>(Descriptor::kReceiver);
  auto context = Parameter<Context>(Descriptor::kContext);

  // Compute the number of registers and parameters.
  TNode<SharedFunctionInfo> shared = LoadObjectField<SharedFunctionInfo>(
      closure, JSFunction::kSharedFunctionInfoOffset);
  TNode<IntPtrT> formal_parameter_count =
      ChangeInt32ToIntPtr(LoadObjectField<Uint16T>(
          shared, SharedFunctionInfo::kFormalParameterCountOffset));
  TNode<BytecodeArray> bytecode_array =
      LoadSharedFunctionInfoBytecodeArray(shared);
  TNode<IntPtrT> frame_size = ChangeInt32ToIntPtr(LoadObjectField<Uint32T>(
      bytecode_array, BytecodeArray::kFrameSizeOffset));
  TNode<IntPtrT> parameters_and_register_length =
      Signed(IntPtrAdd(WordSar(frame_size, IntPtrConstant(kTaggedSizeLog2)),
                       formal_parameter_count));

  // Allocate and initialize the register file.
  TNode<FixedArrayBase> parameters_and_registers =
      AllocateFixedArray(HOLEY_ELEMENTS, parameters_and_register_length,
                         kAllowLargeObjectAllocation);
  FillFixedArrayWithValue(HOLEY_ELEMENTS, parameters_and_registers,
                          IntPtrConstant(0), parameters_and_register_length,
                          RootIndex::kUndefinedValue);

  // Allocate and initialize the promise.
  TNode<NativeContext> native_context = LoadNativeContext(context);
  TNode<JSFunction> promise_function =
      CAST(LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX));
  TNode<Map> promise_map = LoadObjectField<Map>(
      promise_function, JSFunction::kPrototypeOrInitialMapOffset);
  TNode<JSPromise> promise = UncheckedCast<JSPromise>(
      AllocateInNewSpace(JSPromise::kSizeWithEmbedderFields));
  StoreMapNoWriteBarrier(promise, promise_map);
  StoreObjectFieldRoot(promise, JSPromise::kPropertiesOrHashOffset,
                       RootIndex::kEmptyFixedArray);
  StoreObjectFieldRoot(promise, JSPromise::kElementsOffset,
                       RootIndex::kEmptyFixedArray);
  PromiseInit(promise);

  // Allocate and initialize the async function object.
  TNode<Map> async_function_object_map = CAST(LoadContextElement(
      native_context, Context::ASYNC_FUNCTION_OBJECT_MAP_INDEX));
  TNode<JSAsyncFunctionObject> async_function_object =
      UncheckedCast<JSAsyncFunctionObject>(
          AllocateInNewSpace(JSAsyncFunctionObject::kHeaderSize));
  StoreMapNoWriteBarrier(async_function_object, async_function_object_map);
  StoreObjectFieldRoot(async_function_object,
                       JSAsyncFunctionObject::kPropertiesOrHashOffset,
                       RootIndex::kEmptyFixedArray);
  StoreObjectFieldRoot(async_function_object,
                       JSAsyncFunctionObject::kElementsOffset,
                       RootIndex::kEmptyFixedArray);
  StoreObjectFieldNoWriteBarrier(
      async_function_object, JSAsyncFunctionObject::kFunctionOffset, closure);
  StoreObjectFieldNoWriteBarrier(
      async_function_object, JSAsyncFunctionObject::kContextOffset, context);
  StoreObjectFieldNoWriteBarrier(
      async_function_object, JSAsyncFunctionObject::kReceiverOffset, receiver);
  StoreObjectFieldNoWriteBarrier(async_function_object,
                                 JSAsyncFunctionObject::kInputOrDebugPosOffset,
                                 SmiConstant(0));
  StoreObjectFieldNoWriteBarrier(async_function_object,
                                 JSAsyncFunctionObject::kResumeModeOffset,
                                 SmiConstant(JSAsyncFunctionObject::kNext));
  StoreObjectFieldNoWriteBarrier(
      async_function_object, JSAsyncFunctionObject::kContinuationOffset,
      SmiConstant(JSAsyncFunctionObject::kGeneratorExecuting));
  StoreObjectFieldNoWriteBarrier(
      async_function_object,
      JSAsyncFunctionObject::kParametersAndRegistersOffset,
      parameters_and_registers);
  StoreObjectFieldNoWriteBarrier(
      async_function_object, JSAsyncFunctionObject::kPromiseOffset, promise);

  RunContextPromiseHookInit(context, promise, UndefinedConstant());

  // Fire promise hooks if enabled and push the Promise under construction
  // in an async function on the catch prediction stack to handle exceptions
  // thrown before the first await.
  Label if_instrumentation(this, Label::kDeferred),
      if_instrumentation_done(this);
  Branch(IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
         &if_instrumentation, &if_instrumentation_done);
  BIND(&if_instrumentation);
  {
    CallRuntime(Runtime::kDebugAsyncFunctionEntered, context, promise);
    Goto(&if_instrumentation_done);
  }
  BIND(&if_instrumentation_done);

  Return(async_function_object);
}

TF_BUILTIN(AsyncFunctionReject, AsyncFunctionBuiltinsAssembler) {
  auto async_function_object =
      Parameter<JSAsyncFunctionObject>(Descriptor::kAsyncFunctionObject);
  auto reason = Parameter<Object>(Descriptor::kReason);
  auto can_suspend = Parameter<Oddball>(Descriptor::kCanSuspend);
  auto context = Parameter<Context>(Descriptor::kContext);
  TNode<JSPromise> promise = LoadObjectField<JSPromise>(
      async_function_object, JSAsyncFunctionObject::kPromiseOffset);

  // Reject the {promise} for the given {reason}, disabling the
  // additional debug event for the rejection since a debug event
  // already happend for the exception that got us here.
  CallBuiltin(Builtin::kRejectPromise, context, promise, reason,
              FalseConstant());

  Label if_debugging(this, Label::kDeferred);
  GotoIf(HasAsyncEventDelegate(), &if_debugging);
  GotoIf(IsDebugActive(), &if_debugging);
  Return(promise);

  BIND(&if_debugging);
  TailCallRuntime(Runtime::kDebugAsyncFunctionFinished, context, can_suspend,
                  promise);
}

TF_BUILTIN(AsyncFunctionResolve, AsyncFunctionBuiltinsAssembler) {
  auto async_function_object =
      Parameter<JSAsyncFunctionObject>(Descriptor::kAsyncFunctionObject);
  auto value = Parameter<Object>(Descriptor::kValue);
  auto can_suspend = Parameter<Oddball>(Descriptor::kCanSuspend);
  auto context = Parameter<Context>(Descriptor::kContext);
  TNode<JSPromise> promise = LoadObjectField<JSPromise>(
      async_function_object, JSAsyncFunctionObject::kPromiseOffset);

  CallBuiltin(Builtin::kResolvePromise, context, promise, value);

  Label if_debugging(this, Label::kDeferred);
  GotoIf(HasAsyncEventDelegate(), &if_debugging);
  GotoIf(IsDebugActive(), &if_debugging);
  Return(promise);

  BIND(&if_debugging);
  TailCallRuntime(Runtime::kDebugAsyncFunctionFinished, context, can_suspend,
                  promise);
}

// AsyncFunctionReject and AsyncFunctionResolve are both required to return
// the promise instead of the result of RejectPromise or ResolvePromise
// respectively from a lazy deoptimization.
TF_BUILTIN(AsyncFunctionLazyDeoptContinuation, AsyncFunctionBuiltinsAssembler) {
  auto promise = Parameter<JSPromise>(Descriptor::kPromise);
  Return(promise);
}

TF_BUILTIN(AsyncFunctionAwaitRejectClosure, AsyncFunctionBuiltinsAssembler) {
  CSA_ASSERT_JS_ARGC_EQ(this, 1);
  const auto sentError = Parameter<Object>(Descriptor::kSentError);
  const auto context = Parameter<Context>(Descriptor::kContext);

  AsyncFunctionAwaitResumeClosure(context, sentError,
                                  JSGeneratorObject::kThrow);
  Return(UndefinedConstant());
}

TF_BUILTIN(AsyncFunctionAwaitResolveClosure, AsyncFunctionBuiltinsAssembler) {
  CSA_ASSERT_JS_ARGC_EQ(this, 1);
  const auto sentValue = Parameter<Object>(Descriptor::kSentValue);
  const auto context = Parameter<Context>(Descriptor::kContext);

  AsyncFunctionAwaitResumeClosure(context, sentValue, JSGeneratorObject::kNext);
  Return(UndefinedConstant());
}

// ES#abstract-ops-async-function-await
// AsyncFunctionAwait ( value )
// Shared logic for the core of await. The parser desugars
//   await value
// into
//   yield AsyncFunctionAwait{Caught,Uncaught}(.generator_object, value)
// The 'value' parameter is the value; the .generator_object stands in
// for the asyncContext.
template <typename Descriptor>
void AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait(
    const bool is_predicted_as_caught) {
  auto async_function_object =
      Parameter<JSAsyncFunctionObject>(Descriptor::kAsyncFunctionObject);
  auto value = Parameter<Object>(Descriptor::kValue);
  auto context = Parameter<Context>(Descriptor::kContext);

  TNode<JSPromise> outer_promise = LoadObjectField<JSPromise>(
      async_function_object, JSAsyncFunctionObject::kPromiseOffset);

  Label after_debug_hook(this), call_debug_hook(this, Label::kDeferred);
  GotoIf(HasAsyncEventDelegate(), &call_debug_hook);
  Goto(&after_debug_hook);
  BIND(&after_debug_hook);

  TNode<SharedFunctionInfo> on_resolve_sfi =
      AsyncFunctionAwaitResolveSharedFunConstant();
  TNode<SharedFunctionInfo> on_reject_sfi =
      AsyncFunctionAwaitRejectSharedFunConstant();
  Await(context, async_function_object, value, outer_promise, on_resolve_sfi,
        on_reject_sfi, is_predicted_as_caught);

  // Return outer promise to avoid adding an load of the outer promise before
  // suspending in BytecodeGenerator.
  Return(outer_promise);

  BIND(&call_debug_hook);
  CallRuntime(Runtime::kDebugAsyncFunctionSuspended, context, outer_promise);
  Goto(&after_debug_hook);
}

// Called by the parser from the desugaring of 'await' when catch
// prediction indicates that there is a locally surrounding catch block.
TF_BUILTIN(AsyncFunctionAwaitCaught, AsyncFunctionBuiltinsAssembler) {
  static const bool kIsPredictedAsCaught = true;
  AsyncFunctionAwait<Descriptor>(kIsPredictedAsCaught);
}

// Called by the parser from the desugaring of 'await' when catch
// prediction indicates no locally surrounding catch block.
TF_BUILTIN(AsyncFunctionAwaitUncaught, AsyncFunctionBuiltinsAssembler) {
  static const bool kIsPredictedAsCaught = false;
  AsyncFunctionAwait<Descriptor>(kIsPredictedAsCaught);
}

}  // namespace internal
}  // namespace v8
