// Copyright 2016 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/heap/factory-inl.h"
#include "src/objects/js-promise.h"
#include "src/objects/shared-function-info.h"

namespace v8 {
namespace internal {

using compiler::Node;

namespace {
// Describe fields of Context associated with the AsyncIterator unwrap closure.
class ValueUnwrapContext {
 public:
  enum Fields { kDoneSlot = Context::MIN_CONTEXT_SLOTS, kLength };
};

}  // namespace

Node* AsyncBuiltinsAssembler::AwaitOld(Node* context, Node* generator,
                                       Node* value, Node* outer_promise,
                                       Node* on_resolve_context_index,
                                       Node* on_reject_context_index,
                                       Node* is_predicted_as_caught) {
  Node* const native_context = LoadNativeContext(context);

  static const int kWrappedPromiseOffset =
      FixedArray::SizeFor(Context::MIN_CONTEXT_SLOTS);
  static const int kResolveClosureOffset =
      kWrappedPromiseOffset + JSPromise::kSizeWithEmbedderFields;
  static const int kRejectClosureOffset =
      kResolveClosureOffset + JSFunction::kSizeWithoutPrototype;
  static const int kTotalSize =
      kRejectClosureOffset + JSFunction::kSizeWithoutPrototype;

  TNode<HeapObject> base = AllocateInNewSpace(kTotalSize);
  TNode<Context> closure_context = UncheckedCast<Context>(base);
  {
    // Initialize the await context, storing the {generator} as extension.
    StoreMapNoWriteBarrier(closure_context, RootIndex::kAwaitContextMap);
    StoreObjectFieldNoWriteBarrier(closure_context, Context::kLengthOffset,
                                   SmiConstant(Context::MIN_CONTEXT_SLOTS));
    Node* const empty_scope_info =
        LoadContextElement(native_context, Context::SCOPE_INFO_INDEX);
    StoreContextElementNoWriteBarrier(
        closure_context, Context::SCOPE_INFO_INDEX, empty_scope_info);
    StoreContextElementNoWriteBarrier(closure_context, Context::PREVIOUS_INDEX,
                                      native_context);
    StoreContextElementNoWriteBarrier(closure_context, Context::EXTENSION_INDEX,
                                      generator);
    StoreContextElementNoWriteBarrier(
        closure_context, Context::NATIVE_CONTEXT_INDEX, native_context);
  }

  // Let promiseCapability be ! NewPromiseCapability(%Promise%).
  Node* const promise_fun =
      LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
  CSA_ASSERT(this, IsFunctionWithPrototypeSlotMap(LoadMap(promise_fun)));
  Node* const promise_map =
      LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset);
  // Assert that the JSPromise map has an instance size is
  // JSPromise::kSizeWithEmbedderFields.
  CSA_ASSERT(this, WordEqual(LoadMapInstanceSizeInWords(promise_map),
                             IntPtrConstant(JSPromise::kSizeWithEmbedderFields /
                                            kTaggedSize)));
  TNode<HeapObject> wrapped_value = InnerAllocate(base, kWrappedPromiseOffset);
  {
    // Initialize Promise
    StoreMapNoWriteBarrier(wrapped_value, promise_map);
    StoreObjectFieldRoot(wrapped_value, JSPromise::kPropertiesOrHashOffset,
                         RootIndex::kEmptyFixedArray);
    StoreObjectFieldRoot(wrapped_value, JSPromise::kElementsOffset,
                         RootIndex::kEmptyFixedArray);
    PromiseInit(wrapped_value);
  }

  // Initialize resolve handler
  TNode<HeapObject> on_resolve = InnerAllocate(base, kResolveClosureOffset);
  InitializeNativeClosure(closure_context, native_context, on_resolve,
                          on_resolve_context_index);

  // Initialize reject handler
  TNode<HeapObject> on_reject = InnerAllocate(base, kRejectClosureOffset);
  InitializeNativeClosure(closure_context, native_context, on_reject,
                          on_reject_context_index);

  VARIABLE(var_throwaway, MachineRepresentation::kTaggedPointer,
           UndefinedConstant());

  // Deal with PromiseHooks and debug support in the runtime. This
  // also allocates the throwaway promise, which is only needed in
  // case of PromiseHooks or debugging.
  Label if_debugging(this, Label::kDeferred), do_resolve_promise(this);
  Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
         &if_debugging, &do_resolve_promise);
  BIND(&if_debugging);
  var_throwaway.Bind(CallRuntime(Runtime::kAwaitPromisesInitOld, context, value,
                                 wrapped_value, outer_promise, on_reject,
                                 is_predicted_as_caught));
  Goto(&do_resolve_promise);
  BIND(&do_resolve_promise);

  // Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »).
  CallBuiltin(Builtins::kResolvePromise, context, wrapped_value, value);

  return CallBuiltin(Builtins::kPerformPromiseThen, context, wrapped_value,
                     on_resolve, on_reject, var_throwaway.value());
}

