From f22c213304ec3542df87019aed0909b7dafeaa93 Mon Sep 17 00:00:00 2001 From: Joshua Litt Date: Thu, 23 Jan 2020 08:50:05 -0800 Subject: [PATCH] [promises] Port remaining promise code to Torque. Bug: v8:9838 Change-Id: Idc6bda122354a54dd24e39b0356f35b0f54ef089 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2012596 Commit-Queue: Joshua Litt Reviewed-by: Jakob Gruber Cr-Commit-Position: refs/heads/master@{#66031} --- BUILD.gn | 1 + src/builtins/builtins-async-generator-gen.cc | 2 +- src/builtins/builtins-async-iterator-gen.cc | 2 +- src/builtins/builtins-promise-gen.cc | 226 +----------------- src/builtins/builtins-promise-gen.h | 107 +-------- src/builtins/cast.tq | 9 + src/builtins/promise-abstract-operations.tq | 29 +-- src/builtins/promise-constructor.tq | 17 +- src/builtins/promise-finally.tq | 5 - src/builtins/promise-misc.tq | 236 +++++++++++++++++++ src/builtins/promise-resolve.tq | 8 +- src/builtins/promise-then.tq | 18 +- src/codegen/code-stub-assembler.cc | 7 + src/codegen/code-stub-assembler.h | 3 + test/cctest/test-code-stub-assembler.cc | 18 +- 15 files changed, 297 insertions(+), 391 deletions(-) create mode 100644 src/builtins/promise-misc.tq diff --git a/BUILD.gn b/BUILD.gn index e661877c4b..0d29751c86 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -987,6 +987,7 @@ torque_files = [ "src/builtins/promise-all-element-closure.tq", "src/builtins/promise-constructor.tq", "src/builtins/promise-finally.tq", + "src/builtins/promise-misc.tq", "src/builtins/promise-race.tq", "src/builtins/promise-reaction-job.tq", "src/builtins/promise-resolve.tq", diff --git a/src/builtins/builtins-async-generator-gen.cc b/src/builtins/builtins-async-generator-gen.cc index dccce1907f..acc7721465 100644 --- a/src/builtins/builtins-async-generator-gen.cc +++ b/src/builtins/builtins-async-generator-gen.cc @@ -148,7 +148,7 @@ void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorEnqueue( // presently executing, then this method will loop through, processing each // request from front to back. // This loop resides in AsyncGeneratorResumeNext. - TNode promise = AllocateAndInitJSPromise(context); + TNode promise = NewJSPromise(context); Label if_receiverisincompatible(this, Label::kDeferred); GotoIf(TaggedIsSmi(receiver), &if_receiverisincompatible); diff --git a/src/builtins/builtins-async-iterator-gen.cc b/src/builtins/builtins-async-iterator-gen.cc index d320339f76..1515605649 100644 --- a/src/builtins/builtins-async-iterator-gen.cc +++ b/src/builtins/builtins-async-iterator-gen.cc @@ -105,7 +105,7 @@ void AsyncFromSyncBuiltinsAssembler::Generate_AsyncFromSyncIteratorMethod( const char* operation_name, Label::Type reject_label_type, base::Optional> initial_exception_value) { const TNode native_context = LoadNativeContext(context); - const TNode promise = AllocateAndInitJSPromise(context); + const TNode promise = NewJSPromise(context); TVARIABLE( Object, var_exception, diff --git a/src/builtins/builtins-promise-gen.cc b/src/builtins/builtins-promise-gen.cc index 5cf93e6597..6b1f43d8ea 100644 --- a/src/builtins/builtins-promise-gen.cc +++ b/src/builtins/builtins-promise-gen.cc @@ -20,234 +20,22 @@ namespace v8 { namespace internal { -using Node = compiler::Node; -using IteratorRecord = TorqueStructIteratorRecord; -using PromiseResolvingFunctions = TorqueStructPromiseResolvingFunctions; - -TNode PromiseBuiltinsAssembler::AllocateJSPromise( - TNode context) { - const TNode native_context = LoadNativeContext(context); - const TNode promise_fun = - CAST(LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX)); - CSA_ASSERT(this, IsFunctionWithPrototypeSlotMap(LoadMap(promise_fun))); - const TNode promise_map = LoadObjectField( - promise_fun, JSFunction::kPrototypeOrInitialMapOffset); - const TNode promise = - Allocate(JSPromise::kSizeWithEmbedderFields); - StoreMapNoWriteBarrier(promise, promise_map); - StoreObjectFieldRoot(promise, JSPromise::kPropertiesOrHashOffset, - RootIndex::kEmptyFixedArray); - StoreObjectFieldRoot(promise, JSPromise::kElementsOffset, - RootIndex::kEmptyFixedArray); - return CAST(promise); -} - -void PromiseBuiltinsAssembler::PromiseInit(TNode promise) { - STATIC_ASSERT(v8::Promise::kPending == 0); - StoreObjectFieldNoWriteBarrier(promise, JSPromise::kReactionsOrResultOffset, - SmiConstant(Smi::zero())); - StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, - SmiConstant(Smi::zero())); +void PromiseBuiltinsAssembler::ZeroOutEmbedderOffsets( + TNode promise) { for (int offset = JSPromise::kHeaderSize; offset < JSPromise::kSizeWithEmbedderFields; offset += kTaggedSize) { StoreObjectFieldNoWriteBarrier(promise, offset, SmiConstant(Smi::zero())); } } -TNode PromiseBuiltinsAssembler::AllocateAndInitJSPromise( +TNode PromiseBuiltinsAssembler::AllocatePromiseReactionJobTask( TNode context) { - return AllocateAndInitJSPromise(context, UndefinedConstant()); + return Allocate(PromiseReactionJobTask::kSizeOfAllPromiseReactionJobTasks); } -TNode PromiseBuiltinsAssembler::AllocateAndInitJSPromise( - TNode context, TNode parent) { - const TNode instance = AllocateJSPromise(context); - PromiseInit(instance); - - Label out(this); - GotoIfNot(IsPromiseHookEnabledOrHasAsyncEventDelegate(), &out); - CallRuntime(Runtime::kPromiseHookInit, context, instance, parent); - Goto(&out); - - BIND(&out); - return instance; -} - -TNode PromiseBuiltinsAssembler::AllocateAndSetJSPromise( - TNode context, v8::Promise::PromiseState status, - TNode result) { - DCHECK_NE(Promise::kPending, status); - - const TNode instance = AllocateJSPromise(context); - StoreObjectFieldNoWriteBarrier(instance, JSPromise::kReactionsOrResultOffset, - result); - STATIC_ASSERT(JSPromise::kStatusShift == 0); - StoreObjectFieldNoWriteBarrier(instance, JSPromise::kFlagsOffset, - SmiConstant(status)); - for (int offset = JSPromise::kHeaderSize; - offset < JSPromise::kSizeWithEmbedderFields; offset += kTaggedSize) { - StoreObjectFieldNoWriteBarrier(instance, offset, SmiConstant(0)); - } - - Label out(this); - GotoIfNot(IsPromiseHookEnabledOrHasAsyncEventDelegate(), &out); - CallRuntime(Runtime::kPromiseHookInit, context, instance, - UndefinedConstant()); - Goto(&out); - - BIND(&out); - return instance; -} - -TNode PromiseBuiltinsAssembler::PromiseHasHandler( - TNode promise) { - const TNode flags = - LoadObjectField(promise, JSPromise::kFlagsOffset); - return IsSetWord(SmiUntag(flags), 1 << JSPromise::kHasHandlerBit); -} - -TNode PromiseBuiltinsAssembler::AllocatePromiseReaction( - TNode next, TNode promise_or_capability, - TNode fulfill_handler, TNode reject_handler) { - const TNode reaction = Allocate(PromiseReaction::kSize); - StoreMapNoWriteBarrier(reaction, RootIndex::kPromiseReactionMap); - StoreObjectFieldNoWriteBarrier(reaction, PromiseReaction::kNextOffset, next); - StoreObjectFieldNoWriteBarrier(reaction, - PromiseReaction::kPromiseOrCapabilityOffset, - promise_or_capability); - StoreObjectFieldNoWriteBarrier( - reaction, PromiseReaction::kFulfillHandlerOffset, fulfill_handler); - StoreObjectFieldNoWriteBarrier( - reaction, PromiseReaction::kRejectHandlerOffset, reject_handler); - return CAST(reaction); -} - -TNode -PromiseBuiltinsAssembler::AllocatePromiseReactionJobTask( - TNode map, TNode context, TNode argument, - TNode handler, TNode promise_or_capability) { - const TNode microtask = - Allocate(PromiseReactionJobTask::kSizeOfAllPromiseReactionJobTasks); - StoreMapNoWriteBarrier(microtask, map); - StoreObjectFieldNoWriteBarrier( - microtask, PromiseReactionJobTask::kArgumentOffset, argument); - StoreObjectFieldNoWriteBarrier( - microtask, PromiseReactionJobTask::kContextOffset, context); - StoreObjectFieldNoWriteBarrier( - microtask, PromiseReactionJobTask::kHandlerOffset, handler); - StoreObjectFieldNoWriteBarrier( - microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset, - promise_or_capability); - return CAST(microtask); -} - -TNode -PromiseBuiltinsAssembler::AllocatePromiseResolveThenableJobTask( - TNode promise_to_resolve, TNode then, - TNode thenable, TNode context) { - const TNode microtask = - Allocate(PromiseResolveThenableJobTask::kSize); - StoreMapNoWriteBarrier(microtask, - RootIndex::kPromiseResolveThenableJobTaskMap); - StoreObjectFieldNoWriteBarrier( - microtask, PromiseResolveThenableJobTask::kContextOffset, context); - StoreObjectFieldNoWriteBarrier( - microtask, PromiseResolveThenableJobTask::kPromiseToResolveOffset, - promise_to_resolve); - StoreObjectFieldNoWriteBarrier( - microtask, PromiseResolveThenableJobTask::kThenOffset, then); - StoreObjectFieldNoWriteBarrier( - microtask, PromiseResolveThenableJobTask::kThenableOffset, thenable); - return CAST(microtask); -} - -void PromiseBuiltinsAssembler::BranchIfPromiseResolveLookupChainIntact( - TNode native_context, TNode constructor, - Label* if_fast, Label* if_slow) { - GotoIfForceSlowPath(if_slow); - TNode promise_fun = - LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX); - GotoIfNot(TaggedEqual(promise_fun, constructor), if_slow); - Branch(IsPromiseResolveProtectorCellInvalid(), if_slow, if_fast); -} - -void PromiseBuiltinsAssembler::GotoIfNotPromiseResolveLookupChainIntact( - TNode native_context, TNode constructor, - Label* if_slow) { - Label if_fast(this); - BranchIfPromiseResolveLookupChainIntact(native_context, constructor, &if_fast, - if_slow); - BIND(&if_fast); -} - -void PromiseBuiltinsAssembler::BranchIfPromiseSpeciesLookupChainIntact( - TNode native_context, TNode promise_map, Label* if_fast, - Label* if_slow) { - TNode promise_prototype = - LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX); - GotoIfForceSlowPath(if_slow); - GotoIfNot(TaggedEqual(LoadMapPrototype(promise_map), promise_prototype), - if_slow); - Branch(IsPromiseSpeciesProtectorCellInvalid(), if_slow, if_fast); -} - -void PromiseBuiltinsAssembler::BranchIfPromiseThenLookupChainIntact( - TNode native_context, TNode receiver_map, - Label* if_fast, Label* if_slow) { - GotoIfForceSlowPath(if_slow); - GotoIfNot(IsJSPromiseMap(receiver_map), if_slow); - const TNode promise_prototype = - LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX); - GotoIfNot(TaggedEqual(LoadMapPrototype(receiver_map), promise_prototype), - if_slow); - Branch(IsPromiseThenProtectorCellInvalid(), if_slow, if_fast); -} - -void PromiseBuiltinsAssembler::BranchIfAccessCheckFailed( - TNode context, TNode native_context, - TNode promise_constructor, TNode executor, - Label* if_noaccess) { - TVARIABLE(HeapObject, var_executor); - var_executor = CAST(executor); - Label has_access(this), call_runtime(this, Label::kDeferred); - - // If executor is a bound function, load the bound function until we've - // reached an actual function. - Label found_function(this), loop_over_bound_function(this, &var_executor); - Goto(&loop_over_bound_function); - BIND(&loop_over_bound_function); - { - TNode executor_type = LoadInstanceType(var_executor.value()); - GotoIf(InstanceTypeEqual(executor_type, JS_FUNCTION_TYPE), &found_function); - GotoIfNot(InstanceTypeEqual(executor_type, JS_BOUND_FUNCTION_TYPE), - &call_runtime); - var_executor = LoadObjectField( - var_executor.value(), JSBoundFunction::kBoundTargetFunctionOffset); - Goto(&loop_over_bound_function); - } - - // Load the context from the function and compare it to the Promise - // constructor's context. If they match, everything is fine, otherwise, bail - // out to the runtime. - BIND(&found_function); - { - TNode function_context = LoadObjectField( - var_executor.value(), JSFunction::kContextOffset); - TNode native_function_context = - LoadNativeContext(function_context); - Branch(TaggedEqual(native_context, native_function_context), &has_access, - &call_runtime); - } - - BIND(&call_runtime); - { - Branch(TaggedEqual(CallRuntime(Runtime::kAllowDynamicFunction, context, - promise_constructor), - TrueConstant()), - &has_access, if_noaccess); - } - - BIND(&has_access); +TNode PromiseBuiltinsAssembler::AllocateJSPromise( + TNode context) { + return Allocate(JSPromise::kSizeWithEmbedderFields); } } // namespace internal diff --git a/src/builtins/builtins-promise-gen.h b/src/builtins/builtins-promise-gen.h index 1aa8e112d1..66044b51af 100644 --- a/src/builtins/builtins-promise-gen.h +++ b/src/builtins/builtins-promise-gen.h @@ -17,112 +17,11 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler { public: explicit PromiseBuiltinsAssembler(compiler::CodeAssemblerState* state) : CodeStubAssembler(state) {} - // These allocate and initialize a promise with pending state and - // undefined fields. - // - // This uses undefined as the parent promise for the promise init - // hook. - TNode AllocateAndInitJSPromise(TNode context); - // This uses the given parent as the parent promise for the promise - // init hook. - TNode AllocateAndInitJSPromise(TNode context, - TNode parent); + void ZeroOutEmbedderOffsets(TNode promise); - // This allocates and initializes a promise with the given state and - // fields. - TNode AllocateAndSetJSPromise(TNode context, - v8::Promise::PromiseState status, - TNode result); + TNode AllocateJSPromise(TNode context); - TNode AllocatePromiseReaction( - TNode next, TNode promise_or_capability, - TNode fulfill_handler, TNode reject_handler); - - TNode AllocatePromiseReactionJobTask( - TNode map, TNode context, TNode argument, - TNode handler, TNode promise_or_capability); - - TNode AllocatePromiseResolveThenableJobTask( - TNode promise_to_resolve, TNode then, - TNode thenable, TNode context); - TNode PromiseHasHandler(TNode promise); - - void BranchIfAccessCheckFailed(TNode context, - TNode native_context, - TNode promise_constructor, - TNode executor, Label* if_noaccess); - void PromiseInit(TNode promise); - - // We can shortcut the SpeciesConstructor on {promise_map} if it's - // [[Prototype]] is the (initial) Promise.prototype and the @@species - // protector is intact, as that guards the lookup path for the "constructor" - // property on JSPromise instances which have the %PromisePrototype%. - void BranchIfPromiseSpeciesLookupChainIntact( - TNode native_context, TNode promise_map, - Label* if_fast, Label* if_slow); - - template - TNode InvokeThen(TNode native_context, - TNode receiver, TArgs... args) { - TVARIABLE(Object, var_result); - Label if_fast(this), if_slow(this, Label::kDeferred), - done(this, &var_result); - GotoIf(TaggedIsSmi(receiver), &if_slow); - const TNode receiver_map = LoadMap(CAST(receiver)); - // We can skip the "then" lookup on {receiver} if it's [[Prototype]] - // is the (initial) Promise.prototype and the Promise#then protector - // is intact, as that guards the lookup path for the "then" property - // on JSPromise instances which have the (initial) %PromisePrototype%. - BranchIfPromiseThenLookupChainIntact(native_context, receiver_map, &if_fast, - &if_slow); - - BIND(&if_fast); - { - const TNode then = - LoadContextElement(native_context, Context::PROMISE_THEN_INDEX); - var_result = - CallJS(CodeFactory::CallFunction( - isolate(), ConvertReceiverMode::kNotNullOrUndefined), - native_context, then, receiver, args...); - Goto(&done); - } - - BIND(&if_slow); - { - const TNode then = GetProperty( - native_context, receiver, isolate()->factory()->then_string()); - var_result = - CallJS(CodeFactory::Call(isolate(), - ConvertReceiverMode::kNotNullOrUndefined), - native_context, then, receiver, args...); - Goto(&done); - } - - BIND(&done); - return var_result.value(); - } - - protected: - // We can skip the "resolve" lookup on {constructor} if it's the (initial) - // Promise constructor and the Promise.resolve() protector is intact, as - // that guards the lookup path for the "resolve" property on the %Promise% - // intrinsic object. - void BranchIfPromiseResolveLookupChainIntact( - TNode native_context, TNode constructor, - Label* if_fast, Label* if_slow); - void GotoIfNotPromiseResolveLookupChainIntact( - TNode native_context, TNode constructor, - Label* if_slow); - - // We can skip the "then" lookup on {receiver_map} if it's [[Prototype]] - // is the (initial) Promise.prototype and the Promise#then() protector - // is intact, as that guards the lookup path for the "then" property - // on JSPromise instances which have the (initial) %PromisePrototype%. - void BranchIfPromiseThenLookupChainIntact(TNode native_context, - TNode receiver_map, - Label* if_fast, Label* if_slow); - - TNode AllocateJSPromise(TNode context); + TNode AllocatePromiseReactionJobTask(TNode context); }; } // namespace internal diff --git a/src/builtins/cast.tq b/src/builtins/cast.tq index 44556e2320..7e8d08097f 100644 --- a/src/builtins/cast.tq +++ b/src/builtins/cast.tq @@ -36,6 +36,7 @@ extern macro IsExtensibleMap(Map): bool; extern macro IsJSPrimitiveWrapper(HeapObject): bool; extern macro IsPromiseCapability(HeapObject): bool; extern macro IsPromiseReaction(HeapObject): bool; +extern macro IsPromiseReactionJobTask(HeapObject): bool; extern macro IsPromiseRejectReactionJobTask(HeapObject): bool; extern macro IsPromiseFulfillReactionJobTask(HeapObject): bool; extern macro IsSharedFunctionInfo(HeapObject): bool; @@ -643,6 +644,14 @@ Cast(o: HeapObject): JSReceiver|Null } } +Cast(o: HeapObject): + PromiseReactionJobTask labels CastError { + if (IsPromiseReactionJobTask(o)) { + return %RawDownCast(o); + } + goto CastError; +} + Cast(o: HeapObject): PromiseFulfillReactionJobTask labels CastError { if (IsPromiseFulfillReactionJobTask(o)) { diff --git a/src/builtins/promise-abstract-operations.tq b/src/builtins/promise-abstract-operations.tq index cd0d4a935d..8828ab84d2 100644 --- a/src/builtins/promise-abstract-operations.tq +++ b/src/builtins/promise-abstract-operations.tq @@ -246,9 +246,6 @@ namespace promise { const kPromiseBuiltinsDebugEventSlot: constexpr ContextSlot generates 'PromiseBuiltins::kDebugEventSlot'; - extern macro - PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Context): JSPromise; - @export macro CreatePromiseCapabilitiesExecutorContext( nativeContext: NativeContext, capability: PromiseCapability): Context { @@ -306,7 +303,7 @@ namespace promise { if (TaggedEqual( constructor, nativeContext[NativeContextSlot::PROMISE_FUNCTION_INDEX])) { - const promise = AllocateAndInitJSPromise(nativeContext); + const promise = NewJSPromise(); const pair = CreatePromiseResolvingFunctions(promise, debugEvent, nativeContext); @@ -409,14 +406,6 @@ namespace promise { return ResolvePromise(context, promise, resolution); } - extern macro - PromiseBuiltinsAssembler::AllocatePromiseReaction( - Object, HeapObject, HeapObject, HeapObject): PromiseReaction; - - extern macro - PromiseBuiltinsAssembler::AllocatePromiseReactionJobTask( - Map, Context, Object, HeapObject, HeapObject): PromiseReactionJobTask; - @export transitioning macro PerformPromiseThenImpl(implicit context: Context)( promise: JSPromise, onFulfilled: Callable|Undefined, @@ -427,13 +416,14 @@ namespace promise { // PromiseReaction holding both the onFulfilled and onRejected callbacks. // Once the {promise} is resolved we decide on the concrete handler to // push onto the microtask queue. - const promiseReactions = promise.reactions_or_result; - const reaction = AllocatePromiseReaction( + const promiseReactions = + UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result); + const reaction = NewPromiseReaction( promiseReactions, resultPromiseOrCapability, onFulfilled, onRejected); promise.reactions_or_result = reaction; } else { let map: Map; - let handler: HeapObject; + let handler: Callable|Undefined = Undefined; let handlerContext: Context; if (promise.Status() == PromiseState::kFulfilled) { map = PromiseFulfillReactionJobTaskMapConstant(); @@ -451,7 +441,7 @@ namespace promise { } const reactionsOrResult = promise.reactions_or_result; - const microtask = AllocatePromiseReactionJobTask( + const microtask = NewPromiseReactionJobTask( map, handlerContext, reactionsOrResult, handler, resultPromiseOrCapability); EnqueueMicrotask(handlerContext, microtask); @@ -469,10 +459,6 @@ namespace promise { return resultPromise; } - extern macro - PromiseBuiltinsAssembler::AllocateAndSetJSPromise( - Context, constexpr PromiseState, JSAny): JSPromise; - // https://tc39.es/ecma262/#sec-promise-reject-functions transitioning javascript builtin PromiseReject(js-implicit context: NativeContext, receiver: JSAny)( @@ -484,8 +470,7 @@ namespace promise { const promiseFun = context[NativeContextSlot::PROMISE_FUNCTION_INDEX]; if (promiseFun == receiver) { - const promise = - AllocateAndSetJSPromise(context, PromiseState::kRejected, reason); + const promise = NewJSPromise(PromiseState::kRejected, reason); runtime::PromiseRejectEventFromStack(promise, reason); return promise; } else { diff --git a/src/builtins/promise-constructor.tq b/src/builtins/promise-constructor.tq index 1f8a5bbb32..dc0e077485 100644 --- a/src/builtins/promise-constructor.tq +++ b/src/builtins/promise-constructor.tq @@ -25,18 +25,13 @@ namespace promise { const kPromiseConstructorReturnedUndefined: constexpr UseCounterFeature generates 'v8::Isolate::kPromiseConstructorReturnedUndefined'; - extern macro - PromiseBuiltinsAssembler::BranchIfAccessCheckFailed( - Context, Context, Object, Object): void labels NoAccess; - extern macro IsDebugActive(): bool; transitioning macro - HasAccessCheckFailed( - context: Context, nativeContext: Context, promiseFun: Object, - executor: Object): bool { - BranchIfAccessCheckFailed(context, nativeContext, promiseFun, executor) + HasAccessCheckFailed(implicit context: Context)( + nativeContext: NativeContext, promiseFun: JSAny, executor: JSAny): bool { + BranchIfAccessCheckFailed(nativeContext, promiseFun, executor) otherwise return true; return false; } @@ -44,8 +39,6 @@ namespace promise { extern macro ConstructorBuiltinsAssembler::EmitFastNewObject( Context, JSFunction, JSReceiver): JSObject; - extern macro PromiseBuiltinsAssembler::PromiseInit(JSPromise): void; - extern macro PromiseBuiltinsAssembler::IsPromiseHookEnabledOrHasAsyncEventDelegate(): bool; @@ -68,7 +61,7 @@ namespace promise { context[NativeContextSlot::PROMISE_FUNCTION_INDEX]); // Silently fail if the stack looks fishy. - if (HasAccessCheckFailed(context, context, promiseFun, executor)) { + if (HasAccessCheckFailed(context, promiseFun, executor)) { IncrementUseCounter( context, SmiConstant(kPromiseConstructorReturnedUndefined)); return Undefined; @@ -76,7 +69,7 @@ namespace promise { let result: JSPromise; if (promiseFun == newTarget) { - result = AllocateAndInitJSPromise(context); + result = NewJSPromise(); } else { result = UnsafeCast(EmitFastNewObject( context, promiseFun, UnsafeCast(newTarget))); diff --git a/src/builtins/promise-finally.tq b/src/builtins/promise-finally.tq index 08e3181bff..32028b819d 100644 --- a/src/builtins/promise-finally.tq +++ b/src/builtins/promise-finally.tq @@ -48,11 +48,6 @@ namespace promise { return AllocateFunctionWithMapAndContext(map, throwerInfo, throwerContext); } - extern transitioning macro - PromiseBuiltinsAssembler::InvokeThen(NativeContext, JSAny, JSAny): JSAny; - extern transitioning macro PromiseBuiltinsAssembler::InvokeThen( - NativeContext, JSAny, JSAny, JSAny): JSAny; - transitioning javascript builtin PromiseCatchFinally(js-implicit context: Context, receiver: JSAny)( reason: JSAny): JSAny { diff --git a/src/builtins/promise-misc.tq b/src/builtins/promise-misc.tq new file mode 100644 index 0000000000..7996cc5b3d --- /dev/null +++ b/src/builtins/promise-misc.tq @@ -0,0 +1,236 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include 'src/builtins/builtins-promise.h' +#include 'src/builtins/builtins-promise-gen.h' + +namespace runtime { + extern transitioning runtime + AllowDynamicFunction(implicit context: Context)(JSAny): JSAny; +} + +// Unsafe functions that should be used very carefully. +namespace promise_internal { + extern macro PromiseBuiltinsAssembler::ZeroOutEmbedderOffsets(JSPromise): + void; + + extern macro PromiseBuiltinsAssembler::AllocateJSPromise(Context): HeapObject; + + extern macro PromiseBuiltinsAssembler::AllocatePromiseReactionJobTask( + Context): HeapObject; + +} + +namespace promise { + extern macro IsFunctionWithPrototypeSlotMap(Map): bool; + + @export + macro PromiseHasHandler(promise: JSPromise): bool { + return promise.HasHandler(); + } + + @export + macro PromiseInit(promise: JSPromise): void { + assert(PromiseState::kPending == 0); + promise.reactions_or_result = kZero; + promise.flags = 0; + promise_internal::ZeroOutEmbedderOffsets(promise); + } + + macro InnerNewJSPromise(implicit context: Context)(): JSPromise { + const nativeContext = LoadNativeContext(context); + const promiseFun = UnsafeCast( + nativeContext[NativeContextSlot::PROMISE_FUNCTION_INDEX]); + assert(IsFunctionWithPrototypeSlotMap(promiseFun.map)); + const promiseMap = UnsafeCast(promiseFun.prototype_or_initial_map); + const promiseHeapObject = promise_internal::AllocateJSPromise(context); + promiseHeapObject.map = promiseMap; + const promise = UnsafeCast(promiseHeapObject); + promise.properties_or_hash = kEmptyFixedArray; + promise.elements = kEmptyFixedArray; + promise.reactions_or_result = kZero; + promise.flags = 0; + return promise; + } + + macro NewPromiseReactionJobTask(implicit context: Context)( + map: Map, handlerContext: Context, argument: Object, + handler: Callable|Undefined, + promiseOrCapability: JSPromise|PromiseCapability| + Undefined): PromiseReactionJobTask { + const taskHeapObject = + promise_internal::AllocatePromiseReactionJobTask(context); + taskHeapObject.map = map; + const jobTask = UnsafeCast(taskHeapObject); + jobTask.argument = argument; + jobTask.context = handlerContext; + jobTask.handler = handler; + jobTask.promise_or_capability = promiseOrCapability; + return jobTask; + } + + // These allocate and initialize a promise with pending state and + // undefined fields. + // + // This uses the given parent as the parent promise for the promise + // init hook. + @export + transitioning macro NewJSPromise(implicit context: Context)(parent: Object): + JSPromise { + const instance = InnerNewJSPromise(); + PromiseInit(instance); + if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) { + runtime::PromiseHookInit(instance, parent); + } + return instance; + } + + // This uses undefined as the parent promise for the promise init + // hook. + @export + transitioning macro NewJSPromise(implicit context: Context)(): JSPromise { + return NewJSPromise(Undefined); + } + + // This allocates and initializes a promise with the given state and + // fields. + @export + transitioning macro NewJSPromise(implicit context: Context)( + status: constexpr PromiseState, result: JSAny): JSPromise { + assert(status != PromiseState::kPending); + assert(kJSPromiseStatusShift == 0); + + const instance = InnerNewJSPromise(); + instance.reactions_or_result = result; + instance.SetStatus(status); + promise_internal::ZeroOutEmbedderOffsets(instance); + + if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) { + runtime::PromiseHookInit(instance, Undefined); + } + return instance; + } + + macro NewPromiseReaction(implicit context: Context)( + next: Zero|PromiseReaction, + promiseOrCapability: JSPromise|PromiseCapability|Undefined, + fulfillHandler: Callable|Undefined, + rejectHandler: Callable|Undefined): PromiseReaction { + return new PromiseReaction{ + map: PromiseReactionMapConstant(), + next: next, + reject_handler: rejectHandler, + fulfill_handler: fulfillHandler, + promise_or_capability: promiseOrCapability + }; + } + + extern macro PromiseResolveThenableJobTaskMapConstant(): Map; + + macro NewPromiseResolveThenableJobTask(implicit context: Context)( + promiseToResolve: JSPromise, then: JSReceiver, thenable: JSReceiver, + thenableContext: Context): PromiseResolveThenableJobTask { + return new PromiseResolveThenableJobTask{ + map: PromiseResolveThenableJobTaskMapConstant(), + context: thenableContext, + promise_to_resolve: promiseToResolve, + then: then, + thenable: thenable + }; + } + + struct InvokeThenOneArgFunctor { + transitioning + macro Call( + nativeContext: NativeContext, then: JSAny, receiver: JSAny, arg1: JSAny, + _arg2: JSAny): JSAny { + return Call(nativeContext, then, receiver, arg1); + } + } + + struct InvokeThenTwoArgFunctor { + transitioning + macro Call( + nativeContext: NativeContext, then: JSAny, receiver: JSAny, arg1: JSAny, + arg2: JSAny): JSAny { + return Call(nativeContext, then, receiver, arg1, arg2); + } + } + + transitioning + macro InvokeThen(implicit context: Context)( + nativeContext: NativeContext, receiver: JSAny, arg1: JSAny, arg2: JSAny, + callFunctor: F): JSAny { + // We can skip the "then" lookup on {receiver} if it's [[Prototype]] + // is the (initial) Promise.prototype and the Promise#then protector + // is intact, as that guards the lookup path for the "then" property + // on JSPromise instances which have the (initial) %PromisePrototype%. + if (!Is(receiver) && + IsPromiseThenLookupChainIntact( + nativeContext, UnsafeCast(receiver).map)) { + const then = UnsafeCast( + nativeContext[NativeContextSlot::PROMISE_THEN_INDEX]); + return callFunctor.Call(nativeContext, then, receiver, arg1, arg2); + } else + deferred { + const then = UnsafeCast(GetProperty(receiver, kThenString)); + return callFunctor.Call(nativeContext, then, receiver, arg1, arg2); + } + } + + transitioning + macro InvokeThen(implicit context: Context)( + nativeContext: NativeContext, receiver: JSAny, arg: JSAny): JSAny { + return InvokeThen( + nativeContext, receiver, arg, Undefined, InvokeThenOneArgFunctor{}); + } + + transitioning + macro InvokeThen(implicit context: Context)( + nativeContext: NativeContext, receiver: JSAny, arg1: JSAny, + arg2: JSAny): JSAny { + return InvokeThen( + nativeContext, receiver, arg1, arg2, InvokeThenTwoArgFunctor{}); + } + + transitioning + macro BranchIfAccessCheckFailed(implicit context: Context)( + nativeContext: NativeContext, promiseConstructor: JSAny, + executor: JSAny): void labels IfNoAccess { + try { + // If executor is a bound function, load the bound function until we've + // reached an actual function. + let foundExecutor = executor; + while (true) { + typeswitch (foundExecutor) { + case (f: JSFunction): { + // Load the context from the function and compare it to the Promise + // constructor's context. If they match, everything is fine, + // otherwise, bail out to the runtime. + const functionContext = f.context; + const nativeFunctionContext = LoadNativeContext(functionContext); + if (TaggedEqual(nativeContext, nativeFunctionContext)) { + goto HasAccess; + } else { + goto CallRuntime; + } + } + case (b: JSBoundFunction): { + foundExecutor = b.bound_target_function; + } + case (Object): { + goto CallRuntime; + } + } + } + } + label CallRuntime deferred { + const result = runtime::AllowDynamicFunction(promiseConstructor); + if (result != True) { + goto IfNoAccess; + } + } + label HasAccess {} + } +} diff --git a/src/builtins/promise-resolve.tq b/src/builtins/promise-resolve.tq index 386f264a2c..af7dd7afa0 100644 --- a/src/builtins/promise-resolve.tq +++ b/src/builtins/promise-resolve.tq @@ -64,7 +64,7 @@ namespace promise { if (promiseFun == constructor) { // This adds a fast path for native promises that don't need to // create NewPromiseCapability. - const result = AllocateAndInitJSPromise(context); + const result = NewJSPromise(); ResolvePromise(context, result, value); return result; } else @@ -85,10 +85,6 @@ namespace promise { const kThenString: String = ThenStringConstant(); - extern macro PromiseBuiltinsAssembler::AllocatePromiseResolveThenableJobTask( - JSPromise, JSReceiver, JSReceiver, - Context): PromiseResolveThenableJobTask; - transitioning builtin ResolvePromise(implicit context: Context)(promise: JSPromise, resolution: JSAny): JSAny { @@ -182,7 +178,7 @@ namespace promise { // 12. Perform EnqueueJob("PromiseJobs", PromiseResolveThenableJob, // «promise, resolution, thenAction»). const nativeContext = LoadNativeContext(context); - const task = AllocatePromiseResolveThenableJobTask( + const task = NewPromiseResolveThenableJobTask( promise, UnsafeCast(then), UnsafeCast(resolution), nativeContext); return EnqueueMicrotask(nativeContext, task); diff --git a/src/builtins/promise-then.tq b/src/builtins/promise-then.tq index 47d63af7f9..45f8fd0c81 100644 --- a/src/builtins/promise-then.tq +++ b/src/builtins/promise-then.tq @@ -6,22 +6,16 @@ namespace promise { - extern macro - PromiseBuiltinsAssembler::BranchIfPromiseSpeciesLookupChainIntact( - NativeContext, Map): never labels IsIntact, - IsNotIntact; - macro IsPromiseSpeciesLookupChainIntact( nativeContext: NativeContext, promiseMap: Map): bool { - BranchIfPromiseSpeciesLookupChainIntact(nativeContext, promiseMap) - otherwise return true, return false; + const promisePrototype = + nativeContext[NativeContextSlot::PROMISE_PROTOTYPE_INDEX]; + if (IsForceSlowPath()) return false; + if (promiseMap.prototype != promisePrototype) return false; + return !IsPromiseSpeciesProtectorCellInvalid(); } - extern macro - PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Context, Object): - JSPromise; - // https://tc39.es/ecma262/#sec-promise.prototype.then transitioning javascript builtin PromisePrototypeThen(js-implicit context: NativeContext, receiver: JSAny)( @@ -54,7 +48,7 @@ namespace promise { } } label AllocateAndInit { - const resultJSPromise = AllocateAndInitJSPromise(context, promise); + const resultJSPromise = NewJSPromise(promise); resultPromiseOrCapability = resultJSPromise; resultPromise = resultJSPromise; } diff --git a/src/codegen/code-stub-assembler.cc b/src/codegen/code-stub-assembler.cc index 609189e183..702a64d091 100644 --- a/src/codegen/code-stub-assembler.cc +++ b/src/codegen/code-stub-assembler.cc @@ -6010,6 +6010,13 @@ TNode CodeStubAssembler::IsPromiseReaction( return HasInstanceType(object, PROMISE_REACTION_TYPE); } +TNode CodeStubAssembler::IsPromiseReactionJobTask( + TNode object) { + TNode instance_type = LoadInstanceType(object); + return IsInRange(instance_type, FIRST_PROMISE_REACTION_JOB_TASK_TYPE, + LAST_PROMISE_REACTION_JOB_TASK_TYPE); +} + TNode CodeStubAssembler::IsPromiseRejectReactionJobTask( SloppyTNode object) { return HasInstanceType(object, PROMISE_REJECT_REACTION_JOB_TASK_TYPE); diff --git a/src/codegen/code-stub-assembler.h b/src/codegen/code-stub-assembler.h index 3601c6e112..6eb9baef69 100644 --- a/src/codegen/code-stub-assembler.h +++ b/src/codegen/code-stub-assembler.h @@ -129,6 +129,8 @@ enum class PrimitiveType { kBoolean, kNumber, kString, kSymbol }; V(PromiseReactionMap, promise_reaction_map, PromiseReactionMap) \ V(PromiseRejectReactionJobTaskMap, promise_reject_reaction_job_task_map, \ PromiseRejectReactionJobTaskMap) \ + V(PromiseResolveThenableJobTaskMap, promise_resolve_thenable_job_task_map, \ + PromiseResolveThenableJobTaskMap) \ V(prototype_string, prototype_string, PrototypeString) \ V(PrototypeInfoMap, prototype_info_map, PrototypeInfoMap) \ V(replace_symbol, replace_symbol, ReplaceSymbol) \ @@ -2548,6 +2550,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler TNode IsPropertyArray(SloppyTNode object); TNode IsPropertyCell(SloppyTNode object); TNode IsPromiseReaction(SloppyTNode object); + TNode IsPromiseReactionJobTask(TNode object); TNode IsPromiseRejectReactionJobTask(SloppyTNode object); TNode IsPromiseFulfillReactionJobTask(SloppyTNode object); TNode IsPrototypeInitialArrayPrototype(SloppyTNode context, diff --git a/test/cctest/test-code-stub-assembler.cc b/test/cctest/test-code-stub-assembler.cc index 4c9dcc3689..623ff0d0f3 100644 --- a/test/cctest/test-code-stub-assembler.cc +++ b/test/cctest/test-code-stub-assembler.cc @@ -2449,7 +2449,7 @@ TEST(IsPromiseHookEnabled) { CHECK_EQ(ReadOnlyRoots(isolate).false_value(), *result); } -TEST(AllocateAndInitJSPromise) { +TEST(NewJSPromise) { Isolate* isolate(CcTest::InitIsolateOnce()); const int kNumParams = 1; @@ -2457,7 +2457,7 @@ TEST(AllocateAndInitJSPromise) { PromiseBuiltinsAssembler m(asm_tester.state()); Node* const context = m.Parameter(kNumParams + 2); - const TNode promise = m.AllocateAndInitJSPromise(m.CAST(context)); + const TNode promise = m.NewJSPromise(m.CAST(context)); m.Return(promise); FunctionTester ft(asm_tester.GenerateCode(), kNumParams); @@ -2466,7 +2466,7 @@ TEST(AllocateAndInitJSPromise) { CHECK(result->IsJSPromise()); } -TEST(AllocateAndSetJSPromise) { +TEST(NewJSPromise2) { Isolate* isolate(CcTest::InitIsolateOnce()); const int kNumParams = 1; @@ -2474,8 +2474,8 @@ TEST(AllocateAndSetJSPromise) { PromiseBuiltinsAssembler m(asm_tester.state()); Node* const context = m.Parameter(kNumParams + 2); - const TNode promise = m.AllocateAndSetJSPromise( - m.CAST(context), v8::Promise::kRejected, m.SmiConstant(1)); + const TNode promise = + m.NewJSPromise(m.CAST(context), v8::Promise::kRejected, m.SmiConstant(1)); m.Return(promise); FunctionTester ft(asm_tester.GenerateCode(), kNumParams); @@ -2538,7 +2538,7 @@ TEST(PromiseHasHandler) { Node* const context = m.Parameter(kNumParams + 2); const TNode promise = - m.AllocateAndInitJSPromise(m.CAST(context), m.UndefinedConstant()); + m.NewJSPromise(m.CAST(context), m.UndefinedConstant()); m.Return(m.SelectBooleanConstant(m.PromiseHasHandler(promise))); FunctionTester ft(asm_tester.GenerateCode(), kNumParams); @@ -2557,7 +2557,7 @@ TEST(CreatePromiseResolvingFunctionsContext) { const TNode context = m.CAST(m.Parameter(kNumParams + 2)); const TNode native_context = m.LoadNativeContext(context); const TNode promise = - m.AllocateAndInitJSPromise(context, m.UndefinedConstant()); + m.NewJSPromise(context, m.UndefinedConstant()); const TNode promise_context = m.CreatePromiseResolvingFunctionsContext( context, promise, m.BooleanConstant(false), native_context); @@ -2585,7 +2585,7 @@ TEST(CreatePromiseResolvingFunctions) { Node* const context = m.Parameter(kNumParams + 2); const TNode native_context = m.LoadNativeContext(context); const TNode promise = - m.AllocateAndInitJSPromise(m.CAST(context), m.UndefinedConstant()); + m.NewJSPromise(m.CAST(context), m.UndefinedConstant()); PromiseResolvingFunctions funcs = m.CreatePromiseResolvingFunctions( m.CAST(context), promise, m.BooleanConstant(false), native_context); Node *resolve = funcs.resolve, *reject = funcs.reject; @@ -2675,7 +2675,7 @@ TEST(AllocateFunctionWithMapAndContext) { const TNode context = m.CAST(m.Parameter(kNumParams + 2)); const TNode native_context = m.LoadNativeContext(context); const TNode promise = - m.AllocateAndInitJSPromise(context, m.UndefinedConstant()); + m.NewJSPromise(context, m.UndefinedConstant()); TNode promise_context = m.CreatePromiseResolvingFunctionsContext( context, promise, m.BooleanConstant(false), native_context); TNode resolve_info = m.LoadContextElement(