| Index: src/builtins/builtins-promise.cc
|
| diff --git a/src/builtins/builtins-promise.cc b/src/builtins/builtins-promise.cc
|
| index b7a93a517f253422eeeaef9f0635e97b79141053..aa38002218e744fd87a221fe753230a78487f337 100644
|
| --- a/src/builtins/builtins-promise.cc
|
| +++ b/src/builtins/builtins-promise.cc
|
| @@ -11,30 +11,6 @@
|
| namespace v8 {
|
| namespace internal {
|
|
|
| -// ES#sec-promise-resolve-functions
|
| -// Promise Resolve Functions
|
| -BUILTIN(PromiseResolveClosure) {
|
| - HandleScope scope(isolate);
|
| -
|
| - Handle<Context> context(isolate->context(), isolate);
|
| -
|
| - if (PromiseUtils::HasAlreadyVisited(context)) {
|
| - return isolate->heap()->undefined_value();
|
| - }
|
| -
|
| - PromiseUtils::SetAlreadyVisited(context);
|
| - Handle<JSObject> promise = handle(PromiseUtils::GetPromise(context), isolate);
|
| - Handle<Object> value = args.atOrUndefined(isolate, 1);
|
| -
|
| - 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();
|
| -}
|
| -
|
| // ES#sec-promise-reject-functions
|
| // Promise Reject Functions
|
| BUILTIN(PromiseRejectClosure) {
|
| @@ -86,6 +62,7 @@ void PromiseInit(CodeStubAssembler* a, compiler::Node* promise,
|
| CSA_ASSERT(a, a->TaggedIsSmi(status));
|
| a->StoreObjectField(promise, JSPromise::kStatusOffset, status);
|
| a->StoreObjectField(promise, JSPromise::kResultOffset, result);
|
| + a->StoreObjectField(promise, JSPromise::kFlagsOffset, a->SmiConstant(0));
|
| }
|
|
|
| void Builtins::Generate_PromiseConstructor(
|
| @@ -286,6 +263,7 @@ compiler::Node* ThrowIfNotJSReceiver(CodeStubAssembler* a, Isolate* isolate,
|
| a->Bind(&out);
|
| return var_value_map.value();
|
| }
|
| +
|
| } // namespace
|
|
|
| void Builtins::Generate_IsPromise(compiler::CodeAssemblerState* state) {
|
| @@ -307,6 +285,24 @@ void Builtins::Generate_IsPromise(compiler::CodeAssemblerState* state) {
|
| }
|
|
|
| namespace {
|
| +
|
| +compiler::Node* PromiseHasHandler(CodeStubAssembler* a,
|
| + compiler::Node* promise) {
|
| + typedef compiler::Node Node;
|
| +
|
| + Node* const flags = a->LoadObjectField(promise, JSPromise::kFlagsOffset);
|
| + return a->IsSetWord(a->SmiUntag(flags), 1 << JSPromise::kHasHandlerBit);
|
| +}
|
| +
|
| +void PromiseSetHasHandler(CodeStubAssembler* a, compiler::Node* promise) {
|
| + typedef compiler::Node Node;
|
| +
|
| + Node* const flags = a->LoadObjectField(promise, JSPromise::kFlagsOffset);
|
| + Node* const new_flags =
|
| + a->WordOr(flags, a->IntPtrConstant(1 << JSPromise::kHasHandlerBit));
|
| + a->StoreObjectField(promise, JSPromise::kFlagsOffset, a->SmiTag(new_flags));
|
| +}
|
| +
|
| compiler::Node* SpeciesConstructor(CodeStubAssembler* a, Isolate* isolate,
|
| compiler::Node* context,
|
| compiler::Node* object,
|
| @@ -404,7 +400,6 @@ compiler::Node* InternalPerformPromiseThen(CodeStubAssembler* a,
|
| typedef CodeStubAssembler::Variable Variable;
|
| typedef CodeStubAssembler::Label Label;
|
| typedef compiler::Node Node;
|
| -
|
| Isolate* isolate = a->isolate();
|
| Node* const native_context = a->LoadNativeContext(context);
|
|
|
| @@ -536,16 +531,11 @@ compiler::Node* InternalPerformPromiseThen(CodeStubAssembler* a,
|
|
|
| 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);
|
| -
|
| + Node* const has_handler = PromiseHasHandler(a, promise);
|
| Label enqueue(a);
|
|
|
| // TODO(gsathya): Fold these runtime calls and move to TF.
|
| - a->GotoIf(a->WordEqual(has_handler, a->TrueConstant()), &enqueue);
|
| + a->GotoIf(has_handler, &enqueue);
|
| a->CallRuntime(Runtime::kPromiseRevokeReject, context, promise);
|
| a->Goto(&enqueue);
|
|
|
| @@ -562,11 +552,7 @@ compiler::Node* InternalPerformPromiseThen(CodeStubAssembler* a,
|
| }
|
|
|
| a->Bind(&out);
|
| - // 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));
|
| + PromiseSetHasHandler(a, promise);
|
|
|
| // TODO(gsathya): This call will be removed once we don't have to
|
| // deal with deferred objects.
|
| @@ -667,5 +653,244 @@ void Builtins::Generate_PromiseThen(compiler::CodeAssemblerState* state) {
|
| a.Return(result);
|
| }
|
|
|
| +namespace {
|
| +
|
| +// 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 BranchIfFastPath(CodeStubAssembler* a, compiler::Node* context,
|
| + compiler::Node* promise,
|
| + CodeStubAssembler::Label* if_isunmodified,
|
| + CodeStubAssembler::Label* if_ismodified) {
|
| + typedef compiler::Node Node;
|
| +
|
| + // TODO(gsathya): Assert if promise is receiver
|
| + Node* const map = a->LoadMap(promise);
|
| + 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 has_initialmap = a->WordEqual(map, initial_map);
|
| +
|
| + a->GotoUnless(has_initialmap, if_ismodified);
|
| +
|
| + Node* const initial_proto_initial_map = a->LoadContextElement(
|
| + native_context, Context::PROMISE_PROTOTYPE_MAP_INDEX);
|
| + Node* const proto_map = a->LoadMap(a->LoadMapPrototype(map));
|
| + Node* const proto_has_initialmap =
|
| + a->WordEqual(proto_map, initial_proto_initial_map);
|
| +
|
| + a->Branch(proto_has_initialmap, if_isunmodified, if_ismodified);
|
| +}
|
| +
|
| +void InternalResolvePromise(CodeStubAssembler* a, compiler::Node* context,
|
| + compiler::Node* promise, compiler::Node* result,
|
| + CodeStubAssembler::Label* out) {
|
| + typedef CodeStubAssembler::Variable Variable;
|
| + typedef CodeStubAssembler::Label Label;
|
| + typedef compiler::Node Node;
|
| +
|
| + Isolate* isolate = a->isolate();
|
| +
|
| + Variable var_reason(a, MachineRepresentation::kTagged),
|
| + var_then(a, MachineRepresentation::kTagged);
|
| +
|
| + Label do_enqueue(a), fulfill(a), if_cycle(a, Label::kDeferred),
|
| + if_rejectpromise(a, Label::kDeferred);
|
| +
|
| + // 6. If SameValue(resolution, promise) is true, then
|
| + a->GotoIf(a->SameValue(promise, result, context), &if_cycle);
|
| +
|
| + // 7. If Type(resolution) is not Object, then
|
| + a->GotoIf(a->TaggedIsSmi(result), &fulfill);
|
| + a->GotoUnless(a->IsJSReceiver(result), &fulfill);
|
| +
|
| + Label if_nativepromise(a), if_notnativepromise(a, Label::kDeferred);
|
| + BranchIfFastPath(a, context, 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.
|
| + a->Bind(&if_nativepromise);
|
| + {
|
| + Node* const thenable_status =
|
| + a->LoadObjectField(result, JSPromise::kStatusOffset);
|
| + Node* const thenable_value =
|
| + a->LoadObjectField(result, JSPromise::kResultOffset);
|
| +
|
| + Label if_isnotpending(a);
|
| + a->GotoUnless(a->SmiEqual(a->SmiConstant(kPromisePending), 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 native_context = a->LoadNativeContext(context);
|
| + Node* const then =
|
| + a->LoadContextElement(native_context, Context::PROMISE_THEN_INDEX);
|
| + var_then.Bind(then);
|
| + a->Goto(&do_enqueue);
|
| +
|
| + a->Bind(&if_isnotpending);
|
| + {
|
| + Label if_fulfilled(a), if_rejected(a);
|
| + a->Branch(a->SmiEqual(a->SmiConstant(kPromiseFulfilled), thenable_status),
|
| + &if_fulfilled, &if_rejected);
|
| +
|
| + a->Bind(&if_fulfilled);
|
| + {
|
| + a->CallRuntime(Runtime::kPromiseFulfill, context, promise,
|
| + a->SmiConstant(kPromiseFulfilled), thenable_value);
|
| + PromiseSetHasHandler(a, promise);
|
| + a->Goto(out);
|
| + }
|
| +
|
| + a->Bind(&if_rejected);
|
| + {
|
| + Label reject(a);
|
| + Node* const has_handler = PromiseHasHandler(a, result);
|
| +
|
| + // Promise has already been rejected, but had no handler.
|
| + // Revoke previously triggered reject event.
|
| + a->GotoIf(has_handler, &reject);
|
| + a->CallRuntime(Runtime::kPromiseRevokeReject, context, result);
|
| + a->Goto(&reject);
|
| +
|
| + a->Bind(&reject);
|
| + // Don't cause a debug event as this case is forwarding a rejection
|
| + a->CallRuntime(Runtime::kPromiseReject, context, promise,
|
| + thenable_value, a->FalseConstant());
|
| + PromiseSetHasHandler(a, result);
|
| + a->Goto(out);
|
| + }
|
| + }
|
| + }
|
| +
|
| + a->Bind(&if_notnativepromise);
|
| + {
|
| + // 8. Let then be Get(resolution, "then").
|
| + Node* const then_str = a->HeapConstant(isolate->factory()->then_string());
|
| + Callable getproperty_callable = CodeFactory::GetProperty(a->isolate());
|
| + Node* const then =
|
| + a->CallStub(getproperty_callable, context, result, then_str);
|
| +
|
| + // 9. If then is an abrupt completion, then
|
| + a->GotoIfException(then, &if_rejectpromise, &var_reason);
|
| +
|
| + // 11. If IsCallable(thenAction) is false, then
|
| + a->GotoIf(a->TaggedIsSmi(then), &fulfill);
|
| + Node* const then_map = a->LoadMap(then);
|
| + a->GotoUnless(a->IsCallableMap(then_map), &fulfill);
|
| + var_then.Bind(then);
|
| + a->Goto(&do_enqueue);
|
| + }
|
| +
|
| + a->Bind(&do_enqueue);
|
| + {
|
| + Label enqueue(a);
|
| + a->GotoUnless(a->IsDebugActive(), &enqueue);
|
| + a->GotoIf(a->TaggedIsSmi(result), &enqueue);
|
| + a->GotoUnless(a->HasInstanceType(result, JS_PROMISE_TYPE), &enqueue);
|
| + // Mark the dependency of the new promise on the resolution
|
| + Node* const key =
|
| + a->HeapConstant(isolate->factory()->promise_handled_by_symbol());
|
| + a->CallRuntime(Runtime::kSetProperty, context, result, key, promise,
|
| + a->SmiConstant(STRICT));
|
| + a->Goto(&enqueue);
|
| +
|
| + // 12. Perform EnqueueJob("PromiseJobs",
|
| + // PromiseResolveThenableJob, « promise, resolution, thenAction
|
| + // »).
|
| + a->Bind(&enqueue);
|
| + a->CallRuntime(Runtime::kEnqueuePromiseResolveThenableJob, context, promise,
|
| + result, var_then.value());
|
| + a->Goto(out);
|
| + }
|
| + // 7.b Return FulfillPromise(promise, resolution).
|
| + a->Bind(&fulfill);
|
| + {
|
| + a->CallRuntime(Runtime::kPromiseFulfill, context, promise,
|
| + a->SmiConstant(kPromiseFulfilled), result);
|
| + a->Goto(out);
|
| + }
|
| +
|
| + a->Bind(&if_cycle);
|
| + {
|
| + // 6.a Let selfResolutionError be a newly created TypeError object.
|
| + Node* const message_id = a->SmiConstant(MessageTemplate::kPromiseCyclic);
|
| + Node* const error =
|
| + a->CallRuntime(Runtime::kNewTypeError, context, message_id, result);
|
| + var_reason.Bind(error);
|
| +
|
| + // 6.b Return RejectPromise(promise, selfResolutionError).
|
| + a->Goto(&if_rejectpromise);
|
| + }
|
| +
|
| + // 9.a Return RejectPromise(promise, then.[[Value]]).
|
| + a->Bind(&if_rejectpromise);
|
| + {
|
| + a->CallRuntime(Runtime::kPromiseReject, context, promise,
|
| + var_reason.value(), a->TrueConstant());
|
| + a->Goto(out);
|
| + }
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +// ES#sec-promise-resolve-functions
|
| +// Promise Resolve Functions
|
| +void Builtins::Generate_PromiseResolveClosure(
|
| + compiler::CodeAssemblerState* state) {
|
| + CodeStubAssembler a(state);
|
| + typedef compiler::Node Node;
|
| + typedef CodeStubAssembler::Label Label;
|
| +
|
| + Node* const value = a.Parameter(1);
|
| + Node* const context = a.Parameter(4);
|
| +
|
| + Label out(&a);
|
| +
|
| + // 3. Let alreadyResolved be F.[[AlreadyResolved]].
|
| + Node* const has_already_visited_slot =
|
| + a.IntPtrConstant(PromiseUtils::kAlreadyVisitedSlot);
|
| +
|
| + Node* const has_already_visited =
|
| + a.LoadFixedArrayElement(context, has_already_visited_slot);
|
| +
|
| + // 4. If alreadyResolved.[[Value]] is true, return undefined.
|
| + a.GotoIf(a.SmiEqual(has_already_visited, a.SmiConstant(1)), &out);
|
| +
|
| + // 5.Set alreadyResolved.[[Value]] to true.
|
| + a.StoreFixedArrayElement(context, has_already_visited_slot, a.SmiConstant(1));
|
| +
|
| + // 2. Let promise be F.[[Promise]].
|
| + Node* const promise = a.LoadFixedArrayElement(
|
| + context, a.IntPtrConstant(PromiseUtils::kPromiseSlot));
|
| +
|
| + InternalResolvePromise(&a, context, promise, value, &out);
|
| +
|
| + a.Bind(&out);
|
| + a.Return(a.UndefinedConstant());
|
| +}
|
| +
|
| +void Builtins::Generate_ResolvePromise(compiler::CodeAssemblerState* state) {
|
| + CodeStubAssembler a(state);
|
| + typedef compiler::Node Node;
|
| + typedef CodeStubAssembler::Label Label;
|
| +
|
| + Node* const promise = a.Parameter(1);
|
| + Node* const result = a.Parameter(2);
|
| + Node* const context = a.Parameter(5);
|
| +
|
| + Label out(&a);
|
| + InternalResolvePromise(&a, context, promise, result, &out);
|
| +
|
| + a.Bind(&out);
|
| + a.Return(a.UndefinedConstant());
|
| +}
|
| +
|
| } // namespace internal
|
| } // namespace v8
|
|
|