From 52e2c154ff18ae0e7ca807a5ad539750297c31b3 Mon Sep 17 00:00:00 2001 From: gsathya Date: Tue, 6 Dec 2016 10:42:49 -0800 Subject: [PATCH] Reland Create JSPromise (patchset #16 id:300001 of https://codereview.chromium.org/2536463002/ )" This reverts commit 4c7cccf9f40c063e2d6410fc35664a0efd1acb46. BUG=v8:5343 Review-Url: https://codereview.chromium.org/2554943002 Cr-Commit-Position: refs/heads/master@{#41534} --- src/bootstrapper.cc | 25 +- src/builtins/builtins-promise.cc | 459 +++++++++++++++++++- src/builtins/builtins.h | 3 + src/contexts.h | 113 ++--- src/debug/mirrors.js | 6 +- src/heap-symbols.h | 6 - src/isolate.cc | 25 +- src/js/async-await.js | 5 +- src/js/promise.js | 169 +------ src/objects-debug.cc | 21 +- src/objects-inl.h | 9 +- src/objects-printer.cc | 12 +- src/objects.cc | 13 + src/objects.h | 35 ++ src/runtime/runtime-debug.cc | 31 +- src/runtime/runtime-promise.cc | 104 ++++- src/runtime/runtime.h | 6 +- test/cctest/test-inobject-slack-tracking.cc | 4 +- 18 files changed, 741 insertions(+), 305 deletions(-) diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 314601e435..0242f56712 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1826,9 +1826,9 @@ void Genesis::InitializeGlobal(Handle global_object, Handle prototype = factory->NewJSObject(isolate->object_function(), TENURED); - Handle promise_fun = InstallFunction( - global, "Promise", JS_PROMISE_TYPE, JSObject::kHeaderSize, prototype, - Builtins::kPromiseConstructor); + Handle promise_fun = + InstallFunction(global, "Promise", JS_PROMISE_TYPE, JSPromise::kSize, + prototype, Builtins::kPromiseConstructor); InstallWithIntrinsicDefaultProto(isolate, promise_fun, Context::PROMISE_FUNCTION_INDEX); @@ -1847,6 +1847,9 @@ void Genesis::InitializeGlobal(Handle global_object, prototype, factory->to_string_tag_symbol(), factory->Promise_string(), static_cast(DONT_ENUM | READ_ONLY)); + SimpleInstallFunction(prototype, "then", Builtins::kPromiseThen, 2, true, + DONT_ENUM); + { // Internal: PromiseInternalConstructor Handle function = SimpleCreateFunction(isolate, factory->empty_string(), @@ -1855,6 +1858,14 @@ void Genesis::InitializeGlobal(Handle global_object, isolate, function, Context::PROMISE_INTERNAL_CONSTRUCTOR_INDEX); } + { // Internal: PromiseCreateAndSet + Handle function = + SimpleCreateFunction(isolate, factory->empty_string(), + Builtins::kPromiseCreateAndSet, 2, false); + InstallWithIntrinsicDefaultProto(isolate, function, + Context::PROMISE_CREATE_AND_SET_INDEX); + } + { // Internal: IsPromise Handle function = SimpleCreateFunction( isolate, factory->empty_string(), Builtins::kIsPromise, 1, false); @@ -1862,6 +1873,14 @@ void Genesis::InitializeGlobal(Handle global_object, Context::IS_PROMISE_INDEX); } + { // Internal: PerformPromiseThen + Handle function = + SimpleCreateFunction(isolate, factory->empty_string(), + Builtins::kPerformPromiseThen, 4, false); + InstallWithIntrinsicDefaultProto(isolate, function, + Context::PERFORM_PROMISE_THEN_INDEX); + } + { Handle code = handle(isolate->builtins()->builtin(Builtins::kPromiseResolveClosure), diff --git a/src/builtins/builtins-promise.cc b/src/builtins/builtins-promise.cc index 1a5fa96ad1..70f8c056ab 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,442 @@ 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.Branch(a.HasInstanceType(maybe_promise, JS_PROMISE_TYPE), &if_ispromise, - &if_isnotpromise); + a.GotoIf(a.TaggedIsSmi(maybe_promise), &if_notpromise); - a.Bind(&if_ispromise); - a.Return(a.BooleanConstant(true)); + Node* const result = a.SelectBooleanConstant( + a.HasInstanceType(maybe_promise, JS_PROMISE_TYPE)); + a.Return(result); - a.Bind(&if_isnotpromise); - a.Return(a.BooleanConstant(false)); + 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); +} + +compiler::Node* 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); + // 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, deferred, key); + + return result; +} + +} // 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); + + Node* const result = InternalPerformPromiseThen( + &a, context, promise, on_resolve, on_reject, deferred); + + // TODO(gsathya): This is unused, but value is returned according to spec. + a.Return(result); +} + +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); + } + + // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, + // resultCapability). + a.Bind(&perform_promise_then); + Node* const result = InternalPerformPromiseThen( + &a, context, promise, on_resolve, on_reject, var_deferred.value()); + a.Return(result); } } // namespace internal diff --git a/src/builtins/builtins.h b/src/builtins/builtins.h index 142951b18a..c220424373 100644 --- a/src/builtins/builtins.h +++ b/src/builtins/builtins.h @@ -567,6 +567,9 @@ namespace internal { CPP(CreateResolvingFunctions) \ CPP(PromiseResolveClosure) \ CPP(PromiseRejectClosure) \ + TFJ(PromiseThen, 2) \ + TFJ(PromiseCreateAndSet, 2) \ + TFJ(PerformPromiseThen, 4) \ \ /* Proxy */ \ CPP(ProxyConstructor) \ diff --git a/src/contexts.h b/src/contexts.h index cc9b64b96e..8534f2014f 100644 --- a/src/contexts.h +++ b/src/contexts.h @@ -63,60 +63,67 @@ enum ContextLookupFlags { V(CREATE_RESOLVING_FUNCTION_INDEX, JSFunction, create_resolving_functions) \ V(PROMISE_INTERNAL_CONSTRUCTOR_INDEX, JSFunction, \ promise_internal_constructor) \ - V(IS_PROMISE_INDEX, JSFunction, is_promise) + V(IS_PROMISE_INDEX, JSFunction, is_promise) \ + V(PERFORM_PROMISE_THEN_INDEX, JSFunction, perform_promise_then) \ + V(PROMISE_CREATE_AND_SET_INDEX, JSFunction, promise_create_and_set) -#define NATIVE_CONTEXT_IMPORTED_FIELDS(V) \ - V(ARRAY_CONCAT_INDEX, JSFunction, array_concat) \ - V(ARRAY_POP_INDEX, JSFunction, array_pop) \ - V(ARRAY_PUSH_INDEX, JSFunction, array_push) \ - V(ARRAY_SHIFT_INDEX, JSFunction, array_shift) \ - V(ARRAY_SPLICE_INDEX, JSFunction, array_splice) \ - V(ARRAY_SLICE_INDEX, JSFunction, array_slice) \ - V(ARRAY_UNSHIFT_INDEX, JSFunction, array_unshift) \ - V(ARRAY_VALUES_ITERATOR_INDEX, JSFunction, array_values_iterator) \ - V(ASYNC_FUNCTION_AWAIT_CAUGHT_INDEX, JSFunction, \ - async_function_await_caught) \ - V(ASYNC_FUNCTION_AWAIT_UNCAUGHT_INDEX, JSFunction, \ - async_function_await_uncaught) \ - V(ASYNC_FUNCTION_PROMISE_CREATE_INDEX, JSFunction, \ - async_function_promise_create) \ - V(ASYNC_FUNCTION_PROMISE_RELEASE_INDEX, JSFunction, \ - async_function_promise_release) \ - V(DERIVED_GET_TRAP_INDEX, JSFunction, derived_get_trap) \ - V(ERROR_FUNCTION_INDEX, JSFunction, error_function) \ - V(ERROR_TO_STRING, JSFunction, error_to_string) \ - V(EVAL_ERROR_FUNCTION_INDEX, JSFunction, eval_error_function) \ - V(FUNCTION_HAS_INSTANCE_INDEX, JSFunction, function_has_instance) \ - V(GLOBAL_EVAL_FUN_INDEX, JSFunction, global_eval_fun) \ - V(MAP_DELETE_METHOD_INDEX, JSFunction, map_delete) \ - V(MAP_GET_METHOD_INDEX, JSFunction, map_get) \ - V(MAP_HAS_METHOD_INDEX, JSFunction, map_has) \ - V(MAP_SET_METHOD_INDEX, JSFunction, map_set) \ - V(OBJECT_VALUE_OF, JSFunction, object_value_of) \ - V(OBJECT_TO_STRING, JSFunction, object_to_string) \ - V(PROMISE_CATCH_INDEX, JSFunction, promise_catch) \ - V(PROMISE_CREATE_INDEX, JSFunction, promise_create) \ - V(PROMISE_FUNCTION_INDEX, JSFunction, promise_function) \ - V(PROMISE_HANDLE_INDEX, JSFunction, promise_handle) \ - V(PROMISE_HAS_USER_DEFINED_REJECT_HANDLER_INDEX, JSFunction, \ - promise_has_user_defined_reject_handler) \ - V(PROMISE_DEBUG_GET_INFO_INDEX, JSFunction, promise_debug_get_info) \ - V(PROMISE_REJECT_INDEX, JSFunction, promise_reject) \ - V(PROMISE_INTERNAL_REJECT_INDEX, JSFunction, promise_internal_reject) \ - V(PROMISE_RESOLVE_INDEX, JSFunction, promise_resolve) \ - V(PROMISE_THEN_INDEX, JSFunction, promise_then) \ - V(RANGE_ERROR_FUNCTION_INDEX, JSFunction, range_error_function) \ - V(REJECT_PROMISE_NO_DEBUG_EVENT_INDEX, JSFunction, \ - reject_promise_no_debug_event) \ - V(REFERENCE_ERROR_FUNCTION_INDEX, JSFunction, reference_error_function) \ - V(SET_ADD_METHOD_INDEX, JSFunction, set_add) \ - V(SET_DELETE_METHOD_INDEX, JSFunction, set_delete) \ - V(SET_HAS_METHOD_INDEX, JSFunction, set_has) \ - V(SYNTAX_ERROR_FUNCTION_INDEX, JSFunction, syntax_error_function) \ - V(TYPE_ERROR_FUNCTION_INDEX, JSFunction, type_error_function) \ - V(URI_ERROR_FUNCTION_INDEX, JSFunction, uri_error_function) \ - V(WASM_COMPILE_ERROR_FUNCTION_INDEX, JSFunction, \ - wasm_compile_error_function) \ +#define NATIVE_CONTEXT_IMPORTED_FIELDS(V) \ + V(ARRAY_CONCAT_INDEX, JSFunction, array_concat) \ + V(ARRAY_POP_INDEX, JSFunction, array_pop) \ + V(ARRAY_PUSH_INDEX, JSFunction, array_push) \ + V(ARRAY_SHIFT_INDEX, JSFunction, array_shift) \ + V(ARRAY_SPLICE_INDEX, JSFunction, array_splice) \ + V(ARRAY_SLICE_INDEX, JSFunction, array_slice) \ + V(ARRAY_UNSHIFT_INDEX, JSFunction, array_unshift) \ + V(ARRAY_VALUES_ITERATOR_INDEX, JSFunction, array_values_iterator) \ + V(ASYNC_FUNCTION_AWAIT_CAUGHT_INDEX, JSFunction, \ + async_function_await_caught) \ + V(ASYNC_FUNCTION_AWAIT_UNCAUGHT_INDEX, JSFunction, \ + async_function_await_uncaught) \ + V(ASYNC_FUNCTION_PROMISE_CREATE_INDEX, JSFunction, \ + async_function_promise_create) \ + V(ASYNC_FUNCTION_PROMISE_RELEASE_INDEX, JSFunction, \ + async_function_promise_release) \ + V(DERIVED_GET_TRAP_INDEX, JSFunction, derived_get_trap) \ + V(ERROR_FUNCTION_INDEX, JSFunction, error_function) \ + V(ERROR_TO_STRING, JSFunction, error_to_string) \ + V(EVAL_ERROR_FUNCTION_INDEX, JSFunction, eval_error_function) \ + V(GLOBAL_EVAL_FUN_INDEX, JSFunction, global_eval_fun) \ + V(MAP_DELETE_METHOD_INDEX, JSFunction, map_delete) \ + V(MAP_GET_METHOD_INDEX, JSFunction, map_get) \ + V(MAP_HAS_METHOD_INDEX, JSFunction, map_has) \ + V(MAP_SET_METHOD_INDEX, JSFunction, map_set) \ + V(FUNCTION_HAS_INSTANCE_INDEX, JSFunction, function_has_instance) \ + V(OBJECT_VALUE_OF, JSFunction, object_value_of) \ + V(OBJECT_TO_STRING, JSFunction, object_to_string) \ + V(PROMISE_CATCH_INDEX, JSFunction, promise_catch) \ + V(PROMISE_CREATE_INDEX, JSFunction, promise_create) \ + V(PROMISE_FUNCTION_INDEX, JSFunction, promise_function) \ + V(PROMISE_HANDLE_INDEX, JSFunction, promise_handle) \ + V(PROMISE_HAS_USER_DEFINED_REJECT_HANDLER_INDEX, JSFunction, \ + promise_has_user_defined_reject_handler) \ + V(PROMISE_DEBUG_GET_INFO_INDEX, JSFunction, promise_debug_get_info) \ + V(PROMISE_REJECT_INDEX, JSFunction, promise_reject) \ + V(PROMISE_INTERNAL_REJECT_INDEX, JSFunction, promise_internal_reject) \ + V(PROMISE_RESOLVE_INDEX, JSFunction, promise_resolve) \ + V(PROMISE_ID_RESOLVE_HANDLER_INDEX, JSFunction, promise_id_resolve_handler) \ + V(PROMISE_ID_REJECT_HANDLER_INDEX, JSFunction, promise_id_reject_handler) \ + V(PROMISE_THEN_INDEX, JSFunction, promise_then) \ + V(NEW_PROMISE_CAPABILITY_INDEX, JSFunction, new_promise_capability) \ + V(INTERNAL_PROMISE_CAPABILITY_INDEX, JSFunction, \ + internal_promise_capability) \ + V(RANGE_ERROR_FUNCTION_INDEX, JSFunction, range_error_function) \ + V(REJECT_PROMISE_NO_DEBUG_EVENT_INDEX, JSFunction, \ + reject_promise_no_debug_event) \ + V(REFERENCE_ERROR_FUNCTION_INDEX, JSFunction, reference_error_function) \ + V(SET_ADD_METHOD_INDEX, JSFunction, set_add) \ + V(SET_DELETE_METHOD_INDEX, JSFunction, set_delete) \ + V(SET_HAS_METHOD_INDEX, JSFunction, set_has) \ + V(SYNTAX_ERROR_FUNCTION_INDEX, JSFunction, syntax_error_function) \ + V(TYPE_ERROR_FUNCTION_INDEX, JSFunction, type_error_function) \ + V(URI_ERROR_FUNCTION_INDEX, JSFunction, uri_error_function) \ + V(WASM_COMPILE_ERROR_FUNCTION_INDEX, JSFunction, \ + wasm_compile_error_function) \ V(WASM_RUNTIME_ERROR_FUNCTION_INDEX, JSFunction, wasm_runtime_error_function) #define NATIVE_CONTEXT_JS_ARRAY_ITERATOR_MAPS(V) \ diff --git a/src/debug/mirrors.js b/src/debug/mirrors.js index edc6af5e58..689235b151 100644 --- a/src/debug/mirrors.js +++ b/src/debug/mirrors.js @@ -13,8 +13,6 @@ var IsNaN = global.isNaN; var JSONStringify = global.JSON.stringify; var MapEntries; var MapIteratorNext; -var promiseStateSymbol = utils.ImportNow("promise_state_symbol"); -var promiseResultSymbol = utils.ImportNow("promise_result_symbol"); var SetIteratorNext; var SetValues; @@ -1267,7 +1265,7 @@ inherits(PromiseMirror, ObjectMirror); function PromiseGetStatus_(value) { - var status = %DebugGetProperty(value, promiseStateSymbol); + var status = %PromiseStatus(value); if (status == 0) return "pending"; if (status == 1) return "resolved"; return "rejected"; @@ -1275,7 +1273,7 @@ function PromiseGetStatus_(value) { function PromiseGetValue_(value) { - return %DebugGetProperty(value, promiseResultSymbol); + return %PromiseResult(value); } diff --git a/src/heap-symbols.h b/src/heap-symbols.h index 5fbc964e90..0eb234a07b 100644 --- a/src/heap-symbols.h +++ b/src/heap-symbols.h @@ -210,16 +210,10 @@ V(premonomorphic_symbol) \ V(promise_async_stack_id_symbol) \ V(promise_debug_marker_symbol) \ - V(promise_deferred_reaction_symbol) \ V(promise_forwarding_handler_symbol) \ - V(promise_fulfill_reactions_symbol) \ V(promise_handled_by_symbol) \ V(promise_handled_hint_symbol) \ V(promise_has_handler_symbol) \ - V(promise_raw_symbol) \ - V(promise_reject_reactions_symbol) \ - V(promise_result_symbol) \ - V(promise_state_symbol) \ V(sealed_symbol) \ V(stack_trace_symbol) \ V(strict_function_transition_symbol) \ diff --git a/src/isolate.cc b/src/isolate.cc index 77e8c6a02a..14b9df7b10 100644 --- a/src/isolate.cc +++ b/src/isolate.cc @@ -3182,23 +3182,15 @@ void Isolate::PromiseReactionJob(Handle info, Handle tasks(info->tasks(), this); Handle promise_handle_fn = promise_handle(); Handle undefined = factory()->undefined_value(); + Handle deferred(info->deferred(), this); - // If tasks is an array we have multiple onFulfilled/onRejected callbacks - // associated with the promise. The deferred object for each callback - // is attached to this array as well. - // Otherwise, there is a single callback and the deferred object is attached - // directly to PromiseReactionJobInfo. - if (tasks->IsJSArray()) { - Handle array = Handle::cast(tasks); - DCHECK(array->length()->IsSmi()); - int length = Smi::cast(array->length())->value(); - ElementsAccessor* accessor = array->GetElementsAccessor(); - DCHECK(length % 2 == 0); - for (int i = 0; i < length; i += 2) { - DCHECK(accessor->HasElement(array, i)); - DCHECK(accessor->HasElement(array, i + 1)); - Handle argv[] = {value, accessor->Get(array, i), - accessor->Get(array, i + 1)}; + if (deferred->IsFixedArray()) { + DCHECK(tasks->IsFixedArray()); + Handle deferred_arr = Handle::cast(deferred); + Handle tasks_arr = Handle::cast(tasks); + for (int i = 0; i < deferred_arr->length(); i++) { + Handle argv[] = {value, handle(tasks_arr->get(i), this), + handle(deferred_arr->get(i), this)}; *result = Execution::TryCall(this, promise_handle_fn, undefined, arraysize(argv), argv, maybe_exception); // If execution is terminating, just bail out. @@ -3207,7 +3199,6 @@ void Isolate::PromiseReactionJob(Handle info, } } } else { - Handle deferred(info->deferred(), this); Handle argv[] = {value, tasks, deferred}; *result = Execution::TryCall(this, promise_handle_fn, undefined, arraysize(argv), argv, maybe_exception); diff --git a/src/js/async-await.js b/src/js/async-await.js index 221fdb881a..f05feb2936 100644 --- a/src/js/async-await.js +++ b/src/js/async-await.js @@ -14,7 +14,6 @@ var AsyncFunctionNext; var AsyncFunctionThrow; var CreateInternalPromiseCapability; -var PerformPromiseThen; var PromiseCreate; var PromiseNextMicrotaskID; var RejectPromise; @@ -24,7 +23,6 @@ utils.Import(function(from) { AsyncFunctionNext = from.AsyncFunctionNext; AsyncFunctionThrow = from.AsyncFunctionThrow; CreateInternalPromiseCapability = from.CreateInternalPromiseCapability; - PerformPromiseThen = from.PerformPromiseThen; PromiseCreate = from.PromiseCreate; RejectPromise = from.RejectPromise; ResolvePromise = from.ResolvePromise; @@ -44,6 +42,7 @@ var promiseHasHandlerSymbol = // ------------------------------------------------------------------- function PromiseCastResolved(value) { + // TODO(caitp): This is non spec compliant. See v8:5694. if (%is_promise(value)) { return value; } else { @@ -106,7 +105,7 @@ function AsyncFunctionAwait(generator, awaited, outerPromise) { outerPromise); } - PerformPromiseThen(promise, onFulfilled, onRejected, throwawayCapability); + %perform_promise_then(promise, onFulfilled, onRejected, throwawayCapability); } // Called by the parser from the desugaring of 'await' when catch diff --git a/src/js/promise.js b/src/js/promise.js index 25c8beaa9e..b78f68c4f7 100644 --- a/src/js/promise.js +++ b/src/js/promise.js @@ -20,12 +20,6 @@ var promiseForwardingHandlerSymbol = utils.ImportNow("promise_forwarding_handler_symbol"); var promiseHasHandlerSymbol = utils.ImportNow("promise_has_handler_symbol"); -var promiseRejectReactionsSymbol = - utils.ImportNow("promise_reject_reactions_symbol"); -var promiseFulfillReactionsSymbol = - utils.ImportNow("promise_fulfill_reactions_symbol"); -var promiseDeferredReactionSymbol = - utils.ImportNow("promise_deferred_reaction_symbol"); var promiseHandledHintSymbol = utils.ImportNow("promise_handled_hint_symbol"); var promiseRawSymbol = utils.ImportNow("promise_raw_symbol"); @@ -36,6 +30,7 @@ var speciesSymbol = utils.ImportNow("species_symbol"); var toStringTagSymbol = utils.ImportNow("to_string_tag_symbol"); var ObjectHasOwnProperty; var GlobalPromise = global.Promise; +var PromiseThen = GlobalPromise.prototype.then; utils.Import(function(from) { ObjectHasOwnProperty = from.ObjectHasOwnProperty; @@ -46,42 +41,6 @@ utils.Import(function(from) { // Core functionality. -function PromiseSet(promise, status, value) { - SET_PRIVATE(promise, promiseStateSymbol, status); - SET_PRIVATE(promise, promiseResultSymbol, value); - - // There are 3 possible states for the resolve, reject symbols when we add - // a new callback -- - // 1) UNDEFINED -- This is the zero state where there is no callback - // registered. When we see this state, we directly attach the callbacks to - // the symbol. - // 2) !IS_ARRAY -- There is a single callback directly attached to the - // symbols. We need to create a new array to store additional callbacks. - // 3) IS_ARRAY -- There are multiple callbacks already registered, - // therefore we can just push the new callback to the existing array. - SET_PRIVATE(promise, promiseFulfillReactionsSymbol, UNDEFINED); - SET_PRIVATE(promise, promiseRejectReactionsSymbol, UNDEFINED); - - // This symbol is used only when one deferred needs to be attached. When more - // than one deferred need to be attached the promise, we attach them directly - // to the promiseFulfillReactionsSymbol and promiseRejectReactionsSymbol and - // reset this back to UNDEFINED. - SET_PRIVATE(promise, promiseDeferredReactionSymbol, UNDEFINED); - - return promise; -} - -function PromiseCreateAndSet(status, value) { - var promise = %promise_internal_constructor(); - // If debug is active, notify about the newly created promise first. - if (DEBUG_IS_ACTIVE) PromiseSet(promise, kPending, UNDEFINED); - return PromiseSet(promise, status, value); -} - -function PromiseInit(promise) { - return PromiseSet(promise, kPending, UNDEFINED); -} - function PromiseHandle(value, handler, deferred) { var debug_is_active = DEBUG_IS_ACTIVE; try { @@ -98,7 +57,6 @@ function PromiseHandle(value, handler, deferred) { // Pass false for debugEvent so .then chaining or throwaway promises // in async functions do not trigger redundant ExceptionEvents. %PromiseReject(deferred.promise, exception, false); - PromiseSet(deferred.promise, kRejected, exception); } else { %_Call(deferred.reject, UNDEFINED, exception); } @@ -134,34 +92,6 @@ function PromiseDebugGetInfo(deferreds, status) { return [id, name]; } -function PromiseAttachCallbacks(promise, deferred, onResolve, onReject) { - var maybeResolveCallbacks = - GET_PRIVATE(promise, promiseFulfillReactionsSymbol); - if (IS_UNDEFINED(maybeResolveCallbacks)) { - SET_PRIVATE(promise, promiseFulfillReactionsSymbol, onResolve); - SET_PRIVATE(promise, promiseRejectReactionsSymbol, onReject); - SET_PRIVATE(promise, promiseDeferredReactionSymbol, deferred); - } else if (!IS_ARRAY(maybeResolveCallbacks)) { - var resolveCallbacks = new InternalArray(); - var rejectCallbacks = new InternalArray(); - var existingDeferred = GET_PRIVATE(promise, promiseDeferredReactionSymbol); - - resolveCallbacks.push( - maybeResolveCallbacks, existingDeferred, onResolve, deferred); - rejectCallbacks.push(GET_PRIVATE(promise, promiseRejectReactionsSymbol), - existingDeferred, - onReject, - deferred); - - SET_PRIVATE(promise, promiseFulfillReactionsSymbol, resolveCallbacks); - SET_PRIVATE(promise, promiseRejectReactionsSymbol, rejectCallbacks); - SET_PRIVATE(promise, promiseDeferredReactionSymbol, UNDEFINED); - } else { - maybeResolveCallbacks.push(onResolve, deferred); - GET_PRIVATE(promise, promiseRejectReactionsSymbol).push(onReject, deferred); - } -} - function PromiseIdResolveHandler(x) { return x; } function PromiseIdRejectHandler(r) { %_ReThrow(r); } SET_PRIVATE(PromiseIdRejectHandler, promiseForwardingHandlerSymbol, true); @@ -171,8 +101,9 @@ SET_PRIVATE(PromiseIdRejectHandler, promiseForwardingHandlerSymbol, true); // For bootstrapper. +// This is used by utils and v8-extras. function PromiseCreate() { - return PromiseInit(%promise_internal_constructor()); + return %promise_internal_constructor(); } // ES#sec-promise-resolve-functions @@ -181,7 +112,6 @@ function ResolvePromise(promise, resolution) { if (resolution === promise) { var exception = %make_type_error(kPromiseCyclic, resolution); %PromiseReject(promise, exception, true); - PromiseSet(promise, kRejected, exception); return; } if (IS_RECEIVER(resolution)) { @@ -190,7 +120,6 @@ function ResolvePromise(promise, resolution) { var then = resolution.then; } catch (e) { %PromiseReject(promise, e, true); - PromiseSet(promise, kRejected, e); return; } @@ -198,18 +127,16 @@ function ResolvePromise(promise, resolution) { // rejected, shortcircuit the resolution procedure by directly // reusing the value from the promise. if (%is_promise(resolution) && then === PromiseThen) { - var thenableState = GET_PRIVATE(resolution, promiseStateSymbol); + var thenableState = %PromiseStatus(resolution); if (thenableState === kFulfilled) { // This goes inside the if-else to save one symbol lookup in // the slow path. - var thenableValue = GET_PRIVATE(resolution, promiseResultSymbol); - %PromiseFulfill(promise, kFulfilled, thenableValue, - promiseFulfillReactionsSymbol); - PromiseSet(promise, kFulfilled, thenableValue); + var thenableValue = %PromiseResult(resolution); + %PromiseFulfill(promise, kFulfilled, thenableValue); SET_PRIVATE(promise, promiseHasHandlerSymbol, true); return; } else if (thenableState === kRejected) { - var thenableValue = GET_PRIVATE(resolution, promiseResultSymbol); + var thenableValue = %PromiseResult(resolution); if (!HAS_DEFINED_PRIVATE(resolution, promiseHasHandlerSymbol)) { // Promise has already been rejected, but had no handler. // Revoke previously triggered reject event. @@ -217,7 +144,6 @@ function ResolvePromise(promise, resolution) { } // Don't cause a debug event as this case is forwarding a rejection %PromiseReject(promise, thenableValue, false); - PromiseSet(promise, kRejected, thenableValue); SET_PRIVATE(resolution, promiseHasHandlerSymbol, true); return; } @@ -232,21 +158,17 @@ function ResolvePromise(promise, resolution) { return; } } - %PromiseFulfill(promise, kFulfilled, resolution, - promiseFulfillReactionsSymbol); - PromiseSet(promise, kFulfilled, resolution); + %PromiseFulfill(promise, kFulfilled, resolution); } // Only used by async-await.js function RejectPromise(promise, reason, debugEvent) { %PromiseReject(promise, reason, debugEvent); - PromiseSet(promise, kRejected, reason); } // Export to bindings function DoRejectPromise(promise, reason) { %PromiseReject(promise, reason, true); - PromiseSet(promise, kRejected, reason); } // The resultCapability.promise is only ever fulfilled internally, @@ -254,7 +176,7 @@ function DoRejectPromise(promise, reason) { // calling them multiple times. function CreateInternalPromiseCapability() { return { - promise: PromiseCreate(), + promise: %promise_internal_constructor(), resolve: UNDEFINED, reject: UNDEFINED }; @@ -265,7 +187,7 @@ function CreateInternalPromiseCapability() { function NewPromiseCapability(C, debugEvent) { if (C === GlobalPromise) { // Optimized case, avoid extra closure. - var promise = PromiseCreate(); + var promise = %promise_internal_constructor(); // TODO(gsathya): Remove container for callbacks when this is // moved to CPP/TF. var callbacks = %create_resolving_functions(promise, debugEvent); @@ -298,7 +220,7 @@ function PromiseReject(r) { } if (this === GlobalPromise) { // Optimized case, avoid extra closure. - var promise = PromiseCreateAndSet(kRejected, r); + var promise = %promise_create_and_set(kRejected, r); // Trigger debug events if the debugger is on, as Promise.reject is // equivalent to throwing an exception directly. %PromiseRejectEventFromStack(promise, r); @@ -310,55 +232,6 @@ function PromiseReject(r) { } } -function PerformPromiseThen(promise, onResolve, onReject, resultCapability) { - if (!IS_CALLABLE(onResolve)) onResolve = PromiseIdResolveHandler; - if (!IS_CALLABLE(onReject)) onReject = PromiseIdRejectHandler; - - var status = GET_PRIVATE(promise, promiseStateSymbol); - switch (status) { - case kPending: - PromiseAttachCallbacks(promise, resultCapability, onResolve, onReject); - break; - case kFulfilled: - %EnqueuePromiseReactionJob(GET_PRIVATE(promise, promiseResultSymbol), - onResolve, resultCapability, kFulfilled); - break; - case kRejected: - if (!HAS_DEFINED_PRIVATE(promise, promiseHasHandlerSymbol)) { - // Promise has already been rejected, but had no handler. - // Revoke previously triggered reject event. - %PromiseRevokeReject(promise); - } - %EnqueuePromiseReactionJob(GET_PRIVATE(promise, promiseResultSymbol), - onReject, resultCapability, kRejected); - break; - } - - // Mark this promise as having handler. - SET_PRIVATE(promise, promiseHasHandlerSymbol, true); - return resultCapability.promise; -} - -// ES#sec-promise.prototype.then -// Promise.prototype.then ( onFulfilled, onRejected ) -// Multi-unwrapped chaining with thenable coercion. -function PromiseThen(onResolve, onReject) { - if (!%is_promise(this)) { - throw %make_type_error(kNotAPromise, this); - } - - var constructor = SpeciesConstructor(this, GlobalPromise); - var resultCapability; - if (constructor === GlobalPromise) { - resultCapability = CreateInternalPromiseCapability(); - } else { - // Pass false for debugEvent so .then chaining does not trigger - // redundant ExceptionEvents. - resultCapability = NewPromiseCapability(constructor, false); - } - return PerformPromiseThen(this, onResolve, onReject, resultCapability); -} - // ES#sec-promise.prototype.catch // Promise.prototype.catch ( onRejected ) function PromiseCatch(onReject) { @@ -377,7 +250,7 @@ function PromiseResolve(x) { // Avoid creating resolving functions. if (this === GlobalPromise) { - var promise = PromiseCreate(); + var promise = %promise_internal_constructor(); ResolvePromise(promise, x); return promise; } @@ -520,8 +393,10 @@ function PromiseHasUserDefinedRejectHandlerRecursive(promise) { return true; } - var queue = GET_PRIVATE(promise, promiseRejectReactionsSymbol); - var deferred = GET_PRIVATE(promise, promiseDeferredReactionSymbol); + if (!%is_promise(promise)) return false; + + var queue = %PromiseRejectReactions(promise); + var deferred = %PromiseDeferred(promise); if (IS_UNDEFINED(queue)) return false; @@ -529,8 +404,8 @@ function PromiseHasUserDefinedRejectHandlerRecursive(promise) { return PromiseHasUserDefinedRejectHandlerCheck(queue, deferred); } - for (var i = 0; i < queue.length; i += 2) { - if (PromiseHasUserDefinedRejectHandlerCheck(queue[i], queue[i + 1])) { + for (var i = 0; i < queue.length; i++) { + if (PromiseHasUserDefinedRejectHandlerCheck(queue[i], deferred[i])) { return true; } } @@ -568,7 +443,6 @@ utils.InstallFunctions(GlobalPromise, DONT_ENUM, [ utils.InstallGetter(GlobalPromise, speciesSymbol, PromiseSpecies); utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [ - "then", PromiseThen, "catch", PromiseCatch ]); @@ -582,7 +456,11 @@ utils.InstallFunctions(GlobalPromise.prototype, DONT_ENUM, [ "promise_resolve", ResolvePromise, "promise_then", PromiseThen, "promise_handle", PromiseHandle, - "promise_debug_get_info", PromiseDebugGetInfo + "promise_debug_get_info", PromiseDebugGetInfo, + "new_promise_capability", NewPromiseCapability, + "internal_promise_capability", CreateInternalPromiseCapability, + "promise_id_resolve_handler", PromiseIdResolveHandler, + "promise_id_reject_handler", PromiseIdRejectHandler ]); // This allows extras to create promises quickly without building extra @@ -600,7 +478,6 @@ utils.Export(function(to) { to.PromiseThen = PromiseThen; to.CreateInternalPromiseCapability = CreateInternalPromiseCapability; - to.PerformPromiseThen = PerformPromiseThen; to.ResolvePromise = ResolvePromise; to.RejectPromise = RejectPromise; }); diff --git a/src/objects-debug.cc b/src/objects-debug.cc index f479375573..be72b78269 100644 --- a/src/objects-debug.cc +++ b/src/objects-debug.cc @@ -104,7 +104,6 @@ void HeapObject::HeapObjectVerify() { case JS_API_OBJECT_TYPE: case JS_SPECIAL_API_OBJECT_TYPE: case JS_CONTEXT_EXTENSION_OBJECT_TYPE: - case JS_PROMISE_TYPE: JSObject::cast(this)->JSObjectVerify(); break; case JS_GENERATOR_OBJECT_TYPE: @@ -205,6 +204,9 @@ void HeapObject::HeapObjectVerify() { case JS_WEAK_SET_TYPE: JSWeakSet::cast(this)->JSWeakSetVerify(); break; + case JS_PROMISE_TYPE: + JSPromise::cast(this)->JSPromiseVerify(); + break; case JS_REGEXP_TYPE: JSRegExp::cast(this)->JSRegExpVerify(); break; @@ -884,6 +886,19 @@ void JSWeakSet::JSWeakSetVerify() { CHECK(table()->IsHashTable() || table()->IsUndefined(GetIsolate())); } +void JSPromise::JSPromiseVerify() { + CHECK(IsJSPromise()); + JSObjectVerify(); + Isolate* isolate = GetIsolate(); + CHECK(result()->IsUndefined(isolate) || result()->IsObject()); + CHECK(deferred()->IsUndefined(isolate) || deferred()->IsJSObject() || + deferred()->IsFixedArray()); + CHECK(fulfill_reactions()->IsUndefined(isolate) || + fulfill_reactions()->IsCallable() || + fulfill_reactions()->IsFixedArray()); + CHECK(reject_reactions()->IsUndefined(isolate) || + reject_reactions()->IsCallable() || reject_reactions()->IsFixedArray()); +} void JSRegExp::JSRegExpVerify() { JSObjectVerify(); @@ -1011,8 +1026,8 @@ void PromiseReactionJobInfo::PromiseReactionJobInfoVerify() { Isolate* isolate = GetIsolate(); CHECK(IsPromiseReactionJobInfo()); CHECK(value()->IsObject()); - CHECK(tasks()->IsJSArray() || tasks()->IsCallable()); - CHECK(deferred()->IsJSObject() || deferred()->IsUndefined(isolate)); + CHECK(tasks()->IsFixedArray() || tasks()->IsCallable()); + CHECK(deferred()->IsFixedArray() || deferred()->IsJSObject()); CHECK(debug_id()->IsNumber() || debug_id()->IsUndefined(isolate)); CHECK(debug_name()->IsString() || debug_name()->IsUndefined(isolate)); CHECK(context()->IsContext()); diff --git a/src/objects-inl.h b/src/objects-inl.h index 48395ee773..3b5e1acbb7 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -858,7 +858,6 @@ bool HeapObject::IsJSArrayBufferView() const { TYPE_CHECKER(JSRegExp, JS_REGEXP_TYPE) - template <> inline bool Is(Object* obj) { return obj->IsJSArray(); } @@ -2094,7 +2093,7 @@ int JSObject::GetHeaderSize(InstanceType type) { case JS_WEAK_SET_TYPE: return JSWeakSet::kSize; case JS_PROMISE_TYPE: - return JSObject::kHeaderSize; + return JSPromise::kSize; case JS_REGEXP_TYPE: return JSRegExp::kSize; case JS_CONTEXT_EXTENSION_OBJECT_TYPE: @@ -3323,6 +3322,7 @@ CAST_ACCESSOR(JSObject) CAST_ACCESSOR(JSProxy) CAST_ACCESSOR(JSReceiver) CAST_ACCESSOR(JSRegExp) +CAST_ACCESSOR(JSPromise) CAST_ACCESSOR(JSSet) CAST_ACCESSOR(JSSetIterator) CAST_ACCESSOR(JSStringIterator) @@ -7095,6 +7095,11 @@ void JSTypedArray::set_length(Object* value, WriteBarrierMode mode) { ACCESSORS(JSTypedArray, raw_length, Object, kLengthOffset) #endif +SMI_ACCESSORS(JSPromise, status, kStatusOffset) +ACCESSORS(JSPromise, result, Object, kResultOffset) +ACCESSORS(JSPromise, deferred, Object, kDeferredOffset) +ACCESSORS(JSPromise, fulfill_reactions, Object, kFulfillReactionsOffset) +ACCESSORS(JSPromise, reject_reactions, Object, kRejectReactionsOffset) ACCESSORS(JSRegExp, data, Object, kDataOffset) ACCESSORS(JSRegExp, flags, Object, kFlagsOffset) diff --git a/src/objects-printer.cc b/src/objects-printer.cc index a11b072377..f8f927c08d 100644 --- a/src/objects-printer.cc +++ b/src/objects-printer.cc @@ -150,11 +150,13 @@ void HeapObject::HeapObjectPrint(std::ostream& os) { // NOLINT case JS_SPECIAL_API_OBJECT_TYPE: case JS_CONTEXT_EXTENSION_OBJECT_TYPE: case JS_GENERATOR_OBJECT_TYPE: - case JS_PROMISE_TYPE: case JS_ARGUMENTS_TYPE: case JS_ERROR_TYPE: JSObject::cast(this)->JSObjectPrint(os); break; + case JS_PROMISE_TYPE: + JSPromise::cast(this)->JSPromisePrint(os); + break; case JS_ARRAY_TYPE: JSArray::cast(this)->JSArrayPrint(os); break; @@ -542,6 +544,14 @@ void JSArray::JSArrayPrint(std::ostream& os) { // NOLINT JSObjectPrintBody(os, this); } +void JSPromise::JSPromisePrint(std::ostream& os) { // NOLINT + JSObjectPrintHeader(os, this, "JSPromise"); + os << "\n - status = " << JSPromise::Status(status()); + os << "\n - result = " << Brief(result()); + os << "\n - deferreds = " << Brief(deferred()); + os << "\n - fulfill_reactions = " << Brief(fulfill_reactions()); + os << "\n - reject_reactions = " << Brief(reject_reactions()); +} void JSRegExp::JSRegExpPrint(std::ostream& os) { // NOLINT JSObjectPrintHeader(os, this, "JSRegExp"); diff --git a/src/objects.cc b/src/objects.cc index 358cdfe90f..47fdf10e36 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -16447,6 +16447,19 @@ class StringSharedKey : public HashTableKey { int scope_position_; }; +// static +const char* JSPromise::Status(int status) { + switch (status) { + case kPromiseFulfilled: + return "resolved"; + case kPromisePending: + return "pending"; + case kPromiseRejected: + return "rejected"; + } + UNREACHABLE(); + return NULL; +} namespace { diff --git a/src/objects.h b/src/objects.h index dd4ccf4017..b44fe18df2 100644 --- a/src/objects.h +++ b/src/objects.h @@ -8895,6 +8895,41 @@ class JSMessageObject: public JSObject { kSize> BodyDescriptor; }; +class JSPromise : public JSObject { + public: + DECL_INT_ACCESSORS(status) + DECL_ACCESSORS(result, Object) + + // There are 3 possible states for these fields -- + // 1) Undefined -- This is the zero state when there is no callback + // or deferred registered. + // 2) Object -- There is a single Callable directly attached to the + // fulfill_reactions, reject_reactions and the deferred JSObject is + // directly attached to the deferred field. + // 3) FixedArray -- There is more than one callback and deferred + // attached to a FixedArray. + DECL_ACCESSORS(deferred, Object) + DECL_ACCESSORS(fulfill_reactions, Object) + DECL_ACCESSORS(reject_reactions, Object) + + static const char* Status(int status); + + DECLARE_CAST(JSPromise) + + // Dispatched behavior. + DECLARE_PRINTER(JSPromise) + DECLARE_VERIFIER(JSPromise) + + // Layout description. + static const int kStatusOffset = JSObject::kHeaderSize; + static const int kResultOffset = kStatusOffset + kPointerSize; + static const int kDeferredOffset = kResultOffset + kPointerSize; + static const int kFulfillReactionsOffset = kDeferredOffset + kPointerSize; + static const int kRejectReactionsOffset = + kFulfillReactionsOffset + kPointerSize; + static const int kSize = kRejectReactionsOffset + kPointerSize; +}; + // Regular expressions // The regular expression holds a single reference to a FixedArray in // the kDataOffset field. diff --git a/src/runtime/runtime-debug.cc b/src/runtime/runtime-debug.cc index 501666f0a9..ba520eb43b 100644 --- a/src/runtime/runtime-debug.cc +++ b/src/runtime/runtime-debug.cc @@ -136,14 +136,6 @@ static Handle DebugGetProperty(LookupIterator* it, return it->isolate()->factory()->undefined_value(); } - -static Handle DebugGetProperty(Handle object, - Handle name) { - LookupIterator it(object, name); - return DebugGetProperty(&it); -} - - template static MaybeHandle GetIteratorInternalProperties( Isolate* isolate, Handle object) { @@ -248,24 +240,8 @@ MaybeHandle Runtime::GetInternalProperties(Isolate* isolate, result->set(5, generator->receiver()); return factory->NewJSArrayWithElements(result); } else if (object->IsJSPromise()) { - Handle promise = Handle::cast(object); - - Handle status_obj = - DebugGetProperty(promise, isolate->factory()->promise_state_symbol()); - CHECK(status_obj->IsSmi()); - const char* status = "rejected"; - int status_val = Handle::cast(status_obj)->value(); - switch (status_val) { - case kPromiseFulfilled: - status = "resolved"; - break; - case kPromisePending: - status = "pending"; - break; - default: - DCHECK_EQ(kPromiseRejected, status_val); - } - + Handle promise = Handle::cast(object); + const char* status = JSPromise::Status(promise->status()); Handle result = factory->NewFixedArray(2 * 2); Handle promise_status = factory->NewStringFromAsciiChecked("[[PromiseStatus]]"); @@ -273,8 +249,7 @@ MaybeHandle Runtime::GetInternalProperties(Isolate* isolate, Handle status_str = factory->NewStringFromAsciiChecked(status); result->set(1, *status_str); - Handle value_obj = - DebugGetProperty(promise, isolate->factory()->promise_result_symbol()); + Handle value_obj(promise->result(), isolate); Handle promise_value = factory->NewStringFromAsciiChecked("[[PromiseValue]]"); result->set(2, *promise_value); diff --git a/src/runtime/runtime-promise.cc b/src/runtime/runtime-promise.cc index b528ff7137..236582be02 100644 --- a/src/runtime/runtime-promise.cc +++ b/src/runtime/runtime-promise.cc @@ -1,7 +1,6 @@ // 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/runtime/runtime-utils.h" #include "src/debug/debug.h" @@ -66,10 +65,17 @@ void EnqueuePromiseReactionJob(Isolate* isolate, Handle value, Handle debug_name = isolate->factory()->undefined_value(); if (isolate->debug()->is_active()) { MaybeHandle maybe_result; - Handle argv[] = {deferred, status}; + Handle deferred_obj(deferred); + + if (deferred->IsFixedArray()) { + deferred_obj = isolate->factory()->undefined_value(); + } + + Handle argv[] = {deferred_obj, status}; maybe_result = Execution::TryCall( isolate, isolate->promise_debug_get_info(), isolate->factory()->undefined_value(), arraysize(argv), argv); + Handle result; if ((maybe_result).ToHandle(&result)) { CHECK(result->IsJSArray()); @@ -88,42 +94,50 @@ void EnqueuePromiseReactionJob(Isolate* isolate, Handle value, isolate->EnqueueMicrotask(info); } -void PromiseFulfill(Isolate* isolate, Handle promise, - Handle status, Handle value, - Handle reaction) { - Handle tasks = JSReceiver::GetDataProperty(promise, reaction); - if (!tasks->IsUndefined(isolate)) { - Handle deferred = JSReceiver::GetDataProperty( - promise, isolate->factory()->promise_deferred_reaction_symbol()); +void PromiseSet(Handle promise, int status, Handle result) { + promise->set_status(status); + promise->set_result(*result); + // TODO(gsathya): reset reactions? +} + +void PromiseFulfill(Isolate* isolate, Handle promise, + Handle status, Handle value) { + // Check if there are any callbacks. + if (!promise->deferred()->IsUndefined(isolate)) { + Handle tasks((status->value() == kPromiseFulfilled) + ? promise->fulfill_reactions() + : promise->reject_reactions(), + isolate); + Handle deferred(promise->deferred(), isolate); EnqueuePromiseReactionJob(isolate, value, tasks, deferred, status); } + + PromiseSet(promise, status->value(), value); } + } // namespace RUNTIME_FUNCTION(Runtime_PromiseReject) { DCHECK(args.length() == 3); HandleScope scope(isolate); - CONVERT_ARG_HANDLE_CHECKED(JSReceiver, promise, 0); + CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0); CONVERT_ARG_HANDLE_CHECKED(Object, reason, 1); CONVERT_BOOLEAN_ARG_CHECKED(debug_event, 2); PromiseRejectEvent(isolate, promise, promise, reason, debug_event); - Handle status = handle(Smi::FromInt(kPromiseRejected), isolate); - Handle reaction = - isolate->factory()->promise_reject_reactions_symbol(); - PromiseFulfill(isolate, promise, status, reason, reaction); + Handle status(Smi::FromInt(kPromiseRejected), isolate); + PromiseFulfill(isolate, promise, status, reason); return isolate->heap()->undefined_value(); } RUNTIME_FUNCTION(Runtime_PromiseFulfill) { - DCHECK(args.length() == 4); + DCHECK(args.length() == 3); HandleScope scope(isolate); - CONVERT_ARG_HANDLE_CHECKED(JSReceiver, promise, 0); + CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0); CONVERT_ARG_HANDLE_CHECKED(Smi, status, 1); CONVERT_ARG_HANDLE_CHECKED(Object, value, 2); - CONVERT_ARG_HANDLE_CHECKED(Symbol, reaction, 3); - PromiseFulfill(isolate, promise, status, value, reaction); + PromiseFulfill(isolate, promise, status, value); return isolate->heap()->undefined_value(); } @@ -192,6 +206,7 @@ RUNTIME_FUNCTION(Runtime_RunMicrotasks) { RUNTIME_FUNCTION(Runtime_CreateResolvingFunctions) { HandleScope scope(isolate); CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0); + DCHECK(args.length() == 1); Handle resolve, reject; PromiseUtils::CreateResolvingFunctions( @@ -204,5 +219,58 @@ RUNTIME_FUNCTION(Runtime_CreateResolvingFunctions) { return *result; } +RUNTIME_FUNCTION(Runtime_PromiseStatus) { + HandleScope scope(isolate); + DCHECK(args.length() == 1); + CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0); + + return Smi::FromInt(promise->status()); +} + +RUNTIME_FUNCTION(Runtime_PromiseResult) { + HandleScope scope(isolate); + DCHECK(args.length() == 1); + CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0); + return promise->result(); +} + +RUNTIME_FUNCTION(Runtime_PromiseDeferred) { + HandleScope scope(isolate); + DCHECK(args.length() == 1); + CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0); + + Handle deferred(promise->deferred(), isolate); + if (deferred->IsUndefined(isolate)) { + return isolate->heap()->undefined_value(); + } + + if (deferred->IsJSObject()) { + return *deferred; + } + + DCHECK(deferred->IsFixedArray()); + return *isolate->factory()->NewJSArrayWithElements( + Handle::cast(deferred)); +} + +RUNTIME_FUNCTION(Runtime_PromiseRejectReactions) { + HandleScope scope(isolate); + DCHECK(args.length() == 1); + CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0); + + Handle reject_reactions(promise->reject_reactions(), isolate); + if (reject_reactions->IsUndefined(isolate)) { + return isolate->heap()->undefined_value(); + } + + if (reject_reactions->IsJSObject()) { + return *reject_reactions; + } + + DCHECK(reject_reactions->IsFixedArray()); + return *isolate->factory()->NewJSArrayWithElements( + Handle::cast(reject_reactions)); +} + } // namespace internal } // namespace v8 diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index ba8f71dbe8..ecbef228de 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -304,10 +304,14 @@ namespace internal { F(NewSyntaxError, 2, 1) \ F(NewTypeError, 2, 1) \ F(OrdinaryHasInstance, 2, 1) \ + F(PromiseDeferred, 1, 1) \ F(PromiseReject, 3, 1) \ - F(PromiseFulfill, 4, 1) \ + F(PromiseFulfill, 3, 1) \ F(PromiseRejectEventFromStack, 2, 1) \ + F(PromiseRejectReactions, 1, 1) \ F(PromiseRevokeReject, 1, 1) \ + F(PromiseResult, 1, 1) \ + F(PromiseStatus, 1, 1) \ F(PromoteScheduledException, 0, 1) \ F(ReThrow, 1, 1) \ F(RunMicrotasks, 0, 1) \ diff --git a/test/cctest/test-inobject-slack-tracking.cc b/test/cctest/test-inobject-slack-tracking.cc index 9f33d55938..357d2f563d 100644 --- a/test/cctest/test-inobject-slack-tracking.cc +++ b/test/cctest/test-inobject-slack-tracking.cc @@ -1099,10 +1099,8 @@ TEST(SubclassPromiseBuiltin) { CcTest::InitializeVM(); v8::HandleScope scope(CcTest::isolate()); - const int first_field = 5; TestSubclassBuiltin("A1", JS_PROMISE_TYPE, "Promise", - "function(resolve, reject) { resolve('ok'); }", - first_field); + "function(resolve, reject) { resolve('ok'); }"); }