// 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-promise.h" #include "src/builtins/builtins-constructor.h" #include "src/builtins/builtins-utils.h" #include "src/builtins/builtins.h" #include "src/code-factory.h" #include "src/code-stub-assembler.h" #include "src/objects-inl.h" namespace v8 { namespace internal { 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); 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 (debug_event) { GotoIfNot(IsDebugActive(), &run_promise_hook); CallRuntime(Runtime::kDebugPromiseReject, context, promise, value); Goto(&run_promise_hook); } else { Goto(&run_promise_hook); } Bind(&run_promise_hook); { GotoIfNot(IsPromiseHookEnabledOrDebugIsActive(), &report_unhandledpromise); CallRuntime(Runtime::kPromiseHookResolve, context, promise); Goto(&report_unhandledpromise); } 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 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); Label if_internalthen(this), if_customthen(this, Label::kDeferred); GotoIf(TaggedIsSmi(promise), &if_customthen); BranchIfFastPath(context, promise, &if_internalthen, &if_customthen); Bind(&if_internalthen); { Node* const result = InternalPromiseThen(context, promise, on_resolve, on_reject); Return(result); } 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); } } 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); // 3.If IsPromise(x) is true, then GotoIf(TaggedIsSmi(value), &if_valueisnotpromise); // 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); } } } TF_BUILTIN(PromiseGetCapabilitiesExecutor, PromiseBuiltinsAssembler) { Node* const resolve = Parameter(1); Node* const reject = Parameter(2); Node* const context = Parameter(5); 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 } // namespace v8