[promises] Port PromiseConstructor to Torque.
Bug: v8:9838 Change-Id: Iceeb7e274c0cc9fd7066fa538818aebf23ce2678 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1925566 Commit-Queue: Joshua Litt <joshualitt@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Cr-Commit-Position: refs/heads/master@{#65176}
This commit is contained in:
parent
3114000ac1
commit
95e9ac05de
1
BUILD.gn
1
BUILD.gn
@ -972,6 +972,7 @@ torque_files = [
|
||||
"src/builtins/object-fromentries.tq",
|
||||
"src/builtins/object.tq",
|
||||
"src/builtins/promise-abstract-operations.tq",
|
||||
"src/builtins/promise-constructor.tq",
|
||||
"src/builtins/proxy-constructor.tq",
|
||||
"src/builtins/proxy-delete-property.tq",
|
||||
"src/builtins/proxy-get-property.tq",
|
||||
|
@ -717,8 +717,6 @@ namespace internal {
|
||||
TFJ(PromiseGetCapabilitiesExecutor, 2, kReceiver, kResolve, kReject) \
|
||||
TFJ(PromiseConstructorLazyDeoptContinuation, 4, kReceiver, kPromise, \
|
||||
kReject, kException, kResult) \
|
||||
/* ES6 #sec-promise-executor */ \
|
||||
TFJ(PromiseConstructor, 1, kReceiver, kExecutor) \
|
||||
CPP(IsPromise) \
|
||||
/* ES #sec-promise.prototype.then */ \
|
||||
TFJ(PromisePrototypeThen, 2, kReceiver, kOnFulfilled, kOnRejected) \
|
||||
|
@ -441,7 +441,8 @@ void PromiseBuiltinsAssembler::BranchIfPromiseThenLookupChainIntact(
|
||||
|
||||
void PromiseBuiltinsAssembler::BranchIfAccessCheckFailed(
|
||||
SloppyTNode<Context> context, SloppyTNode<Context> native_context,
|
||||
Node* promise_constructor, Node* executor, Label* if_noaccess) {
|
||||
TNode<Object> promise_constructor, TNode<Object> executor,
|
||||
Label* if_noaccess) {
|
||||
VARIABLE(var_executor, MachineRepresentation::kTagged);
|
||||
var_executor.Bind(executor);
|
||||
Label has_access(this), call_runtime(this, Label::kDeferred);
|
||||
@ -528,119 +529,6 @@ TF_BUILTIN(PromiseConstructorLazyDeoptContinuation, PromiseBuiltinsAssembler) {
|
||||
Return(promise);
|
||||
}
|
||||
|
||||
// ES6 #sec-promise-executor
|
||||
TF_BUILTIN(PromiseConstructor, PromiseBuiltinsAssembler) {
|
||||
TNode<Object> executor = CAST(Parameter(Descriptor::kExecutor));
|
||||
TNode<Object> new_target = CAST(Parameter(Descriptor::kJSNewTarget));
|
||||
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
|
||||
Isolate* isolate = this->isolate();
|
||||
|
||||
Label if_targetisundefined(this, Label::kDeferred);
|
||||
|
||||
GotoIf(IsUndefined(new_target), &if_targetisundefined);
|
||||
|
||||
Label if_notcallable(this, Label::kDeferred);
|
||||
|
||||
GotoIf(TaggedIsSmi(executor), &if_notcallable);
|
||||
|
||||
const TNode<Map> executor_map = LoadMap(CAST(executor));
|
||||
GotoIfNot(IsCallableMap(executor_map), &if_notcallable);
|
||||
|
||||
const TNode<NativeContext> native_context = LoadNativeContext(context);
|
||||
const TNode<JSFunction> promise_fun =
|
||||
CAST(LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX));
|
||||
Node* const is_debug_active = IsDebugActive();
|
||||
Label if_targetisnotmodified(this),
|
||||
if_targetismodified(this, Label::kDeferred), run_executor(this),
|
||||
debug_push(this), if_noaccess(this, Label::kDeferred);
|
||||
|
||||
BranchIfAccessCheckFailed(context, native_context, promise_fun, executor,
|
||||
&if_noaccess);
|
||||
|
||||
Branch(TaggedEqual(promise_fun, new_target), &if_targetisnotmodified,
|
||||
&if_targetismodified);
|
||||
|
||||
VARIABLE(var_result, MachineRepresentation::kTagged);
|
||||
VARIABLE(var_reject_call, MachineRepresentation::kTagged);
|
||||
VARIABLE(var_reason, MachineRepresentation::kTagged);
|
||||
|
||||
BIND(&if_targetisnotmodified);
|
||||
{
|
||||
const TNode<JSPromise> instance = AllocateAndInitJSPromise(context);
|
||||
var_result.Bind(instance);
|
||||
Goto(&debug_push);
|
||||
}
|
||||
|
||||
BIND(&if_targetismodified);
|
||||
{
|
||||
ConstructorBuiltinsAssembler constructor_assembler(this->state());
|
||||
TNode<JSObject> instance = constructor_assembler.EmitFastNewObject(
|
||||
context, promise_fun, CAST(new_target));
|
||||
PromiseInit(instance);
|
||||
var_result.Bind(instance);
|
||||
|
||||
GotoIfNot(IsPromiseHookEnabledOrHasAsyncEventDelegate(), &debug_push);
|
||||
CallRuntime(Runtime::kPromiseHookInit, context, instance,
|
||||
UndefinedConstant());
|
||||
Goto(&debug_push);
|
||||
}
|
||||
|
||||
BIND(&debug_push);
|
||||
{
|
||||
GotoIfNot(is_debug_active, &run_executor);
|
||||
CallRuntime(Runtime::kDebugPushPromise, context, var_result.value());
|
||||
Goto(&run_executor);
|
||||
}
|
||||
|
||||
BIND(&run_executor);
|
||||
{
|
||||
Label out(this), if_rejectpromise(this), debug_pop(this, Label::kDeferred);
|
||||
|
||||
PromiseResolvingFunctions funcs = CreatePromiseResolvingFunctions(
|
||||
context, CAST(var_result.value()), TrueConstant(), native_context);
|
||||
Node *resolve = funcs.resolve, *reject = funcs.reject;
|
||||
|
||||
Node* const maybe_exception = CallJS(
|
||||
CodeFactory::Call(isolate, ConvertReceiverMode::kNullOrUndefined),
|
||||
context, executor, UndefinedConstant(), resolve, reject);
|
||||
|
||||
GotoIfException(maybe_exception, &if_rejectpromise, &var_reason);
|
||||
Branch(is_debug_active, &debug_pop, &out);
|
||||
|
||||
BIND(&if_rejectpromise);
|
||||
{
|
||||
CallJS(CodeFactory::Call(isolate, ConvertReceiverMode::kNullOrUndefined),
|
||||
context, reject, UndefinedConstant(), var_reason.value());
|
||||
Branch(is_debug_active, &debug_pop, &out);
|
||||
}
|
||||
|
||||
BIND(&debug_pop);
|
||||
{
|
||||
CallRuntime(Runtime::kDebugPopPromise, context);
|
||||
Goto(&out);
|
||||
}
|
||||
BIND(&out);
|
||||
Return(var_result.value());
|
||||
}
|
||||
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
BIND(&if_targetisundefined);
|
||||
ThrowTypeError(context, MessageTemplate::kNotAPromise, new_target);
|
||||
|
||||
// 2. If IsCallable(executor) is false, throw a TypeError exception.
|
||||
BIND(&if_notcallable);
|
||||
ThrowTypeError(context, MessageTemplate::kResolverNotAFunction, executor);
|
||||
|
||||
// Silently fail if the stack looks fishy.
|
||||
BIND(&if_noaccess);
|
||||
{
|
||||
const TNode<Smi> counter_id =
|
||||
SmiConstant(v8::Isolate::kPromiseConstructorReturnedUndefined);
|
||||
CallRuntime(Runtime::kIncrementUseCounter, context, counter_id);
|
||||
Return(UndefinedConstant());
|
||||
}
|
||||
}
|
||||
|
||||
// ES#sec-promise.prototype.then
|
||||
// Promise.prototype.then ( onFulfilled, onRejected )
|
||||
TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler) {
|
||||
|
@ -66,9 +66,13 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler {
|
||||
TNode<JSPromise> promise, TNode<Object> debug_event,
|
||||
TNode<NativeContext> native_context);
|
||||
|
||||
protected:
|
||||
void BranchIfAccessCheckFailed(SloppyTNode<Context> context,
|
||||
SloppyTNode<Context> native_context,
|
||||
TNode<Object> promise_constructor,
|
||||
TNode<Object> executor, Label* if_noaccess);
|
||||
void PromiseInit(Node* promise);
|
||||
|
||||
protected:
|
||||
void PromiseSetHasHandler(Node* promise);
|
||||
void PromiseSetHandledHint(Node* promise);
|
||||
|
||||
@ -106,11 +110,6 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler {
|
||||
template <typename... TArgs>
|
||||
Node* InvokeThen(Node* native_context, Node* receiver, TArgs... args);
|
||||
|
||||
void BranchIfAccessCheckFailed(SloppyTNode<Context> context,
|
||||
SloppyTNode<Context> native_context,
|
||||
Node* promise_constructor, Node* executor,
|
||||
Label* if_noaccess);
|
||||
|
||||
std::pair<Node*, Node*> CreatePromiseFinallyFunctions(Node* on_finally,
|
||||
Node* constructor,
|
||||
Node* native_context);
|
||||
|
108
src/builtins/promise-constructor.tq
Normal file
108
src/builtins/promise-constructor.tq
Normal file
@ -0,0 +1,108 @@
|
||||
// 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-constructor-gen.h'
|
||||
#include 'src/builtins/builtins-promise-gen.h'
|
||||
|
||||
namespace runtime {
|
||||
extern transitioning runtime
|
||||
DebugPushPromise(implicit context: Context)(JSAny): JSAny;
|
||||
|
||||
extern transitioning runtime
|
||||
DebugPopPromise(implicit context: Context)(): JSAny;
|
||||
|
||||
extern transitioning runtime
|
||||
PromiseHookInit(implicit context: Context)(Object, Object): JSAny;
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-promise-constructor
|
||||
namespace promise {
|
||||
|
||||
extern runtime IncrementUseCounter(Context, Smi): void;
|
||||
type UseCounterFeature extends int31
|
||||
constexpr 'v8::Isolate::UseCounterFeature';
|
||||
const kNotAPromise: constexpr MessageTemplate
|
||||
generates 'MessageTemplate::kNotAPromise';
|
||||
const kResolverNotAFunction: constexpr MessageTemplate
|
||||
generates 'MessageTemplate::kResolverNotAFunction';
|
||||
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)
|
||||
otherwise return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
extern macro ConstructorBuiltinsAssembler::EmitFastNewObject(
|
||||
Context, JSFunction, JSReceiver): JSObject;
|
||||
|
||||
extern macro PromiseBuiltinsAssembler::PromiseInit(Object): void;
|
||||
|
||||
extern macro
|
||||
PromiseBuiltinsAssembler::IsPromiseHookEnabledOrHasAsyncEventDelegate(): bool;
|
||||
|
||||
// https://tc39.es/ecma262/#sec-promise-executor
|
||||
transitioning javascript builtin
|
||||
PromiseConstructor(
|
||||
js-implicit context: NativeContext, receiver: JSAny,
|
||||
newTarget: JSAny)(executor: JSAny): JSAny {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if (newTarget == Undefined) {
|
||||
ThrowTypeError(kNotAPromise, newTarget);
|
||||
}
|
||||
|
||||
// 2. If IsCallable(executor) is false, throw a TypeError exception.
|
||||
if (!TaggedIsCallable(executor)) {
|
||||
ThrowTypeError(kResolverNotAFunction, executor);
|
||||
}
|
||||
|
||||
const promiseFun = UnsafeCast<JSFunction>(context[PROMISE_FUNCTION_INDEX]);
|
||||
|
||||
// Silently fail if the stack looks fishy.
|
||||
if (HasAccessCheckFailed(context, context, promiseFun, executor)) {
|
||||
IncrementUseCounter(
|
||||
context, SmiConstant(kPromiseConstructorReturnedUndefined));
|
||||
return Undefined;
|
||||
}
|
||||
|
||||
let result: JSPromise;
|
||||
if (promiseFun == newTarget) {
|
||||
result = AllocateAndInitJSPromise(context);
|
||||
} else {
|
||||
const resultObject = EmitFastNewObject(
|
||||
context, promiseFun, UnsafeCast<JSReceiver>(newTarget));
|
||||
PromiseInit(resultObject);
|
||||
result = UnsafeCast<JSPromise>(resultObject);
|
||||
if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) {
|
||||
runtime::PromiseHookInit(result, Undefined);
|
||||
}
|
||||
}
|
||||
|
||||
const isDebugActive = IsDebugActive();
|
||||
if (isDebugActive) runtime::DebugPushPromise(result);
|
||||
|
||||
const funcs = CreatePromiseResolvingFunctions(result, True, context);
|
||||
const resolve = funcs.resolve;
|
||||
const reject = funcs.reject;
|
||||
try {
|
||||
Call(context, UnsafeCast<Callable>(executor), Undefined, resolve, reject);
|
||||
} catch (e) {
|
||||
Call(context, reject, Undefined, e);
|
||||
}
|
||||
|
||||
if (isDebugActive) runtime::DebugPopPromise();
|
||||
return result;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user