| Index: src/builtins/builtins-promise.cc
|
| diff --git a/src/builtins/builtins-promise.cc b/src/builtins/builtins-promise.cc
|
| index bff20701d51a35aba738735f933d5fe276560f15..e3c4418f75c136f7c2f60362420b242b1db3e999 100644
|
| --- a/src/builtins/builtins-promise.cc
|
| +++ b/src/builtins/builtins-promise.cc
|
| @@ -81,6 +81,13 @@ BUILTIN(CreateResolvingFunctions) {
|
| NOT_TENURED);
|
| }
|
|
|
| +void PromiseInit(CodeStubAssembler* a, compiler::Node* promise,
|
| + compiler::Node* status, compiler::Node* result) {
|
| + CSA_ASSERT(a, a->TaggedIsSmi(status));
|
| + a->StoreObjectField(promise, JSPromise::kStatusOffset, status);
|
| + a->StoreObjectField(promise, JSPromise::kResultOffset, result);
|
| +}
|
| +
|
| void Builtins::Generate_PromiseConstructor(
|
| compiler::CodeAssemblerState* state) {
|
| CodeStubAssembler a(state);
|
| @@ -108,9 +115,9 @@ void Builtins::Generate_PromiseConstructor(
|
| Node* const promise_fun =
|
| a.LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
|
| Node* const is_debug_active = a.IsDebugActive();
|
| -
|
| Label if_targetisnotmodified(&a), if_targetismodified(&a, Label::kDeferred),
|
| - run_executor(&a), debug_push(&a, Label::kDeferred);
|
| + run_executor(&a), debug_push(&a, Label::kDeferred), init(&a);
|
| +
|
| a.Branch(a.WordEqual(promise_fun, new_target), &if_targetisnotmodified,
|
| &if_targetismodified);
|
|
|
| @@ -125,7 +132,7 @@ void Builtins::Generate_PromiseConstructor(
|
|
|
| Node* const instance = a.AllocateJSObjectFromMap(initial_map);
|
| var_result.Bind(instance);
|
| - a.Branch(is_debug_active, &debug_push, &run_executor);
|
| + a.Goto(&init);
|
| }
|
|
|
| a.Bind(&if_targetismodified);
|
| @@ -135,6 +142,13 @@ void Builtins::Generate_PromiseConstructor(
|
| a.CallStub(fast_new_object_stub, context, promise_fun, new_target);
|
|
|
| var_result.Bind(instance);
|
| + a.Goto(&init);
|
| + }
|
| +
|
| + a.Bind(&init);
|
| + {
|
| + PromiseInit(&a, var_result.value(), a.SmiConstant(kPromisePending),
|
| + a.UndefinedConstant());
|
| a.Branch(is_debug_active, &debug_push, &run_executor);
|
| }
|
|
|
| @@ -148,12 +162,7 @@ void Builtins::Generate_PromiseConstructor(
|
| {
|
| Label out(&a), if_rejectpromise(&a), debug_pop(&a, Label::kDeferred);
|
|
|
| - Node* const key = a.LoadRoot(Heap::kpromise_state_symbolRootIndex);
|
| - Node* const value = a.SmiConstant(kPromisePending);
|
| - Node* const language_mode = a.SmiConstant(STRICT);
|
| - // TODO(ishell): Use SetProperty stub once available.
|
| - a.CallRuntime(Runtime::kSetProperty, context, var_result.value(), key,
|
| - value, language_mode);
|
| + // TODO(gsathya): Move this to TF.
|
| Node* const resolving_functions = a.CallRuntime(
|
| Runtime::kCreateResolvingFunctions, context, var_result.value());
|
| Node* const resolve =
|
| @@ -216,26 +225,441 @@ void Builtins::Generate_PromiseInternalConstructor(
|
| Node* const initial_map =
|
| a.LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset);
|
| Node* const instance = a.AllocateJSObjectFromMap(initial_map);
|
| +
|
| + PromiseInit(&a, instance, a.SmiConstant(kPromisePending),
|
| + a.UndefinedConstant());
|
| a.Return(instance);
|
| }
|
|
|
| +void Builtins::Generate_PromiseCreateAndSet(
|
| + compiler::CodeAssemblerState* state) {
|
| + typedef compiler::Node Node;
|
| + CodeStubAssembler a(state);
|
| +
|
| + Node* const status = a.Parameter(1);
|
| + Node* const result = a.Parameter(2);
|
| + Node* const context = a.Parameter(5);
|
| + Node* const native_context = a.LoadNativeContext(context);
|
| +
|
| + Node* const promise_fun =
|
| + a.LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
|
| + Node* const initial_map =
|
| + a.LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset);
|
| + Node* const instance = a.AllocateJSObjectFromMap(initial_map);
|
| +
|
| + PromiseInit(&a, instance, status, result);
|
| + a.Return(instance);
|
| +}
|
| +
|
| +namespace {
|
| +
|
| +compiler::Node* ThrowIfNotJSReceiver(CodeStubAssembler* a, Isolate* isolate,
|
| + compiler::Node* context,
|
| + compiler::Node* value,
|
| + MessageTemplate::Template msg_template) {
|
| + typedef compiler::Node Node;
|
| + typedef CodeStubAssembler::Label Label;
|
| + typedef CodeStubAssembler::Variable Variable;
|
| +
|
| + Label out(a), throw_exception(a, Label::kDeferred);
|
| + Variable var_value_map(a, MachineRepresentation::kTagged);
|
| +
|
| + a->GotoIf(a->TaggedIsSmi(value), &throw_exception);
|
| +
|
| + // Load the instance type of the {value}.
|
| + var_value_map.Bind(a->LoadMap(value));
|
| + Node* const value_instance_type =
|
| + a->LoadMapInstanceType(var_value_map.value());
|
| +
|
| + a->Branch(a->IsJSReceiverInstanceType(value_instance_type), &out,
|
| + &throw_exception);
|
| +
|
| + // The {value} is not a compatible receiver for this method.
|
| + a->Bind(&throw_exception);
|
| + {
|
| + Node* const message_id = a->SmiConstant(msg_template);
|
| + a->CallRuntime(Runtime::kThrowTypeError, context, message_id);
|
| + var_value_map.Bind(a->UndefinedConstant());
|
| + a->Goto(&out); // Never reached.
|
| + }
|
| +
|
| + a->Bind(&out);
|
| + return var_value_map.value();
|
| +}
|
| +} // namespace
|
| +
|
| void Builtins::Generate_IsPromise(compiler::CodeAssemblerState* state) {
|
| CodeStubAssembler a(state);
|
| typedef compiler::Node Node;
|
| typedef CodeStubAssembler::Label Label;
|
|
|
| Node* const maybe_promise = a.Parameter(1);
|
| - Label if_ispromise(&a), if_isnotpromise(&a, Label::kDeferred);
|
| - a.GotoIf(a.TaggedIsSmi(maybe_promise), &if_isnotpromise);
|
| + Label if_notpromise(&a, Label::kDeferred);
|
| +
|
| + a.GotoIf(a.TaggedIsSmi(maybe_promise), &if_notpromise);
|
|
|
| - a.Branch(a.HasInstanceType(maybe_promise, JS_PROMISE_TYPE), &if_ispromise,
|
| - &if_isnotpromise);
|
| + Node* const result = a.SelectBooleanConstant(
|
| + a.HasInstanceType(maybe_promise, JS_PROMISE_TYPE));
|
| + a.Return(result);
|
|
|
| - a.Bind(&if_ispromise);
|
| - a.Return(a.BooleanConstant(true));
|
| + a.Bind(&if_notpromise);
|
| + a.Return(a.FalseConstant());
|
| +}
|
| +
|
| +namespace {
|
| +compiler::Node* SpeciesConstructor(CodeStubAssembler* a, Isolate* isolate,
|
| + compiler::Node* context,
|
| + compiler::Node* object,
|
| + compiler::Node* default_constructor) {
|
| + typedef compiler::Node Node;
|
| + typedef CodeStubAssembler::Label Label;
|
| + typedef CodeStubAssembler::Variable Variable;
|
| +
|
| + Variable var_result(a, MachineRepresentation::kTagged);
|
| + var_result.Bind(default_constructor);
|
| +
|
| + // 2. Let C be ? Get(O, "constructor").
|
| + Node* const constructor_str =
|
| + a->HeapConstant(isolate->factory()->constructor_string());
|
| + Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
|
| + Node* const constructor =
|
| + a->CallStub(getproperty_callable, context, object, constructor_str);
|
| +
|
| + // 3. If C is undefined, return defaultConstructor.
|
| + Label out(a);
|
| + a->GotoIf(a->IsUndefined(constructor), &out);
|
| +
|
| + // 4. If Type(C) is not Object, throw a TypeError exception.
|
| + ThrowIfNotJSReceiver(a, a->isolate(), context, constructor,
|
| + MessageTemplate::kConstructorNotReceiver);
|
| +
|
| + // 5. Let S be ? Get(C, @@species).
|
| + Node* const species_symbol =
|
| + a->HeapConstant(isolate->factory()->species_symbol());
|
| + Node* const species =
|
| + a->CallStub(getproperty_callable, context, constructor, species_symbol);
|
| +
|
| + // 6. If S is either undefined or null, return defaultConstructor.
|
| + a->GotoIf(a->IsUndefined(species), &out);
|
| + a->GotoIf(a->WordEqual(species, a->NullConstant()), &out);
|
| +
|
| + // 7. If IsConstructor(S) is true, return S.
|
| + Label throw_error(a);
|
| + Node* species_bitfield = a->LoadMapBitField(a->LoadMap(species));
|
| + a->GotoUnless(
|
| + a->Word32Equal(a->Word32And(species_bitfield,
|
| + a->Int32Constant((1 << Map::kIsConstructor))),
|
| + a->Int32Constant(1 << Map::kIsConstructor)),
|
| + &throw_error);
|
| + var_result.Bind(species);
|
| + a->Goto(&out);
|
| +
|
| + // 8. Throw a TypeError exception.
|
| + a->Bind(&throw_error);
|
| + {
|
| + Node* const message_id =
|
| + a->SmiConstant(MessageTemplate::kSpeciesNotConstructor);
|
| + a->CallRuntime(Runtime::kThrowTypeError, context, message_id);
|
| + a->Goto(&out);
|
| + }
|
| +
|
| + a->Bind(&out);
|
| + return var_result.value();
|
| +}
|
| +
|
| +void AppendPromiseCallback(CodeStubAssembler* a, int offset,
|
| + compiler::Node* promise, compiler::Node* value) {
|
| + typedef compiler::Node Node;
|
| +
|
| + Node* elements = a->LoadObjectField(promise, offset);
|
| + Node* length = a->LoadFixedArrayBaseLength(elements);
|
| + CodeStubAssembler::ParameterMode mode = a->OptimalParameterMode();
|
| + length = a->UntagParameter(length, mode);
|
| +
|
| + Node* delta = a->IntPtrOrSmiConstant(1, mode);
|
| + Node* new_capacity = a->IntPtrAdd(length, delta);
|
| + ElementsKind kind = FAST_ELEMENTS;
|
| +
|
| + Node* new_elements = a->AllocateFixedArray(kind, new_capacity, mode);
|
| +
|
| + a->CopyFixedArrayElements(kind, elements, new_elements, length,
|
| + UPDATE_WRITE_BARRIER, mode);
|
| + a->StoreFixedArrayElement(new_elements, length, value, UPDATE_WRITE_BARRIER,
|
| + 0, mode);
|
| +
|
| + a->StoreObjectField(promise, offset, new_elements);
|
| +}
|
| +
|
| +void InternalPerformPromiseThen(CodeStubAssembler* a, compiler::Node* context,
|
| + compiler::Node* promise,
|
| + compiler::Node* on_resolve,
|
| + compiler::Node* on_reject,
|
| + compiler::Node* deferred) {
|
| + typedef CodeStubAssembler::Variable Variable;
|
| + typedef CodeStubAssembler::Label Label;
|
| + typedef compiler::Node Node;
|
| +
|
| + Isolate* isolate = a->isolate();
|
| + Node* const native_context = a->LoadNativeContext(context);
|
| +
|
| + Variable var_on_resolve(a, MachineRepresentation::kTagged),
|
| + var_on_reject(a, MachineRepresentation::kTagged);
|
| +
|
| + var_on_resolve.Bind(on_resolve);
|
| + var_on_reject.Bind(on_reject);
|
| +
|
| + Label out(a), if_onresolvenotcallable(a), onrejectcheck(a),
|
| + append_callbacks(a);
|
| + a->GotoIf(a->TaggedIsSmi(on_resolve), &if_onresolvenotcallable);
|
| +
|
| + Node* const on_resolve_map = a->LoadMap(on_resolve);
|
| + a->Branch(a->IsCallableMap(on_resolve_map), &onrejectcheck,
|
| + &if_onresolvenotcallable);
|
| +
|
| + a->Bind(&if_onresolvenotcallable);
|
| + {
|
| + var_on_resolve.Bind(a->LoadContextElement(
|
| + native_context, Context::PROMISE_ID_RESOLVE_HANDLER_INDEX));
|
| + a->Goto(&onrejectcheck);
|
| + }
|
| +
|
| + a->Bind(&onrejectcheck);
|
| + {
|
| + Label if_onrejectnotcallable(a);
|
| + a->GotoIf(a->TaggedIsSmi(on_reject), &if_onrejectnotcallable);
|
| +
|
| + Node* const on_reject_map = a->LoadMap(on_reject);
|
| + a->Branch(a->IsCallableMap(on_reject_map), &append_callbacks,
|
| + &if_onrejectnotcallable);
|
| +
|
| + a->Bind(&if_onrejectnotcallable);
|
| + {
|
| + var_on_reject.Bind(a->LoadContextElement(
|
| + native_context, Context::PROMISE_ID_REJECT_HANDLER_INDEX));
|
| + a->Goto(&append_callbacks);
|
| + }
|
| + }
|
| +
|
| + a->Bind(&append_callbacks);
|
| + {
|
| + Label fulfilled_check(a);
|
| + Node* const status = a->LoadObjectField(promise, JSPromise::kStatusOffset);
|
| + a->GotoUnless(a->SmiEqual(status, a->SmiConstant(kPromisePending)),
|
| + &fulfilled_check);
|
| +
|
| + Node* const existing_deferred =
|
| + a->LoadObjectField(promise, JSPromise::kDeferredOffset);
|
| +
|
| + Label if_noexistingcallbacks(a), if_existingcallbacks(a);
|
| + a->Branch(a->IsUndefined(existing_deferred), &if_noexistingcallbacks,
|
| + &if_existingcallbacks);
|
| +
|
| + a->Bind(&if_noexistingcallbacks);
|
| + {
|
| + // Store callbacks directly in the slots.
|
| + a->StoreObjectField(promise, JSPromise::kDeferredOffset, deferred);
|
| + a->StoreObjectField(promise, JSPromise::kFulfillReactionsOffset,
|
| + var_on_resolve.value());
|
| + a->StoreObjectField(promise, JSPromise::kRejectReactionsOffset,
|
| + var_on_reject.value());
|
| + a->Goto(&out);
|
| + }
|
| +
|
| + a->Bind(&if_existingcallbacks);
|
| + {
|
| + Label if_singlecallback(a), if_multiplecallbacks(a);
|
| + a->BranchIfJSObject(existing_deferred, &if_singlecallback,
|
| + &if_multiplecallbacks);
|
| +
|
| + a->Bind(&if_singlecallback);
|
| + {
|
| + // Create new FixedArrays to store callbacks.
|
| + Node* const deferreds =
|
| + a->AllocateFixedArray(FAST_ELEMENTS, a->Int32Constant(2));
|
| + Node* const fulfill_reactions =
|
| + a->AllocateFixedArray(FAST_ELEMENTS, a->Int32Constant(2));
|
| + Node* const reject_reactions =
|
| + a->AllocateFixedArray(FAST_ELEMENTS, a->Int32Constant(2));
|
| +
|
| + // Store existing callbacks in FixedArrays.
|
| + a->StoreFixedArrayElement(deferreds, 0, existing_deferred);
|
| + a->StoreFixedArrayElement(
|
| + fulfill_reactions, 0,
|
| + a->LoadObjectField(promise, JSPromise::kFulfillReactionsOffset));
|
| + a->StoreFixedArrayElement(
|
| + reject_reactions, 0,
|
| + a->LoadObjectField(promise, JSPromise::kRejectReactionsOffset));
|
| +
|
| + // Store new callbacks in FixedArrays.
|
| + a->StoreFixedArrayElement(deferreds, 1, deferred);
|
| + a->StoreFixedArrayElement(fulfill_reactions, 1, var_on_resolve.value());
|
| + a->StoreFixedArrayElement(reject_reactions, 1, var_on_reject.value());
|
| +
|
| + // Store new FixedArrays in promise.
|
| + a->StoreObjectField(promise, JSPromise::kDeferredOffset, deferreds);
|
| + a->StoreObjectField(promise, JSPromise::kFulfillReactionsOffset,
|
| + fulfill_reactions);
|
| + a->StoreObjectField(promise, JSPromise::kRejectReactionsOffset,
|
| + reject_reactions);
|
| + a->Goto(&out);
|
| + }
|
| +
|
| + a->Bind(&if_multiplecallbacks);
|
| + {
|
| + AppendPromiseCallback(a, JSPromise::kDeferredOffset, promise, deferred);
|
| + AppendPromiseCallback(a, JSPromise::kFulfillReactionsOffset, promise,
|
| + var_on_resolve.value());
|
| + AppendPromiseCallback(a, JSPromise::kRejectReactionsOffset, promise,
|
| + var_on_reject.value());
|
| + a->Goto(&out);
|
| + }
|
| + }
|
| +
|
| + a->Bind(&fulfilled_check);
|
| + {
|
| + Label reject(a);
|
| + Node* const result =
|
| + a->LoadObjectField(promise, JSPromise::kResultOffset);
|
| + a->GotoUnless(a->WordEqual(status, a->SmiConstant(kPromiseFulfilled)),
|
| + &reject);
|
| +
|
| + // TODO(gsathya): Move this to TF.
|
| + a->CallRuntime(Runtime::kEnqueuePromiseReactionJob, context, result,
|
| + var_on_resolve.value(), deferred,
|
| + a->SmiConstant(kPromiseFulfilled));
|
| + a->Goto(&out);
|
| +
|
| + a->Bind(&reject);
|
| + {
|
| + Callable getproperty_callable = CodeFactory::GetProperty(isolate);
|
| + Node* const key =
|
| + a->HeapConstant(isolate->factory()->promise_has_handler_symbol());
|
| + Node* const has_handler =
|
| + a->CallStub(getproperty_callable, context, promise, key);
|
| +
|
| + Label enqueue(a);
|
| +
|
| + // TODO(gsathya): Fold these runtime calls and move to TF.
|
| + a->GotoIf(a->WordEqual(has_handler, a->TrueConstant()), &enqueue);
|
| + a->CallRuntime(Runtime::kPromiseRevokeReject, context, promise);
|
| + a->Goto(&enqueue);
|
| +
|
| + a->Bind(&enqueue);
|
| + {
|
| + a->CallRuntime(Runtime::kEnqueuePromiseReactionJob, context, result,
|
| + var_on_reject.value(), deferred,
|
| + a->SmiConstant(kPromiseRejected));
|
| +
|
| + a->Goto(&out);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + a->Bind(&out);
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +void Builtins::Generate_PerformPromiseThen(
|
| + compiler::CodeAssemblerState* state) {
|
| + CodeStubAssembler a(state);
|
| + typedef compiler::Node Node;
|
| +
|
| + Node* const promise = a.Parameter(1);
|
| + Node* const on_resolve = a.Parameter(2);
|
| + Node* const on_reject = a.Parameter(3);
|
| + Node* const deferred = a.Parameter(4);
|
| + Node* const context = a.Parameter(7);
|
| +
|
| + InternalPerformPromiseThen(&a, context, promise, on_resolve, on_reject,
|
| + deferred);
|
| +
|
| + // TODO(gsathya): This is unused, but value is returned according to spec.
|
| + a.Return(promise);
|
| +}
|
| +
|
| +void Builtins::Generate_PromiseThen(compiler::CodeAssemblerState* state) {
|
| + CodeStubAssembler a(state);
|
| + typedef compiler::Node Node;
|
| + typedef CodeStubAssembler::Label Label;
|
| + typedef CodeStubAssembler::Variable Variable;
|
| +
|
| + // 1. Let promise be the this value.
|
| + Node* const promise = a.Parameter(0);
|
| + Node* const on_resolve = a.Parameter(1);
|
| + Node* const on_reject = a.Parameter(2);
|
| + Node* const context = a.Parameter(5);
|
| + Isolate* isolate = a.isolate();
|
| +
|
| + // 2. If IsPromise(promise) is false, throw a TypeError exception.
|
| + a.ThrowIfNotInstanceType(context, promise, JS_PROMISE_TYPE,
|
| + "Promise.prototype.then");
|
| +
|
| + Node* const native_context = a.LoadNativeContext(context);
|
| + Node* const promise_fun =
|
| + a.LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
|
| +
|
| + // 3. Let C be ? SpeciesConstructor(promise, %Promise%).
|
| + Node* constructor =
|
| + SpeciesConstructor(&a, isolate, context, promise, promise_fun);
|
| +
|
| + // 4. Let resultCapability be ? NewPromiseCapability(C).
|
| + Callable call_callable = CodeFactory::Call(isolate);
|
| + Label fast_promise_capability(&a), promise_capability(&a),
|
| + perform_promise_then(&a);
|
| + Variable var_deferred(&a, MachineRepresentation::kTagged);
|
| +
|
| + a.Branch(a.WordEqual(promise_fun, constructor), &fast_promise_capability,
|
| + &promise_capability);
|
| +
|
| + // TODO(gsathya): Remove deferred object and move
|
| + // NewPromiseCapbability functions to TF.
|
| + a.Bind(&fast_promise_capability);
|
| + {
|
| + // TODO(gsathya): Move this to TF.
|
| + Node* const promise_internal_capability = a.LoadContextElement(
|
| + native_context, Context::INTERNAL_PROMISE_CAPABILITY_INDEX);
|
| + Node* const capability =
|
| + a.CallJS(call_callable, context, promise_internal_capability,
|
| + a.UndefinedConstant());
|
| + var_deferred.Bind(capability);
|
| + a.Goto(&perform_promise_then);
|
| + }
|
| +
|
| + a.Bind(&promise_capability);
|
| + {
|
| + // TODO(gsathya): Move this to TF.
|
| + Node* const new_promise_capability = a.LoadContextElement(
|
| + native_context, Context::NEW_PROMISE_CAPABILITY_INDEX);
|
| + Node* const capability =
|
| + a.CallJS(call_callable, context, new_promise_capability,
|
| + a.UndefinedConstant(), constructor);
|
| + var_deferred.Bind(capability);
|
| + a.Goto(&perform_promise_then);
|
| + }
|
|
|
| - a.Bind(&if_isnotpromise);
|
| - a.Return(a.BooleanConstant(false));
|
| + // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
|
| + // resultCapability).
|
| + a.Bind(&perform_promise_then);
|
| + InternalPerformPromiseThen(&a, context, promise, on_resolve, on_reject,
|
| + var_deferred.value());
|
| +
|
| + // TODO(gsathya): Protect with debug check.
|
| + a.CallRuntime(
|
| + Runtime::kSetProperty, context, promise,
|
| + a.HeapConstant(isolate->factory()->promise_has_handler_symbol()),
|
| + a.TrueConstant(), a.SmiConstant(STRICT));
|
| +
|
| + // TODO(gsathya): This call will be removed once we don't have to
|
| + // deal with deferred objects.
|
| + Callable getproperty_callable = CodeFactory::GetProperty(isolate);
|
| + Node* const key =
|
| + a.HeapConstant(isolate->factory()->NewStringFromAsciiChecked("promise"));
|
| + Node* const result =
|
| + a.CallStub(getproperty_callable, context, var_deferred.value(), key);
|
| +
|
| + a.Return(result);
|
| }
|
|
|
| } // namespace internal
|
|
|