diff options
author | Ben Murdoch <benm@google.com> | 2017-06-06 11:06:27 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2017-06-07 12:29:44 +0100 |
commit | 62ed631aa0ff23db68a47fd423efa9c019ff2c9e (patch) | |
tree | 1df435310690642f8fd4628d6d16462d05ffc797 /src/builtins/builtins-promise.cc | |
parent | b0475911e4499628ec03ff93ce693450724887c2 (diff) | |
download | android_external_v8-62ed631aa0ff23db68a47fd423efa9c019ff2c9e.tar.gz android_external_v8-62ed631aa0ff23db68a47fd423efa9c019ff2c9e.tar.bz2 android_external_v8-62ed631aa0ff23db68a47fd423efa9c019ff2c9e.zip |
Merge V8 5.8.283.32
Test: Build V8 for arm, arm64, x86, x86_64, mips, mips64 and
set a PAC script from the UI on bullhead
Change-Id: I7cc773b5daca34d869e768a1deebae3876f2dfac
Diffstat (limited to 'src/builtins/builtins-promise.cc')
-rw-r--r-- | src/builtins/builtins-promise.cc | 1800 |
1 files changed, 1749 insertions, 51 deletions
diff --git a/src/builtins/builtins-promise.cc b/src/builtins/builtins-promise.cc index 9f5d7c88..0d0238d2 100644 --- a/src/builtins/builtins-promise.cc +++ b/src/builtins/builtins-promise.cc @@ -2,82 +2,1780 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "src/builtins/builtins-promise.h" +#include "src/builtins/builtins-constructor.h" #include "src/builtins/builtins-utils.h" #include "src/builtins/builtins.h" - -#include "src/promise-utils.h" +#include "src/code-factory.h" +#include "src/code-stub-assembler.h" +#include "src/objects-inl.h" namespace v8 { namespace internal { -// ES#sec-promise-resolve-functions -// Promise Resolve Functions -BUILTIN(PromiseResolveClosure) { - HandleScope scope(isolate); +typedef compiler::Node Node; +typedef CodeStubAssembler::ParameterMode ParameterMode; +typedef compiler::CodeAssemblerState CodeAssemblerState; + +Node* PromiseBuiltinsAssembler::AllocateJSPromise(Node* context) { + Node* const native_context = LoadNativeContext(context); + Node* const promise_fun = + LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX); + Node* const initial_map = + LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset); + Node* const instance = AllocateJSObjectFromMap(initial_map); + return instance; +} + +void PromiseBuiltinsAssembler::PromiseInit(Node* promise) { + StoreObjectField(promise, JSPromise::kStatusOffset, + SmiConstant(v8::Promise::kPending)); + StoreObjectField(promise, JSPromise::kFlagsOffset, SmiConstant(0)); +} + +Node* PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Node* context) { + return AllocateAndInitJSPromise(context, UndefinedConstant()); +} + +Node* PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Node* context, + Node* parent) { + Node* const instance = AllocateJSPromise(context); + PromiseInit(instance); + + Label out(this); + GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &out); + CallRuntime(Runtime::kPromiseHookInit, context, instance, parent); + Goto(&out); + + Bind(&out); + return instance; +} + +Node* PromiseBuiltinsAssembler::AllocateAndSetJSPromise(Node* context, + Node* status, + Node* result) { + CSA_ASSERT(this, TaggedIsSmi(status)); + + Node* const instance = AllocateJSPromise(context); + + StoreObjectFieldNoWriteBarrier(instance, JSPromise::kStatusOffset, status); + StoreObjectFieldNoWriteBarrier(instance, JSPromise::kResultOffset, result); + StoreObjectFieldNoWriteBarrier(instance, JSPromise::kFlagsOffset, + SmiConstant(0)); + + Label out(this); + GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &out); + CallRuntime(Runtime::kPromiseHookInit, context, instance, + UndefinedConstant()); + Goto(&out); + + Bind(&out); + return instance; +} + +std::pair<Node*, Node*> +PromiseBuiltinsAssembler::CreatePromiseResolvingFunctions( + Node* promise, Node* debug_event, Node* native_context) { + Node* const promise_context = CreatePromiseResolvingFunctionsContext( + promise, debug_event, native_context); + Node* const map = LoadContextElement( + native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); + Node* const resolve_info = + LoadContextElement(native_context, Context::PROMISE_RESOLVE_SHARED_FUN); + Node* const resolve = + AllocateFunctionWithMapAndContext(map, resolve_info, promise_context); + Node* const reject_info = + LoadContextElement(native_context, Context::PROMISE_REJECT_SHARED_FUN); + Node* const reject = + AllocateFunctionWithMapAndContext(map, reject_info, promise_context); + return std::make_pair(resolve, reject); +} + +Node* PromiseBuiltinsAssembler::NewPromiseCapability(Node* context, + Node* constructor, + Node* debug_event) { + if (debug_event == nullptr) { + debug_event = TrueConstant(); + } + + Node* native_context = LoadNativeContext(context); + + Node* map = LoadRoot(Heap::kJSPromiseCapabilityMapRootIndex); + Node* capability = AllocateJSObjectFromMap(map); + + StoreObjectFieldNoWriteBarrier( + capability, JSPromiseCapability::kPromiseOffset, UndefinedConstant()); + StoreObjectFieldNoWriteBarrier( + capability, JSPromiseCapability::kResolveOffset, UndefinedConstant()); + StoreObjectFieldNoWriteBarrier(capability, JSPromiseCapability::kRejectOffset, + UndefinedConstant()); + + Variable var_result(this, MachineRepresentation::kTagged); + var_result.Bind(capability); + + Label if_builtin_promise(this), if_custom_promise(this, Label::kDeferred), + out(this); + Branch(WordEqual(constructor, + LoadContextElement(native_context, + Context::PROMISE_FUNCTION_INDEX)), + &if_builtin_promise, &if_custom_promise); + + Bind(&if_builtin_promise); + { + Node* promise = AllocateJSPromise(context); + PromiseInit(promise); + StoreObjectFieldNoWriteBarrier( + capability, JSPromiseCapability::kPromiseOffset, promise); + + Node* resolve = nullptr; + Node* reject = nullptr; + + std::tie(resolve, reject) = + CreatePromiseResolvingFunctions(promise, debug_event, native_context); + StoreObjectField(capability, JSPromiseCapability::kResolveOffset, resolve); + StoreObjectField(capability, JSPromiseCapability::kRejectOffset, reject); + + GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &out); + CallRuntime(Runtime::kPromiseHookInit, context, promise, + UndefinedConstant()); + Goto(&out); + } + + Bind(&if_custom_promise); + { + Label if_notcallable(this, Label::kDeferred); + Node* executor_context = + CreatePromiseGetCapabilitiesExecutorContext(capability, native_context); + Node* executor_info = LoadContextElement( + native_context, Context::PROMISE_GET_CAPABILITIES_EXECUTOR_SHARED_FUN); + Node* function_map = LoadContextElement( + native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); + Node* executor = AllocateFunctionWithMapAndContext( + function_map, executor_info, executor_context); + + Node* promise = ConstructJS(CodeFactory::Construct(isolate()), context, + constructor, executor); + + Node* resolve = + LoadObjectField(capability, JSPromiseCapability::kResolveOffset); + GotoIf(TaggedIsSmi(resolve), &if_notcallable); + GotoIfNot(IsCallableMap(LoadMap(resolve)), &if_notcallable); + + Node* reject = + LoadObjectField(capability, JSPromiseCapability::kRejectOffset); + GotoIf(TaggedIsSmi(reject), &if_notcallable); + GotoIfNot(IsCallableMap(LoadMap(reject)), &if_notcallable); + + StoreObjectField(capability, JSPromiseCapability::kPromiseOffset, promise); + + Goto(&out); + + Bind(&if_notcallable); + Node* message = SmiConstant(MessageTemplate::kPromiseNonCallable); + StoreObjectField(capability, JSPromiseCapability::kPromiseOffset, + UndefinedConstant()); + StoreObjectField(capability, JSPromiseCapability::kResolveOffset, + UndefinedConstant()); + StoreObjectField(capability, JSPromiseCapability::kRejectOffset, + UndefinedConstant()); + CallRuntime(Runtime::kThrowTypeError, context, message); + Unreachable(); + } + + Bind(&out); + return var_result.value(); +} + +Node* PromiseBuiltinsAssembler::CreatePromiseContext(Node* native_context, + int slots) { + DCHECK_GE(slots, Context::MIN_CONTEXT_SLOTS); + + Node* const context = Allocate(FixedArray::SizeFor(slots)); + StoreMapNoWriteBarrier(context, Heap::kFunctionContextMapRootIndex); + StoreObjectFieldNoWriteBarrier(context, FixedArray::kLengthOffset, + SmiConstant(slots)); + + Node* const empty_fn = + LoadContextElement(native_context, Context::CLOSURE_INDEX); + StoreContextElementNoWriteBarrier(context, Context::CLOSURE_INDEX, empty_fn); + StoreContextElementNoWriteBarrier(context, Context::PREVIOUS_INDEX, + UndefinedConstant()); + StoreContextElementNoWriteBarrier(context, Context::EXTENSION_INDEX, + TheHoleConstant()); + StoreContextElementNoWriteBarrier(context, Context::NATIVE_CONTEXT_INDEX, + native_context); + return context; +} + +Node* PromiseBuiltinsAssembler::CreatePromiseResolvingFunctionsContext( + Node* promise, Node* debug_event, Node* native_context) { + Node* const context = + CreatePromiseContext(native_context, kPromiseContextLength); + StoreContextElementNoWriteBarrier(context, kAlreadyVisitedSlot, + SmiConstant(0)); + StoreContextElementNoWriteBarrier(context, kPromiseSlot, promise); + StoreContextElementNoWriteBarrier(context, kDebugEventSlot, debug_event); + return context; +} + +Node* PromiseBuiltinsAssembler::CreatePromiseGetCapabilitiesExecutorContext( + Node* promise_capability, Node* native_context) { + int kContextLength = kCapabilitiesContextLength; + Node* context = CreatePromiseContext(native_context, kContextLength); + StoreContextElementNoWriteBarrier(context, kCapabilitySlot, + promise_capability); + return context; +} + +Node* PromiseBuiltinsAssembler::ThrowIfNotJSReceiver( + Node* context, Node* value, MessageTemplate::Template msg_template, + const char* method_name) { + Label out(this), throw_exception(this, Label::kDeferred); + Variable var_value_map(this, MachineRepresentation::kTagged); + + GotoIf(TaggedIsSmi(value), &throw_exception); + + // Load the instance type of the {value}. + var_value_map.Bind(LoadMap(value)); + Node* const value_instance_type = LoadMapInstanceType(var_value_map.value()); + + Branch(IsJSReceiverInstanceType(value_instance_type), &out, &throw_exception); + + // The {value} is not a compatible receiver for this method. + Bind(&throw_exception); + { + Node* const method = + method_name == nullptr + ? UndefinedConstant() + : HeapConstant( + isolate()->factory()->NewStringFromAsciiChecked(method_name)); + Node* const message_id = SmiConstant(msg_template); + CallRuntime(Runtime::kThrowTypeError, context, message_id, method); + Unreachable(); + } + + Bind(&out); + return var_value_map.value(); +} + +Node* PromiseBuiltinsAssembler::PromiseHasHandler(Node* promise) { + Node* const flags = LoadObjectField(promise, JSPromise::kFlagsOffset); + return IsSetWord(SmiUntag(flags), 1 << JSPromise::kHasHandlerBit); +} + +void PromiseBuiltinsAssembler::PromiseSetHasHandler(Node* promise) { + Node* const flags = LoadObjectField(promise, JSPromise::kFlagsOffset); + Node* const new_flags = + SmiOr(flags, SmiConstant(1 << JSPromise::kHasHandlerBit)); + StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags); +} + +void PromiseBuiltinsAssembler::PromiseSetHandledHint(Node* promise) { + Node* const flags = LoadObjectField(promise, JSPromise::kFlagsOffset); + Node* const new_flags = + SmiOr(flags, SmiConstant(1 << JSPromise::kHandledHintBit)); + StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags); +} + +Node* PromiseBuiltinsAssembler::SpeciesConstructor(Node* context, Node* object, + Node* default_constructor) { + Isolate* isolate = this->isolate(); + Variable var_result(this, MachineRepresentation::kTagged); + var_result.Bind(default_constructor); + + // 2. Let C be ? Get(O, "constructor"). + Node* const constructor_str = + HeapConstant(isolate->factory()->constructor_string()); + Callable getproperty_callable = CodeFactory::GetProperty(isolate); + Node* const constructor = + CallStub(getproperty_callable, context, object, constructor_str); + + // 3. If C is undefined, return defaultConstructor. + Label out(this); + GotoIf(IsUndefined(constructor), &out); + + // 4. If Type(C) is not Object, throw a TypeError exception. + ThrowIfNotJSReceiver(context, constructor, + MessageTemplate::kConstructorNotReceiver); + + // 5. Let S be ? Get(C, @@species). + Node* const species_symbol = + HeapConstant(isolate->factory()->species_symbol()); + Node* const species = + CallStub(getproperty_callable, context, constructor, species_symbol); + + // 6. If S is either undefined or null, return defaultConstructor. + GotoIf(IsUndefined(species), &out); + GotoIf(WordEqual(species, NullConstant()), &out); + + // 7. If IsConstructor(S) is true, return S. + Label throw_error(this); + Node* species_bitfield = LoadMapBitField(LoadMap(species)); + GotoIfNot(Word32Equal(Word32And(species_bitfield, + Int32Constant((1 << Map::kIsConstructor))), + Int32Constant(1 << Map::kIsConstructor)), + &throw_error); + var_result.Bind(species); + Goto(&out); + + // 8. Throw a TypeError exception. + Bind(&throw_error); + { + Node* const message_id = + SmiConstant(MessageTemplate::kSpeciesNotConstructor); + CallRuntime(Runtime::kThrowTypeError, context, message_id); + Unreachable(); + } + + Bind(&out); + return var_result.value(); +} + +void PromiseBuiltinsAssembler::AppendPromiseCallback(int offset, Node* promise, + Node* value) { + Node* elements = LoadObjectField(promise, offset); + Node* length = LoadFixedArrayBaseLength(elements); + CodeStubAssembler::ParameterMode mode = OptimalParameterMode(); + length = TaggedToParameter(length, mode); + + Node* delta = IntPtrOrSmiConstant(1, mode); + Node* new_capacity = IntPtrOrSmiAdd(length, delta, mode); + + const ElementsKind kind = FAST_ELEMENTS; + const WriteBarrierMode barrier_mode = UPDATE_WRITE_BARRIER; + const CodeStubAssembler::AllocationFlags flags = + CodeStubAssembler::kAllowLargeObjectAllocation; + int additional_offset = 0; + + Node* new_elements = AllocateFixedArray(kind, new_capacity, mode, flags); + + CopyFixedArrayElements(kind, elements, new_elements, length, barrier_mode, + mode); + StoreFixedArrayElement(new_elements, length, value, barrier_mode, + additional_offset, mode); + + StoreObjectField(promise, offset, new_elements); +} + +Node* PromiseBuiltinsAssembler::InternalPromiseThen(Node* context, + Node* promise, + Node* on_resolve, + Node* on_reject) { + Isolate* isolate = this->isolate(); + + // 2. If IsPromise(promise) is false, throw a TypeError exception. + ThrowIfNotInstanceType(context, promise, JS_PROMISE_TYPE, + "Promise.prototype.then"); + + Node* const native_context = LoadNativeContext(context); + Node* const promise_fun = + LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX); + + // 3. Let C be ? SpeciesConstructor(promise, %Promise%). + Node* constructor = SpeciesConstructor(context, promise, promise_fun); + + // 4. Let resultCapability be ? NewPromiseCapability(C). + Callable call_callable = CodeFactory::Call(isolate); + Label fast_promise_capability(this), promise_capability(this), + perform_promise_then(this); + Variable var_deferred_promise(this, MachineRepresentation::kTagged), + var_deferred_on_resolve(this, MachineRepresentation::kTagged), + var_deferred_on_reject(this, MachineRepresentation::kTagged); + + Branch(WordEqual(promise_fun, constructor), &fast_promise_capability, + &promise_capability); + + Bind(&fast_promise_capability); + { + Node* const deferred_promise = AllocateAndInitJSPromise(context, promise); + var_deferred_promise.Bind(deferred_promise); + var_deferred_on_resolve.Bind(UndefinedConstant()); + var_deferred_on_reject.Bind(UndefinedConstant()); + Goto(&perform_promise_then); + } + + Bind(&promise_capability); + { + Node* const capability = NewPromiseCapability(context, constructor); + var_deferred_promise.Bind( + LoadObjectField(capability, JSPromiseCapability::kPromiseOffset)); + var_deferred_on_resolve.Bind( + LoadObjectField(capability, JSPromiseCapability::kResolveOffset)); + var_deferred_on_reject.Bind( + LoadObjectField(capability, JSPromiseCapability::kRejectOffset)); + Goto(&perform_promise_then); + } + + // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, + // resultCapability). + Bind(&perform_promise_then); + Node* const result = InternalPerformPromiseThen( + context, promise, on_resolve, on_reject, var_deferred_promise.value(), + var_deferred_on_resolve.value(), var_deferred_on_reject.value()); + return result; +} + +Node* PromiseBuiltinsAssembler::InternalPerformPromiseThen( + Node* context, Node* promise, Node* on_resolve, Node* on_reject, + Node* deferred_promise, Node* deferred_on_resolve, + Node* deferred_on_reject) { + + Variable var_on_resolve(this, MachineRepresentation::kTagged), + var_on_reject(this, MachineRepresentation::kTagged); + + var_on_resolve.Bind(on_resolve); + var_on_reject.Bind(on_reject); + + Label out(this), if_onresolvenotcallable(this), onrejectcheck(this), + append_callbacks(this); + GotoIf(TaggedIsSmi(on_resolve), &if_onresolvenotcallable); + + Isolate* isolate = this->isolate(); + Node* const on_resolve_map = LoadMap(on_resolve); + Branch(IsCallableMap(on_resolve_map), &onrejectcheck, + &if_onresolvenotcallable); + + Bind(&if_onresolvenotcallable); + { + Node* const default_resolve_handler_symbol = HeapConstant( + isolate->factory()->promise_default_resolve_handler_symbol()); + var_on_resolve.Bind(default_resolve_handler_symbol); + Goto(&onrejectcheck); + } + + Bind(&onrejectcheck); + { + Label if_onrejectnotcallable(this); + GotoIf(TaggedIsSmi(on_reject), &if_onrejectnotcallable); + + Node* const on_reject_map = LoadMap(on_reject); + Branch(IsCallableMap(on_reject_map), &append_callbacks, + &if_onrejectnotcallable); + + Bind(&if_onrejectnotcallable); + { + Node* const default_reject_handler_symbol = HeapConstant( + isolate->factory()->promise_default_reject_handler_symbol()); + var_on_reject.Bind(default_reject_handler_symbol); + Goto(&append_callbacks); + } + } + + Bind(&append_callbacks); + { + Label fulfilled_check(this); + Node* const status = LoadObjectField(promise, JSPromise::kStatusOffset); + GotoIfNot(SmiEqual(status, SmiConstant(v8::Promise::kPending)), + &fulfilled_check); + + Node* const existing_deferred_promise = + LoadObjectField(promise, JSPromise::kDeferredPromiseOffset); + + Label if_noexistingcallbacks(this), if_existingcallbacks(this); + Branch(IsUndefined(existing_deferred_promise), &if_noexistingcallbacks, + &if_existingcallbacks); + + Bind(&if_noexistingcallbacks); + { + // Store callbacks directly in the slots. + StoreObjectField(promise, JSPromise::kDeferredPromiseOffset, + deferred_promise); + StoreObjectField(promise, JSPromise::kDeferredOnResolveOffset, + deferred_on_resolve); + StoreObjectField(promise, JSPromise::kDeferredOnRejectOffset, + deferred_on_reject); + StoreObjectField(promise, JSPromise::kFulfillReactionsOffset, + var_on_resolve.value()); + StoreObjectField(promise, JSPromise::kRejectReactionsOffset, + var_on_reject.value()); + Goto(&out); + } + + Bind(&if_existingcallbacks); + { + Label if_singlecallback(this), if_multiplecallbacks(this); + BranchIfJSObject(existing_deferred_promise, &if_singlecallback, + &if_multiplecallbacks); + + Bind(&if_singlecallback); + { + // Create new FixedArrays to store callbacks, and migrate + // existing callbacks. + Node* const deferred_promise_arr = + AllocateFixedArray(FAST_ELEMENTS, IntPtrConstant(2)); + StoreFixedArrayElement(deferred_promise_arr, 0, + existing_deferred_promise); + StoreFixedArrayElement(deferred_promise_arr, 1, deferred_promise); + + Node* const deferred_on_resolve_arr = + AllocateFixedArray(FAST_ELEMENTS, IntPtrConstant(2)); + StoreFixedArrayElement( + deferred_on_resolve_arr, 0, + LoadObjectField(promise, JSPromise::kDeferredOnResolveOffset)); + StoreFixedArrayElement(deferred_on_resolve_arr, 1, deferred_on_resolve); + + Node* const deferred_on_reject_arr = + AllocateFixedArray(FAST_ELEMENTS, IntPtrConstant(2)); + StoreFixedArrayElement( + deferred_on_reject_arr, 0, + LoadObjectField(promise, JSPromise::kDeferredOnRejectOffset)); + StoreFixedArrayElement(deferred_on_reject_arr, 1, deferred_on_reject); + + Node* const fulfill_reactions = + AllocateFixedArray(FAST_ELEMENTS, IntPtrConstant(2)); + StoreFixedArrayElement( + fulfill_reactions, 0, + LoadObjectField(promise, JSPromise::kFulfillReactionsOffset)); + StoreFixedArrayElement(fulfill_reactions, 1, var_on_resolve.value()); + + Node* const reject_reactions = + AllocateFixedArray(FAST_ELEMENTS, IntPtrConstant(2)); + StoreFixedArrayElement( + reject_reactions, 0, + LoadObjectField(promise, JSPromise::kRejectReactionsOffset)); + StoreFixedArrayElement(reject_reactions, 1, var_on_reject.value()); + + // Store new FixedArrays in promise. + StoreObjectField(promise, JSPromise::kDeferredPromiseOffset, + deferred_promise_arr); + StoreObjectField(promise, JSPromise::kDeferredOnResolveOffset, + deferred_on_resolve_arr); + StoreObjectField(promise, JSPromise::kDeferredOnRejectOffset, + deferred_on_reject_arr); + StoreObjectField(promise, JSPromise::kFulfillReactionsOffset, + fulfill_reactions); + StoreObjectField(promise, JSPromise::kRejectReactionsOffset, + reject_reactions); + Goto(&out); + } + + Bind(&if_multiplecallbacks); + { + AppendPromiseCallback(JSPromise::kDeferredPromiseOffset, promise, + deferred_promise); + AppendPromiseCallback(JSPromise::kDeferredOnResolveOffset, promise, + deferred_on_resolve); + AppendPromiseCallback(JSPromise::kDeferredOnRejectOffset, promise, + deferred_on_reject); + AppendPromiseCallback(JSPromise::kFulfillReactionsOffset, promise, + var_on_resolve.value()); + AppendPromiseCallback(JSPromise::kRejectReactionsOffset, promise, + var_on_reject.value()); + Goto(&out); + } + } + + Bind(&fulfilled_check); + { + Label reject(this); + Node* const result = LoadObjectField(promise, JSPromise::kResultOffset); + GotoIfNot(WordEqual(status, SmiConstant(v8::Promise::kFulfilled)), + &reject); + + Node* info = AllocatePromiseReactionJobInfo( + result, var_on_resolve.value(), deferred_promise, deferred_on_resolve, + deferred_on_reject, context); + // TODO(gsathya): Move this to TF + CallRuntime(Runtime::kEnqueuePromiseReactionJob, context, info); + Goto(&out); + + Bind(&reject); + { + Node* const has_handler = PromiseHasHandler(promise); + Label enqueue(this); + + // TODO(gsathya): Fold these runtime calls and move to TF. + GotoIf(has_handler, &enqueue); + CallRuntime(Runtime::kPromiseRevokeReject, context, promise); + Goto(&enqueue); + + Bind(&enqueue); + { + Node* info = AllocatePromiseReactionJobInfo( + result, var_on_reject.value(), deferred_promise, + deferred_on_resolve, deferred_on_reject, context); + // TODO(gsathya): Move this to TF + CallRuntime(Runtime::kEnqueuePromiseReactionJob, context, info); + Goto(&out); + } + } + } + } + + Bind(&out); + PromiseSetHasHandler(promise); + return deferred_promise; +} + +// Promise fast path implementations rely on unmodified JSPromise instances. +// We use a fairly coarse granularity for this and simply check whether both +// the promise itself is unmodified (i.e. its map has not changed) and its +// prototype is unmodified. +// TODO(gsathya): Refactor this out to prevent code dupe with builtins-regexp +void PromiseBuiltinsAssembler::BranchIfFastPath(Node* context, Node* promise, + Label* if_isunmodified, + Label* if_ismodified) { + Node* const native_context = LoadNativeContext(context); + Node* const promise_fun = + LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX); + BranchIfFastPath(native_context, promise_fun, promise, if_isunmodified, + if_ismodified); +} + +void PromiseBuiltinsAssembler::BranchIfFastPath(Node* native_context, + Node* promise_fun, + Node* promise, + Label* if_isunmodified, + Label* if_ismodified) { + CSA_ASSERT(this, IsNativeContext(native_context)); + CSA_ASSERT(this, + WordEqual(promise_fun, + LoadContextElement(native_context, + Context::PROMISE_FUNCTION_INDEX))); + + Node* const map = LoadMap(promise); + Node* const initial_map = + LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset); + Node* const has_initialmap = WordEqual(map, initial_map); + + GotoIfNot(has_initialmap, if_ismodified); + + Node* const initial_proto_initial_map = + LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_MAP_INDEX); + Node* const proto_map = LoadMap(LoadMapPrototype(map)); + Node* const proto_has_initialmap = + WordEqual(proto_map, initial_proto_initial_map); + + Branch(proto_has_initialmap, if_isunmodified, if_ismodified); +} + +Node* PromiseBuiltinsAssembler::AllocatePromiseResolveThenableJobInfo( + Node* thenable, Node* then, Node* resolve, Node* reject, Node* context) { + Node* const info = Allocate(PromiseResolveThenableJobInfo::kSize); + StoreMapNoWriteBarrier(info, + Heap::kPromiseResolveThenableJobInfoMapRootIndex); + StoreObjectFieldNoWriteBarrier( + info, PromiseResolveThenableJobInfo::kThenableOffset, thenable); + StoreObjectFieldNoWriteBarrier( + info, PromiseResolveThenableJobInfo::kThenOffset, then); + StoreObjectFieldNoWriteBarrier( + info, PromiseResolveThenableJobInfo::kResolveOffset, resolve); + StoreObjectFieldNoWriteBarrier( + info, PromiseResolveThenableJobInfo::kRejectOffset, reject); + StoreObjectFieldNoWriteBarrier( + info, PromiseResolveThenableJobInfo::kContextOffset, context); + return info; +} + +void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context, + Node* promise, + Node* result) { + Isolate* isolate = this->isolate(); + + Variable var_reason(this, MachineRepresentation::kTagged), + var_then(this, MachineRepresentation::kTagged); + + Label do_enqueue(this), fulfill(this), if_cycle(this, Label::kDeferred), + if_rejectpromise(this, Label::kDeferred), out(this); + + Label cycle_check(this); + GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &cycle_check); + CallRuntime(Runtime::kPromiseHookResolve, context, promise); + Goto(&cycle_check); + + Bind(&cycle_check); + // 6. If SameValue(resolution, promise) is true, then + GotoIf(SameValue(promise, result, context), &if_cycle); + + // 7. If Type(resolution) is not Object, then + GotoIf(TaggedIsSmi(result), &fulfill); + GotoIfNot(IsJSReceiver(result), &fulfill); + + Label if_nativepromise(this), if_notnativepromise(this, Label::kDeferred); + Node* const native_context = LoadNativeContext(context); + Node* const promise_fun = + LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX); + BranchIfFastPath(native_context, promise_fun, result, &if_nativepromise, + &if_notnativepromise); + + // Resolution is a native promise and if it's already resolved or + // rejected, shortcircuit the resolution procedure by directly + // reusing the value from the promise. + Bind(&if_nativepromise); + { + Node* const thenable_status = + LoadObjectField(result, JSPromise::kStatusOffset); + Node* const thenable_value = + LoadObjectField(result, JSPromise::kResultOffset); + + Label if_isnotpending(this); + GotoIfNot(SmiEqual(SmiConstant(v8::Promise::kPending), thenable_status), + &if_isnotpending); + + // TODO(gsathya): Use a marker here instead of the actual then + // callback, and check for the marker in PromiseResolveThenableJob + // and perform PromiseThen. + Node* const then = + LoadContextElement(native_context, Context::PROMISE_THEN_INDEX); + var_then.Bind(then); + Goto(&do_enqueue); + + Bind(&if_isnotpending); + { + Label if_fulfilled(this), if_rejected(this); + Branch(SmiEqual(SmiConstant(v8::Promise::kFulfilled), thenable_status), + &if_fulfilled, &if_rejected); + + Bind(&if_fulfilled); + { + PromiseFulfill(context, promise, thenable_value, + v8::Promise::kFulfilled); + PromiseSetHasHandler(promise); + Goto(&out); + } + + Bind(&if_rejected); + { + Label reject(this); + Node* const has_handler = PromiseHasHandler(result); + + // Promise has already been rejected, but had no handler. + // Revoke previously triggered reject event. + GotoIf(has_handler, &reject); + CallRuntime(Runtime::kPromiseRevokeReject, context, result); + Goto(&reject); + + Bind(&reject); + // Don't cause a debug event as this case is forwarding a rejection + InternalPromiseReject(context, promise, thenable_value, false); + PromiseSetHasHandler(result); + Goto(&out); + } + } + } + + Bind(&if_notnativepromise); + { + // 8. Let then be Get(resolution, "then"). + Node* const then_str = HeapConstant(isolate->factory()->then_string()); + Callable getproperty_callable = CodeFactory::GetProperty(isolate); + Node* const then = + CallStub(getproperty_callable, context, result, then_str); + + // 9. If then is an abrupt completion, then + GotoIfException(then, &if_rejectpromise, &var_reason); + + // 11. If IsCallable(thenAction) is false, then + GotoIf(TaggedIsSmi(then), &fulfill); + Node* const then_map = LoadMap(then); + GotoIfNot(IsCallableMap(then_map), &fulfill); + var_then.Bind(then); + Goto(&do_enqueue); + } + + Bind(&do_enqueue); + { + // TODO(gsathya): Add fast path for native promises with unmodified + // PromiseThen (which don't need these resolving functions, but + // instead can just call resolve/reject directly). + Node* resolve = nullptr; + Node* reject = nullptr; + std::tie(resolve, reject) = CreatePromiseResolvingFunctions( + promise, FalseConstant(), native_context); + + Node* const info = AllocatePromiseResolveThenableJobInfo( + result, var_then.value(), resolve, reject, context); + + Label enqueue(this); + GotoIfNot(IsDebugActive(), &enqueue); + + GotoIf(TaggedIsSmi(result), &enqueue); + GotoIfNot(HasInstanceType(result, JS_PROMISE_TYPE), &enqueue); + + // Mark the dependency of the new promise on the resolution + Node* const key = + HeapConstant(isolate->factory()->promise_handled_by_symbol()); + CallRuntime(Runtime::kSetProperty, context, result, key, promise, + SmiConstant(STRICT)); + Goto(&enqueue); + + // 12. Perform EnqueueJob("PromiseJobs", + // PromiseResolveThenableJob, « promise, resolution, thenAction»). + Bind(&enqueue); + // TODO(gsathya): Move this to TF + CallRuntime(Runtime::kEnqueuePromiseResolveThenableJob, context, info); + Goto(&out); + } + + // 7.b Return FulfillPromise(promise, resolution). + Bind(&fulfill); + { + PromiseFulfill(context, promise, result, v8::Promise::kFulfilled); + Goto(&out); + } + + Bind(&if_cycle); + { + // 6.a Let selfResolutionError be a newly created TypeError object. + Node* const message_id = SmiConstant(MessageTemplate::kPromiseCyclic); + Node* const error = + CallRuntime(Runtime::kNewTypeError, context, message_id, result); + var_reason.Bind(error); + + // 6.b Return RejectPromise(promise, selfResolutionError). + Goto(&if_rejectpromise); + } + + // 9.a Return RejectPromise(promise, then.[[Value]]). + Bind(&if_rejectpromise); + { + InternalPromiseReject(context, promise, var_reason.value(), true); + Goto(&out); + } + + Bind(&out); +} + +void PromiseBuiltinsAssembler::PromiseFulfill( + Node* context, Node* promise, Node* result, + v8::Promise::PromiseState status) { + Label do_promisereset(this), debug_async_event_enqueue_recurring(this); + + Node* const status_smi = SmiConstant(static_cast<int>(status)); + Node* const deferred_promise = + LoadObjectField(promise, JSPromise::kDeferredPromiseOffset); + + GotoIf(IsUndefined(deferred_promise), &debug_async_event_enqueue_recurring); + + Node* const tasks = + status == v8::Promise::kFulfilled + ? LoadObjectField(promise, JSPromise::kFulfillReactionsOffset) + : LoadObjectField(promise, JSPromise::kRejectReactionsOffset); + + Node* const deferred_on_resolve = + LoadObjectField(promise, JSPromise::kDeferredOnResolveOffset); + Node* const deferred_on_reject = + LoadObjectField(promise, JSPromise::kDeferredOnRejectOffset); + + Node* const info = AllocatePromiseReactionJobInfo( + result, tasks, deferred_promise, deferred_on_resolve, deferred_on_reject, + context); + + CallRuntime(Runtime::kEnqueuePromiseReactionJob, context, info); + Goto(&debug_async_event_enqueue_recurring); + + Bind(&debug_async_event_enqueue_recurring); + { + GotoIfNot(IsDebugActive(), &do_promisereset); + CallRuntime(Runtime::kDebugAsyncEventEnqueueRecurring, context, promise, + status_smi); + Goto(&do_promisereset); + } + + Bind(&do_promisereset); + { + StoreObjectField(promise, JSPromise::kStatusOffset, status_smi); + StoreObjectField(promise, JSPromise::kResultOffset, result); + StoreObjectFieldRoot(promise, JSPromise::kDeferredPromiseOffset, + Heap::kUndefinedValueRootIndex); + StoreObjectFieldRoot(promise, JSPromise::kDeferredOnResolveOffset, + Heap::kUndefinedValueRootIndex); + StoreObjectFieldRoot(promise, JSPromise::kDeferredOnRejectOffset, + Heap::kUndefinedValueRootIndex); + StoreObjectFieldRoot(promise, JSPromise::kFulfillReactionsOffset, + Heap::kUndefinedValueRootIndex); + StoreObjectFieldRoot(promise, JSPromise::kRejectReactionsOffset, + Heap::kUndefinedValueRootIndex); + } +} + +void PromiseBuiltinsAssembler::BranchIfAccessCheckFailed( + Node* context, Node* native_context, Node* promise_constructor, + Node* executor, Label* if_noaccess) { + Variable var_executor(this, MachineRepresentation::kTagged); + var_executor.Bind(executor); + Label has_access(this), call_runtime(this, Label::kDeferred); + + // If executor is a bound function, load the bound function until we've + // reached an actual function. + Label found_function(this), loop_over_bound_function(this, &var_executor); + Goto(&loop_over_bound_function); + Bind(&loop_over_bound_function); + { + Node* executor_type = LoadInstanceType(var_executor.value()); + GotoIf(InstanceTypeEqual(executor_type, JS_FUNCTION_TYPE), &found_function); + GotoIfNot(InstanceTypeEqual(executor_type, JS_BOUND_FUNCTION_TYPE), + &call_runtime); + var_executor.Bind(LoadObjectField( + var_executor.value(), JSBoundFunction::kBoundTargetFunctionOffset)); + Goto(&loop_over_bound_function); + } + + // Load the context from the function and compare it to the Promise + // constructor's context. If they match, everything is fine, otherwise, bail + // out to the runtime. + Bind(&found_function); + { + Node* function_context = + LoadObjectField(var_executor.value(), JSFunction::kContextOffset); + Node* native_function_context = LoadNativeContext(function_context); + Branch(WordEqual(native_context, native_function_context), &has_access, + &call_runtime); + } + + Bind(&call_runtime); + { + Branch(WordEqual(CallRuntime(Runtime::kAllowDynamicFunction, context, + promise_constructor), + BooleanConstant(true)), + &has_access, if_noaccess); + } + + Bind(&has_access); +} + +void PromiseBuiltinsAssembler::InternalPromiseReject(Node* context, + Node* promise, Node* value, + Node* debug_event) { + Label out(this); + GotoIfNot(IsDebugActive(), &out); + GotoIfNot(WordEqual(TrueConstant(), debug_event), &out); + CallRuntime(Runtime::kDebugPromiseReject, context, promise, value); + Goto(&out); - Handle<Context> context(isolate->context(), isolate); + Bind(&out); + InternalPromiseReject(context, promise, value, false); +} + +// This duplicates a lot of logic from PromiseRejectEvent in +// runtime-promise.cc +void PromiseBuiltinsAssembler::InternalPromiseReject(Node* context, + Node* promise, Node* value, + bool debug_event) { + Label fulfill(this), report_unhandledpromise(this), run_promise_hook(this); - if (PromiseUtils::HasAlreadyVisited(context)) { - return isolate->heap()->undefined_value(); + if (debug_event) { + GotoIfNot(IsDebugActive(), &run_promise_hook); + CallRuntime(Runtime::kDebugPromiseReject, context, promise, value); + Goto(&run_promise_hook); + } else { + Goto(&run_promise_hook); } - PromiseUtils::SetAlreadyVisited(context); - Handle<JSObject> promise = handle(PromiseUtils::GetPromise(context), isolate); - Handle<Object> value = args.atOrUndefined(isolate, 1); + Bind(&run_promise_hook); + { + GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &report_unhandledpromise); + CallRuntime(Runtime::kPromiseHookResolve, context, promise); + Goto(&report_unhandledpromise); + } - MaybeHandle<Object> maybe_result; - Handle<Object> argv[] = {promise, value}; - RETURN_FAILURE_ON_EXCEPTION( - isolate, Execution::Call(isolate, isolate->promise_resolve(), - isolate->factory()->undefined_value(), - arraysize(argv), argv)); - return isolate->heap()->undefined_value(); + Bind(&report_unhandledpromise); + { + GotoIf(PromiseHasHandler(promise), &fulfill); + CallRuntime(Runtime::kReportPromiseReject, context, promise, value); + Goto(&fulfill); + } + + Bind(&fulfill); + PromiseFulfill(context, promise, value, v8::Promise::kRejected); } // ES#sec-promise-reject-functions // Promise Reject Functions -BUILTIN(PromiseRejectClosure) { - HandleScope scope(isolate); +TF_BUILTIN(PromiseRejectClosure, PromiseBuiltinsAssembler) { + Node* const value = Parameter(1); + Node* const context = Parameter(4); + + Label out(this); + + // 3. Let alreadyResolved be F.[[AlreadyResolved]]. + int has_already_visited_slot = kAlreadyVisitedSlot; + + Node* const has_already_visited = + LoadContextElement(context, has_already_visited_slot); + + // 4. If alreadyResolved.[[Value]] is true, return undefined. + GotoIf(SmiEqual(has_already_visited, SmiConstant(1)), &out); + + // 5.Set alreadyResolved.[[Value]] to true. + StoreContextElementNoWriteBarrier(context, has_already_visited_slot, + SmiConstant(1)); + + // 2. Let promise be F.[[Promise]]. + Node* const promise = + LoadContextElement(context, IntPtrConstant(kPromiseSlot)); + Node* const debug_event = + LoadContextElement(context, IntPtrConstant(kDebugEventSlot)); + + InternalPromiseReject(context, promise, value, debug_event); + Return(UndefinedConstant()); + + Bind(&out); + Return(UndefinedConstant()); +} + +TF_BUILTIN(PromiseConstructor, PromiseBuiltinsAssembler) { + Node* const executor = Parameter(1); + Node* const new_target = Parameter(2); + Node* const context = Parameter(4); + Isolate* isolate = this->isolate(); + + Label if_targetisundefined(this, Label::kDeferred); + + GotoIf(IsUndefined(new_target), &if_targetisundefined); + + Label if_notcallable(this, Label::kDeferred); + + GotoIf(TaggedIsSmi(executor), &if_notcallable); + + Node* const executor_map = LoadMap(executor); + GotoIfNot(IsCallableMap(executor_map), &if_notcallable); + + Node* const native_context = LoadNativeContext(context); + Node* const promise_fun = + LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX); + Node* const is_debug_active = IsDebugActive(); + Label if_targetisnotmodified(this), + if_targetismodified(this, Label::kDeferred), run_executor(this), + debug_push(this), if_noaccess(this, Label::kDeferred); + + BranchIfAccessCheckFailed(context, native_context, promise_fun, executor, + &if_noaccess); + + Branch(WordEqual(promise_fun, new_target), &if_targetisnotmodified, + &if_targetismodified); + + Variable var_result(this, MachineRepresentation::kTagged), + var_reject_call(this, MachineRepresentation::kTagged), + var_reason(this, MachineRepresentation::kTagged); + + Bind(&if_targetisnotmodified); + { + Node* const instance = AllocateAndInitJSPromise(context); + var_result.Bind(instance); + Goto(&debug_push); + } + + Bind(&if_targetismodified); + { + ConstructorBuiltinsAssembler constructor_assembler(this->state()); + Node* const instance = constructor_assembler.EmitFastNewObject( + context, promise_fun, new_target); + PromiseInit(instance); + var_result.Bind(instance); + + GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &debug_push); + CallRuntime(Runtime::kPromiseHookInit, context, instance, + UndefinedConstant()); + Goto(&debug_push); + } + + Bind(&debug_push); + { + GotoIfNot(is_debug_active, &run_executor); + CallRuntime(Runtime::kDebugPushPromise, context, var_result.value()); + Goto(&run_executor); + } + + Bind(&run_executor); + { + Label out(this), if_rejectpromise(this), debug_pop(this, Label::kDeferred); + + Node *resolve, *reject; + std::tie(resolve, reject) = CreatePromiseResolvingFunctions( + var_result.value(), TrueConstant(), native_context); + Callable call_callable = CodeFactory::Call(isolate); + + Node* const maybe_exception = CallJS(call_callable, context, executor, + UndefinedConstant(), resolve, reject); + + GotoIfException(maybe_exception, &if_rejectpromise, &var_reason); + Branch(is_debug_active, &debug_pop, &out); + + Bind(&if_rejectpromise); + { + Callable call_callable = CodeFactory::Call(isolate); + CallJS(call_callable, context, reject, UndefinedConstant(), + var_reason.value()); + Branch(is_debug_active, &debug_pop, &out); + } + + Bind(&debug_pop); + { + CallRuntime(Runtime::kDebugPopPromise, context); + Goto(&out); + } + Bind(&out); + Return(var_result.value()); + } + + // 1. If NewTarget is undefined, throw a TypeError exception. + Bind(&if_targetisundefined); + { + Node* const message_id = SmiConstant(MessageTemplate::kNotAPromise); + CallRuntime(Runtime::kThrowTypeError, context, message_id, new_target); + Unreachable(); + } + + // 2. If IsCallable(executor) is false, throw a TypeError exception. + Bind(&if_notcallable); + { + Node* const message_id = + SmiConstant(MessageTemplate::kResolverNotAFunction); + CallRuntime(Runtime::kThrowTypeError, context, message_id, executor); + Unreachable(); + } + + // Silently fail if the stack looks fishy. + Bind(&if_noaccess); + { + Node* const counter_id = + SmiConstant(v8::Isolate::kPromiseConstructorReturnedUndefined); + CallRuntime(Runtime::kIncrementUseCounter, context, counter_id); + Return(UndefinedConstant()); + } +} + +TF_BUILTIN(PromiseInternalConstructor, PromiseBuiltinsAssembler) { + Node* const parent = Parameter(1); + Node* const context = Parameter(4); + Return(AllocateAndInitJSPromise(context, parent)); +} + +TF_BUILTIN(IsPromise, PromiseBuiltinsAssembler) { + Node* const maybe_promise = Parameter(1); + Label if_notpromise(this, Label::kDeferred); + + GotoIf(TaggedIsSmi(maybe_promise), &if_notpromise); + + Node* const result = + SelectBooleanConstant(HasInstanceType(maybe_promise, JS_PROMISE_TYPE)); + Return(result); + + Bind(&if_notpromise); + Return(FalseConstant()); +} + +// ES#sec-promise.prototype.then +// Promise.prototype.catch ( onFulfilled, onRejected ) +TF_BUILTIN(PromiseThen, PromiseBuiltinsAssembler) { + // 1. Let promise be the this value. + Node* const promise = Parameter(0); + Node* const on_resolve = Parameter(1); + Node* const on_reject = Parameter(2); + Node* const context = Parameter(5); + + Node* const result = + InternalPromiseThen(context, promise, on_resolve, on_reject); + Return(result); +} + +// ES#sec-promise-resolve-functions +// Promise Resolve Functions +TF_BUILTIN(PromiseResolveClosure, PromiseBuiltinsAssembler) { + Node* const value = Parameter(1); + Node* const context = Parameter(4); + + Label out(this); + + // 3. Let alreadyResolved be F.[[AlreadyResolved]]. + int has_already_visited_slot = kAlreadyVisitedSlot; + + Node* const has_already_visited = + LoadContextElement(context, has_already_visited_slot); + + // 4. If alreadyResolved.[[Value]] is true, return undefined. + GotoIf(SmiEqual(has_already_visited, SmiConstant(1)), &out); + + // 5.Set alreadyResolved.[[Value]] to true. + StoreContextElementNoWriteBarrier(context, has_already_visited_slot, + SmiConstant(1)); + + // 2. Let promise be F.[[Promise]]. + Node* const promise = + LoadContextElement(context, IntPtrConstant(kPromiseSlot)); + + InternalResolvePromise(context, promise, value); + Return(UndefinedConstant()); + + Bind(&out); + Return(UndefinedConstant()); +} + +TF_BUILTIN(ResolvePromise, PromiseBuiltinsAssembler) { + Node* const promise = Parameter(1); + Node* const result = Parameter(2); + Node* const context = Parameter(5); + + InternalResolvePromise(context, promise, result); + Return(UndefinedConstant()); +} + +TF_BUILTIN(PromiseHandleReject, PromiseBuiltinsAssembler) { + typedef PromiseHandleRejectDescriptor Descriptor; + + Node* const promise = Parameter(Descriptor::kPromise); + Node* const on_reject = Parameter(Descriptor::kOnReject); + Node* const exception = Parameter(Descriptor::kException); + Node* const context = Parameter(Descriptor::kContext); + + Callable call_callable = CodeFactory::Call(isolate()); + Variable var_unused(this, MachineRepresentation::kTagged); + + Label if_internalhandler(this), if_customhandler(this, Label::kDeferred); + Branch(IsUndefined(on_reject), &if_internalhandler, &if_customhandler); + + Bind(&if_internalhandler); + { + InternalPromiseReject(context, promise, exception, false); + Return(UndefinedConstant()); + } + + Bind(&if_customhandler); + { + CallJS(call_callable, context, on_reject, UndefinedConstant(), exception); + Return(UndefinedConstant()); + } +} + +TF_BUILTIN(PromiseHandle, PromiseBuiltinsAssembler) { + Node* const value = Parameter(1); + Node* const handler = Parameter(2); + Node* const deferred_promise = Parameter(3); + Node* const deferred_on_resolve = Parameter(4); + Node* const deferred_on_reject = Parameter(5); + Node* const context = Parameter(8); + Isolate* isolate = this->isolate(); + + Variable var_reason(this, MachineRepresentation::kTagged); + + Node* const is_debug_active = IsDebugActive(); + Label run_handler(this), if_rejectpromise(this), promisehook_before(this), + promisehook_after(this), debug_pop(this); + + GotoIfNot(is_debug_active, &promisehook_before); + CallRuntime(Runtime::kDebugPushPromise, context, deferred_promise); + Goto(&promisehook_before); + + Bind(&promisehook_before); + { + GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &run_handler); + CallRuntime(Runtime::kPromiseHookBefore, context, deferred_promise); + Goto(&run_handler); + } + + Bind(&run_handler); + { + Label if_defaulthandler(this), if_callablehandler(this), + if_internalhandler(this), if_customhandler(this, Label::kDeferred); + Variable var_result(this, MachineRepresentation::kTagged); + + Branch(IsSymbol(handler), &if_defaulthandler, &if_callablehandler); + + Bind(&if_defaulthandler); + { + Label if_resolve(this), if_reject(this); + Node* const default_resolve_handler_symbol = HeapConstant( + isolate->factory()->promise_default_resolve_handler_symbol()); + Branch(WordEqual(default_resolve_handler_symbol, handler), &if_resolve, + &if_reject); + + Bind(&if_resolve); + { + var_result.Bind(value); + Branch(IsUndefined(deferred_on_resolve), &if_internalhandler, + &if_customhandler); + } + + Bind(&if_reject); + { + var_reason.Bind(value); + Goto(&if_rejectpromise); + } + } + + Bind(&if_callablehandler); + { + Callable call_callable = CodeFactory::Call(isolate); + Node* const result = + CallJS(call_callable, context, handler, UndefinedConstant(), value); + var_result.Bind(result); + GotoIfException(result, &if_rejectpromise, &var_reason); + Branch(IsUndefined(deferred_on_resolve), &if_internalhandler, + &if_customhandler); + } + + Bind(&if_internalhandler); + InternalResolvePromise(context, deferred_promise, var_result.value()); + Goto(&promisehook_after); + + Bind(&if_customhandler); + { + Callable call_callable = CodeFactory::Call(isolate); + Node* const maybe_exception = + CallJS(call_callable, context, deferred_on_resolve, + UndefinedConstant(), var_result.value()); + GotoIfException(maybe_exception, &if_rejectpromise, &var_reason); + Goto(&promisehook_after); + } + } + + Bind(&if_rejectpromise); + { + Callable promise_handle_reject = CodeFactory::PromiseHandleReject(isolate); + CallStub(promise_handle_reject, context, deferred_promise, + deferred_on_reject, var_reason.value()); + Goto(&promisehook_after); + } + + Bind(&promisehook_after); + { + GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &debug_pop); + CallRuntime(Runtime::kPromiseHookAfter, context, deferred_promise); + Goto(&debug_pop); + } + + Bind(&debug_pop); + { + Label out(this); + + GotoIfNot(is_debug_active, &out); + CallRuntime(Runtime::kDebugPopPromise, context); + Goto(&out); + + Bind(&out); + Return(UndefinedConstant()); + } +} + +// ES#sec-promise.prototype.catch +// Promise.prototype.catch ( onRejected ) +TF_BUILTIN(PromiseCatch, PromiseBuiltinsAssembler) { + // 1. Let promise be the this value. + Node* const promise = Parameter(0); + Node* const on_resolve = UndefinedConstant(); + Node* const on_reject = Parameter(1); + Node* const context = Parameter(4); - Handle<Context> context(isolate->context(), isolate); + Label if_internalthen(this), if_customthen(this, Label::kDeferred); + GotoIf(TaggedIsSmi(promise), &if_customthen); + BranchIfFastPath(context, promise, &if_internalthen, &if_customthen); - if (PromiseUtils::HasAlreadyVisited(context)) { - return isolate->heap()->undefined_value(); + Bind(&if_internalthen); + { + Node* const result = + InternalPromiseThen(context, promise, on_resolve, on_reject); + Return(result); } - PromiseUtils::SetAlreadyVisited(context); - Handle<Object> value = args.atOrUndefined(isolate, 1); - Handle<JSObject> promise = handle(PromiseUtils::GetPromise(context), isolate); - Handle<Object> debug_event = - handle(PromiseUtils::GetDebugEvent(context), isolate); - MaybeHandle<Object> maybe_result; - Handle<Object> argv[] = {promise, value, debug_event}; - RETURN_FAILURE_ON_EXCEPTION( - isolate, Execution::Call(isolate, isolate->promise_internal_reject(), - isolate->factory()->undefined_value(), - arraysize(argv), argv)); - return isolate->heap()->undefined_value(); + Bind(&if_customthen); + { + Isolate* isolate = this->isolate(); + Node* const then_str = HeapConstant(isolate->factory()->then_string()); + Callable getproperty_callable = CodeFactory::GetProperty(isolate); + Node* const then = + CallStub(getproperty_callable, context, promise, then_str); + Callable call_callable = CodeFactory::Call(isolate); + Node* const result = + CallJS(call_callable, context, then, promise, on_resolve, on_reject); + Return(result); + } } -// ES#sec-createresolvingfunctions -// CreateResolvingFunctions ( promise ) -BUILTIN(CreateResolvingFunctions) { - HandleScope scope(isolate); - DCHECK_EQ(3, args.length()); +TF_BUILTIN(PromiseResolve, PromiseBuiltinsAssembler) { + // 1. Let C be the this value. + Node* receiver = Parameter(0); + Node* value = Parameter(1); + Node* context = Parameter(4); + Isolate* isolate = this->isolate(); + + // 2. If Type(C) is not Object, throw a TypeError exception. + ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject, + "PromiseResolve"); + + Label if_valueisnativepromise(this), if_valueisnotnativepromise(this), + if_valueisnotpromise(this); - Handle<JSObject> promise = args.at<JSObject>(1); - Handle<Object> debug_event = args.at<Object>(2); - Handle<JSFunction> resolve, reject; + // 3.If IsPromise(x) is true, then + GotoIf(TaggedIsSmi(value), &if_valueisnotpromise); - PromiseUtils::CreateResolvingFunctions(isolate, promise, debug_event, - &resolve, &reject); + // This shortcircuits the constructor lookups. + GotoIfNot(HasInstanceType(value, JS_PROMISE_TYPE), &if_valueisnotpromise); + + // This adds a fast path as non-subclassed native promises don't have + // an observable constructor lookup. + Node* const native_context = LoadNativeContext(context); + Node* const promise_fun = + LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX); + BranchIfFastPath(native_context, promise_fun, value, &if_valueisnativepromise, + &if_valueisnotnativepromise); + + Bind(&if_valueisnativepromise); + { + GotoIfNot(WordEqual(promise_fun, receiver), &if_valueisnotnativepromise); + Return(value); + } + + // At this point, value or/and receiver are not native promises, but + // they could be of the same subclass. + Bind(&if_valueisnotnativepromise); + { + // 3.a Let xConstructor be ? Get(x, "constructor"). + // The constructor lookup is observable. + Node* const constructor_str = + HeapConstant(isolate->factory()->constructor_string()); + Callable getproperty_callable = CodeFactory::GetProperty(isolate); + Node* const constructor = + CallStub(getproperty_callable, context, value, constructor_str); + + // 3.b If SameValue(xConstructor, C) is true, return x. + GotoIfNot(SameValue(constructor, receiver, context), &if_valueisnotpromise); + + Return(value); + } + + Bind(&if_valueisnotpromise); + { + Label if_nativepromise(this), if_notnativepromise(this); + BranchIfFastPath(context, receiver, &if_nativepromise, + &if_notnativepromise); + + // This adds a fast path for native promises that don't need to + // create NewPromiseCapability. + Bind(&if_nativepromise); + { + Label do_resolve(this); + + Node* const result = AllocateAndInitJSPromise(context); + InternalResolvePromise(context, result, value); + Return(result); + } + + Bind(&if_notnativepromise); + { + // 4. Let promiseCapability be ? NewPromiseCapability(C). + Node* const capability = NewPromiseCapability(context, receiver); + + // 5. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »). + Callable call_callable = CodeFactory::Call(isolate); + Node* const resolve = + LoadObjectField(capability, JSPromiseCapability::kResolveOffset); + CallJS(call_callable, context, resolve, UndefinedConstant(), value); + + // 6. Return promiseCapability.[[Promise]]. + Node* const result = + LoadObjectField(capability, JSPromiseCapability::kPromiseOffset); + Return(result); + } + } +} - Handle<FixedArray> result = isolate->factory()->NewFixedArray(2); - result->set(0, *resolve); - result->set(1, *reject); +TF_BUILTIN(PromiseGetCapabilitiesExecutor, PromiseBuiltinsAssembler) { + Node* const resolve = Parameter(1); + Node* const reject = Parameter(2); + Node* const context = Parameter(5); - return *isolate->factory()->NewJSArrayWithElements(result, FAST_ELEMENTS, 2, - NOT_TENURED); + Node* const capability = LoadContextElement(context, kCapabilitySlot); + + Label if_alreadyinvoked(this, Label::kDeferred); + GotoIf(WordNotEqual( + LoadObjectField(capability, JSPromiseCapability::kResolveOffset), + UndefinedConstant()), + &if_alreadyinvoked); + GotoIf(WordNotEqual( + LoadObjectField(capability, JSPromiseCapability::kRejectOffset), + UndefinedConstant()), + &if_alreadyinvoked); + + StoreObjectField(capability, JSPromiseCapability::kResolveOffset, resolve); + StoreObjectField(capability, JSPromiseCapability::kRejectOffset, reject); + + Return(UndefinedConstant()); + + Bind(&if_alreadyinvoked); + Node* message = SmiConstant(MessageTemplate::kPromiseExecutorAlreadyInvoked); + CallRuntime(Runtime::kThrowTypeError, context, message); + Unreachable(); +} + +TF_BUILTIN(NewPromiseCapability, PromiseBuiltinsAssembler) { + Node* constructor = Parameter(1); + Node* debug_event = Parameter(2); + Node* context = Parameter(5); + + CSA_ASSERT_JS_ARGC_EQ(this, 2); + + Return(NewPromiseCapability(context, constructor, debug_event)); +} + +TF_BUILTIN(PromiseReject, PromiseBuiltinsAssembler) { + // 1. Let C be the this value. + Node* const receiver = Parameter(0); + Node* const reason = Parameter(1); + Node* const context = Parameter(4); + + // 2. If Type(C) is not Object, throw a TypeError exception. + ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject, + "PromiseReject"); + + Label if_nativepromise(this), if_custompromise(this, Label::kDeferred); + Node* const native_context = LoadNativeContext(context); + Node* const promise_fun = + LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX); + Branch(WordEqual(promise_fun, receiver), &if_nativepromise, + &if_custompromise); + + Bind(&if_nativepromise); + { + Node* const promise = AllocateAndSetJSPromise( + context, SmiConstant(v8::Promise::kRejected), reason); + CallRuntime(Runtime::kPromiseRejectEventFromStack, context, promise, + reason); + Return(promise); + } + + Bind(&if_custompromise); + { + // 3. Let promiseCapability be ? NewPromiseCapability(C). + Node* const capability = NewPromiseCapability(context, receiver); + + // 4. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »). + Node* const reject = + LoadObjectField(capability, JSPromiseCapability::kRejectOffset); + Callable call_callable = CodeFactory::Call(isolate()); + CallJS(call_callable, context, reject, UndefinedConstant(), reason); + + // 5. Return promiseCapability.[[Promise]]. + Node* const promise = + LoadObjectField(capability, JSPromiseCapability::kPromiseOffset); + Return(promise); + } +} + +TF_BUILTIN(InternalPromiseReject, PromiseBuiltinsAssembler) { + Node* const promise = Parameter(1); + Node* const reason = Parameter(2); + Node* const debug_event = Parameter(3); + Node* const context = Parameter(6); + + InternalPromiseReject(context, promise, reason, debug_event); + Return(UndefinedConstant()); +} + +Node* PromiseBuiltinsAssembler::CreatePromiseFinallyContext( + Node* on_finally, Node* native_context) { + Node* const context = + CreatePromiseContext(native_context, kOnFinallyContextLength); + StoreContextElementNoWriteBarrier(context, kOnFinallySlot, on_finally); + return context; +} + +std::pair<Node*, Node*> PromiseBuiltinsAssembler::CreatePromiseFinallyFunctions( + Node* on_finally, Node* native_context) { + Node* const promise_context = + CreatePromiseFinallyContext(on_finally, native_context); + Node* const map = LoadContextElement( + native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); + Node* const then_finally_info = LoadContextElement( + native_context, Context::PROMISE_THEN_FINALLY_SHARED_FUN); + Node* const then_finally = AllocateFunctionWithMapAndContext( + map, then_finally_info, promise_context); + Node* const catch_finally_info = LoadContextElement( + native_context, Context::PROMISE_CATCH_FINALLY_SHARED_FUN); + Node* const catch_finally = AllocateFunctionWithMapAndContext( + map, catch_finally_info, promise_context); + return std::make_pair(then_finally, catch_finally); +} + +TF_BUILTIN(PromiseValueThunkFinally, PromiseBuiltinsAssembler) { + Node* const context = Parameter(3); + + Node* const value = LoadContextElement(context, kOnFinallySlot); + Return(value); +} + +Node* PromiseBuiltinsAssembler::CreateValueThunkFunctionContext( + Node* value, Node* native_context) { + Node* const context = + CreatePromiseContext(native_context, kOnFinallyContextLength); + StoreContextElementNoWriteBarrier(context, kOnFinallySlot, value); + return context; +} + +Node* PromiseBuiltinsAssembler::CreateValueThunkFunction(Node* value, + Node* native_context) { + Node* const value_thunk_context = + CreateValueThunkFunctionContext(value, native_context); + Node* const map = LoadContextElement( + native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); + Node* const value_thunk_info = LoadContextElement( + native_context, Context::PROMISE_VALUE_THUNK_FINALLY_SHARED_FUN); + Node* const value_thunk = AllocateFunctionWithMapAndContext( + map, value_thunk_info, value_thunk_context); + return value_thunk; +} + +TF_BUILTIN(PromiseThenFinally, PromiseBuiltinsAssembler) { + CSA_ASSERT_JS_ARGC_EQ(this, 1); + + Node* const value = Parameter(1); + Node* const context = Parameter(4); + + Node* const on_finally = LoadContextElement(context, kOnFinallySlot); + + // 2.a Let result be ? Call(onFinally, undefined). + Callable call_callable = CodeFactory::Call(isolate()); + Node* result = + CallJS(call_callable, context, on_finally, UndefinedConstant()); + + // 2.b Let promise be ! PromiseResolve( %Promise%, result). + Node* const promise = AllocateAndInitJSPromise(context); + InternalResolvePromise(context, promise, result); + + // 2.c Let valueThunk be equivalent to a function that returns value. + Node* native_context = LoadNativeContext(context); + Node* const value_thunk = CreateValueThunkFunction(value, native_context); + + // 2.d Let promiseCapability be ! NewPromiseCapability( %Promise%). + Node* const promise_capability = AllocateAndInitJSPromise(context, promise); + + // 2.e Return PerformPromiseThen(promise, valueThunk, undefined, + // promiseCapability). + InternalPerformPromiseThen(context, promise, value_thunk, UndefinedConstant(), + promise_capability, UndefinedConstant(), + UndefinedConstant()); + Return(promise_capability); +} + +TF_BUILTIN(PromiseThrowerFinally, PromiseBuiltinsAssembler) { + Node* const context = Parameter(3); + + Node* const reason = LoadContextElement(context, kOnFinallySlot); + CallRuntime(Runtime::kThrow, context, reason); + Unreachable(); +} + +Node* PromiseBuiltinsAssembler::CreateThrowerFunctionContext( + Node* reason, Node* native_context) { + Node* const context = + CreatePromiseContext(native_context, kOnFinallyContextLength); + StoreContextElementNoWriteBarrier(context, kOnFinallySlot, reason); + return context; +} + +Node* PromiseBuiltinsAssembler::CreateThrowerFunction(Node* reason, + Node* native_context) { + Node* const thrower_context = + CreateThrowerFunctionContext(reason, native_context); + Node* const map = LoadContextElement( + native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX); + Node* const thrower_info = LoadContextElement( + native_context, Context::PROMISE_THROWER_FINALLY_SHARED_FUN); + Node* const thrower = + AllocateFunctionWithMapAndContext(map, thrower_info, thrower_context); + return thrower; +} + +TF_BUILTIN(PromiseCatchFinally, PromiseBuiltinsAssembler) { + CSA_ASSERT_JS_ARGC_EQ(this, 1); + + Node* const reason = Parameter(1); + Node* const context = Parameter(4); + + Node* const on_finally = LoadContextElement(context, kOnFinallySlot); + + // 2.a Let result be ? Call(onFinally, undefined). + Callable call_callable = CodeFactory::Call(isolate()); + Node* result = + CallJS(call_callable, context, on_finally, UndefinedConstant()); + + // 2.b Let promise be ! PromiseResolve( %Promise%, result). + Node* const promise = AllocateAndInitJSPromise(context); + InternalResolvePromise(context, promise, result); + + // 2.c Let thrower be equivalent to a function that throws reason. + Node* native_context = LoadNativeContext(context); + Node* const thrower = CreateThrowerFunction(reason, native_context); + + // 2.d Let promiseCapability be ! NewPromiseCapability( %Promise%). + Node* const promise_capability = AllocateAndInitJSPromise(context, promise); + + // 2.e Return PerformPromiseThen(promise, thrower, undefined, + // promiseCapability). + InternalPerformPromiseThen(context, promise, thrower, UndefinedConstant(), + promise_capability, UndefinedConstant(), + UndefinedConstant()); + Return(promise_capability); +} + +TF_BUILTIN(PromiseFinally, PromiseBuiltinsAssembler) { + CSA_ASSERT_JS_ARGC_EQ(this, 1); + + // 1. Let promise be the this value. + Node* const promise = Parameter(0); + Node* const on_finally = Parameter(1); + Node* const context = Parameter(4); + + // 2. If IsPromise(promise) is false, throw a TypeError exception. + ThrowIfNotInstanceType(context, promise, JS_PROMISE_TYPE, + "Promise.prototype.finally"); + + Variable var_then_finally(this, MachineRepresentation::kTagged), + var_catch_finally(this, MachineRepresentation::kTagged); + + Label if_notcallable(this, Label::kDeferred), perform_finally(this); + + // 3. Let thenFinally be ! CreateThenFinally(onFinally). + // 4. Let catchFinally be ! CreateCatchFinally(onFinally). + GotoIf(TaggedIsSmi(on_finally), &if_notcallable); + Node* const on_finally_map = LoadMap(on_finally); + GotoIfNot(IsCallableMap(on_finally_map), &if_notcallable); + + Node* const native_context = LoadNativeContext(context); + Node* then_finally = nullptr; + Node* catch_finally = nullptr; + std::tie(then_finally, catch_finally) = + CreatePromiseFinallyFunctions(on_finally, native_context); + var_then_finally.Bind(then_finally); + var_catch_finally.Bind(catch_finally); + Goto(&perform_finally); + + Bind(&if_notcallable); + { + var_then_finally.Bind(on_finally); + var_catch_finally.Bind(on_finally); + Goto(&perform_finally); + } + + // 5. Return PerformPromiseThen(promise, valueThunk, undefined, + // promiseCapability). + Bind(&perform_finally); + Label if_nativepromise(this), if_custompromise(this, Label::kDeferred); + BranchIfFastPath(context, promise, &if_nativepromise, &if_custompromise); + + Bind(&if_nativepromise); + { + Node* deferred_promise = AllocateAndInitJSPromise(context, promise); + InternalPerformPromiseThen(context, promise, var_then_finally.value(), + var_catch_finally.value(), deferred_promise, + UndefinedConstant(), UndefinedConstant()); + Return(deferred_promise); + } + + Bind(&if_custompromise); + { + Isolate* isolate = this->isolate(); + Node* const then_str = HeapConstant(isolate->factory()->then_string()); + Callable getproperty_callable = CodeFactory::GetProperty(isolate); + Node* const then = + CallStub(getproperty_callable, context, promise, then_str); + Callable call_callable = CodeFactory::Call(isolate); + // 5. Return ? Invoke(promise, "then", « thenFinally, catchFinally »). + Node* const result = + CallJS(call_callable, context, then, promise, var_then_finally.value(), + var_catch_finally.value()); + Return(result); + } } } // namespace internal |