Node* AsyncBuiltinsAssembler::AwaitOptimized(Node* context, Node* generator,
                                             Node* promise, Node* outer_promise,
                                             Node* on_resolve_context_index,
                                             Node* on_reject_context_index,
                                             Node* is_predicted_as_caught) {
  Node* const native_context = LoadNativeContext(context);
  CSA_ASSERT(this, IsJSPromise(promise));

  static const int kResolveClosureOffset =
      FixedArray::SizeFor(Context::MIN_CONTEXT_SLOTS);
  static const int kRejectClosureOffset =
      kResolveClosureOffset + JSFunction::kSizeWithoutPrototype;
  static const int kTotalSize =
      kRejectClosureOffset + JSFunction::kSizeWithoutPrototype;

  // 2. Let promise be ? PromiseResolve(« promise »).
  // Node* const promise =
  // CallBuiltin(Builtins::kPromiseResolve, context, promise_fun, value);

  TNode<HeapObject> base = AllocateInNewSpace(kTotalSize);
  TNode<Context> closure_context = UncheckedCast<Context>(base);
  {
    // Initialize the await context, storing the {generator} as extension.
    StoreMapNoWriteBarrier(closure_context, RootIndex::kAwaitContextMap);
    StoreObjectFieldNoWriteBarrier(closure_context, Context::kLengthOffset,
                                   SmiConstant(Context::MIN_CONTEXT_SLOTS));
    Node* const empty_scope_info =
        LoadContextElement(native_context, Context::SCOPE_INFO_INDEX);
    StoreContextElementNoWriteBarrier(
        closure_context, Context::SCOPE_INFO_INDEX, empty_scope_info);
    StoreContextElementNoWriteBarrier(closure_context, Context::PREVIOUS_INDEX,
                                      native_context);
    StoreContextElementNoWriteBarrier(closure_context, Context::EXTENSION_INDEX,
                                      generator);
    StoreContextElementNoWriteBarrier(
        closure_context, Context::NATIVE_CONTEXT_INDEX, native_context);
  }

  // Initialize resolve handler
  TNode<HeapObject> on_resolve = InnerAllocate(base, kResolveClosureOffset);
  InitializeNativeClosure(closure_context, native_context, on_resolve,
                          on_resolve_context_index);

  // Initialize reject handler
  TNode<HeapObject> on_reject = InnerAllocate(base, kRejectClosureOffset);
  InitializeNativeClosure(closure_context, native_context, on_reject,
                          on_reject_context_index);

  VARIABLE(var_throwaway, MachineRepresentation::kTaggedPointer,
           UndefinedConstant());

  // Deal with PromiseHooks and debug support in the runtime. This
  // also allocates the throwaway promise, which is only needed in
  // case of PromiseHooks or debugging.
  Label if_debugging(this, Label::kDeferred), do_perform_promise_then(this);
  Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
         &if_debugging, &do_perform_promise_then);
  BIND(&if_debugging);
  var_throwaway.Bind(CallRuntime(Runtime::kAwaitPromisesInit, context, promise,
                                 promise, outer_promise, on_reject,
                                 is_predicted_as_caught));
  Goto(&do_perform_promise_then);
  BIND(&do_perform_promise_then);

  return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise,
                     on_resolve, on_reject, var_throwaway.value());
}

Node* AsyncBuiltinsAssembler::Await(Node* context, Node* generator, Node* value,
                                    Node* outer_promise,
                                    Node* on_resolve_context_index,
                                    Node* on_reject_context_index,
                                    Node* is_predicted_as_caught) {
  VARIABLE(result, MachineRepresentation::kTagged);
  Label if_old(this), if_new(this), done(this),
      if_slow_constructor(this, Label::kDeferred);

  STATIC_ASSERT(sizeof(FLAG_harmony_await_optimization) == 1);
  TNode<Word32T> flag_value = UncheckedCast<Word32T>(Load(
      MachineType::Uint8(),
      ExternalConstant(
          ExternalReference::address_of_harmony_await_optimization_flag())));
  GotoIf(Word32Equal(flag_value, Int32Constant(0)), &if_old);

  // We're running with --harmony-await-optimization enabled, which means
  // we do the `PromiseResolve(%Promise%,value)` avoiding to unnecessarily
  // create wrapper promises. Now if {value} is already a promise with the
  // intrinsics %Promise% constructor as its "constructor", we don't need
  // to allocate the wrapper promise and can just use the `AwaitOptimized`
  // logic.
  GotoIf(TaggedIsSmi(value), &if_old);
  Node* const value_map = LoadMap(value);
  GotoIfNot(IsJSPromiseMap(value_map), &if_old);
  // We can skip the "constructor" lookup on {value} if it's [[Prototype]]
  // is the (initial) Promise.prototype and the @@species protector is
  // intact, as that guards the lookup path for "constructor" on
  // JSPromise instances which have the (initial) Promise.prototype.
  Node* const native_context = LoadNativeContext(context);
  Node* const promise_prototype =
      LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
  GotoIfNot(WordEqual(LoadMapPrototype(value_map), promise_prototype),
            &if_slow_constructor);
  Branch(IsPromiseSpeciesProtectorCellInvalid(), &if_slow_constructor, &if_new);

  // At this point, {value} doesn't have the initial promise prototype or
  // the promise @@species protector was invalidated, but {value} could still
  // have the %Promise% as its "constructor", so we need to check that as well.
  BIND(&if_slow_constructor);
  {
    Node* const value_constructor =
        GetProperty(context, value, isolate()->factory()->constructor_string());
    Node* const promise_function =
        LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
    Branch(WordEqual(value_constructor, promise_function), &if_new, &if_old);
  }

  BIND(&if_old);
  result.Bind(AwaitOld(context, generator, value, outer_promise,
                       on_resolve_context_index, on_reject_context_index,
                       is_predicted_as_caught));
  Goto(&done);

  BIND(&if_new);
  result.Bind(AwaitOptimized(context, generator, value, outer_promise,
                             on_resolve_context_index, on_reject_context_index,
                             is_predicted_as_caught));
  Goto(&done);

  BIND(&done);
  return result.value();
}

void AsyncBuiltinsAssembler::InitializeNativeClosure(Node* context,
                                                     Node* native_context,
                                                     Node* function,
                                                     Node* context_index) {
  TNode<Map> function_map = CAST(LoadContextElement(
      native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
  // Ensure that we don't have to initialize prototype_or_initial_map field of
  // JSFunction.
  CSA_ASSERT(this, WordEqual(LoadMapInstanceSizeInWords(function_map),
                             IntPtrConstant(JSFunction::kSizeWithoutPrototype /
                                            kTaggedSize)));
  STATIC_ASSERT(JSFunction::kSizeWithoutPrototype == 7 * kTaggedSize);
  StoreMapNoWriteBarrier(function, function_map);
  StoreObjectFieldRoot(function, JSObject::kPropertiesOrHashOffset,
                       RootIndex::kEmptyFixedArray);
  StoreObjectFieldRoot(function, JSObject::kElementsOffset,
                       RootIndex::kEmptyFixedArray);
  StoreObjectFieldRoot(function, JSFunction::kFeedbackCellOffset,
                       RootIndex::kManyClosuresCell);

  TNode<SharedFunctionInfo> shared_info =
      CAST(LoadContextElement(native_context, context_index));
  StoreObjectFieldNoWriteBarrier(
      function, JSFunction::kSharedFunctionInfoOffset, shared_info);
  StoreObjectFieldNoWriteBarrier(function, JSFunction::kContextOffset, context);

  // For the native closures that are initialized here (for `await`)
  // we know that their SharedFunctionInfo::function_data() slot
  // contains a builtin index (as Smi), so there's no need to use
  // CodeStubAssembler::GetSharedFunctionInfoCode() helper here,
  // which almost doubles the size of `await` builtins (unnecessarily).
  TNode<Smi> builtin_id = LoadObjectField<Smi>(
      shared_info, SharedFunctionInfo::kFunctionDataOffset);
  TNode<Code> code = LoadBuiltin(builtin_id);
  StoreObjectFieldNoWriteBarrier(function, JSFunction::kCodeOffset, code);
}

Node* AsyncBuiltinsAssembler::CreateUnwrapClosure(Node* native_context,
                                                  Node* done) {
  Node* const map = LoadContextElement(
      native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
  Node* const on_fulfilled_shared = LoadContextElement(
      native_context, Context::ASYNC_ITERATOR_VALUE_UNWRAP_SHARED_FUN);
  CSA_ASSERT(this,
             HasInstanceType(on_fulfilled_shared, SHARED_FUNCTION_INFO_TYPE));
  Node* const closure_context =
      AllocateAsyncIteratorValueUnwrapContext(native_context, done);
  return AllocateFunctionWithMapAndContext(map, on_fulfilled_shared,
                                           closure_context);
}

Node* AsyncBuiltinsAssembler::AllocateAsyncIteratorValueUnwrapContext(
    Node* native_context, Node* done) {
  CSA_ASSERT(this, IsNativeContext(native_context));
  CSA_ASSERT(this, IsBoolean(done));

  Node* const context =
      CreatePromiseContext(native_context, ValueUnwrapContext::kLength);
  StoreContextElementNoWriteBarrier(context, ValueUnwrapContext::kDoneSlot,
                                    done);
  return context;
}

TF_BUILTIN(AsyncIteratorValueUnwrap, AsyncBuiltinsAssembler) {
  Node* const value = Parameter(Descriptor::kValue);
  Node* const context = Parameter(Descriptor::kContext);

  Node* const done = LoadContextElement(context, ValueUnwrapContext::kDoneSlot);
  CSA_ASSERT(this, IsBoolean(done));

  Node* const unwrapped_value =
      CallBuiltin(Builtins::kCreateIterResultObject, context, value, done);

  Return(unwrapped_value);
}

}  // namespace internal
}  // namespace v8