[promises] Port PromiseCatchFinally to Torque.
Also port a few smaller functions. Bug: v8:9838 Change-Id: I2245abe648378970a89331baa19af49f7f49359b Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1961942 Commit-Queue: Joshua Litt <joshualitt@chromium.org> Reviewed-by: Tobias Tebbi <tebbi@chromium.org> Cr-Commit-Position: refs/heads/master@{#65510}
This commit is contained in:
parent
3e177c79bf
commit
5b74763e9a
1
BUILD.gn
1
BUILD.gn
@ -987,6 +987,7 @@ torque_files = [
|
||||
"src/builtins/object.tq",
|
||||
"src/builtins/promise-abstract-operations.tq",
|
||||
"src/builtins/promise-constructor.tq",
|
||||
"src/builtins/promise-finally.tq",
|
||||
"src/builtins/promise-reaction-job.tq",
|
||||
"src/builtins/promise-resolve.tq",
|
||||
"src/builtins/promise-then.tq",
|
||||
|
@ -527,6 +527,7 @@ extern macro StringFromSingleCharCode(int32): String;
|
||||
extern macro StrictEqual(JSAny, JSAny): Boolean;
|
||||
extern macro SmiLexicographicCompare(Smi, Smi): Smi;
|
||||
extern runtime ReThrow(Context, JSAny): never;
|
||||
extern runtime Throw(implicit context: Context)(JSAny): never;
|
||||
extern runtime ThrowInvalidStringLength(Context): never;
|
||||
|
||||
extern operator '==' macro WordEqual(RawPtr, RawPtr): bool;
|
||||
|
@ -711,17 +711,11 @@ namespace internal {
|
||||
TFS(ForInFilter, kKey, kObject) \
|
||||
\
|
||||
/* Promise */ \
|
||||
TFJ(PromiseGetCapabilitiesExecutor, 2, kReceiver, kResolve, kReject) \
|
||||
TFJ(PromiseConstructorLazyDeoptContinuation, 4, kReceiver, kPromise, \
|
||||
kReject, kException, kResult) \
|
||||
CPP(IsPromise) \
|
||||
/* ES #sec-promise.prototype.catch */ \
|
||||
TFJ(PromisePrototypeCatch, 1, kReceiver, kOnRejected) \
|
||||
TFJ(PromisePrototypeFinally, 1, kReceiver, kOnFinally) \
|
||||
TFJ(PromiseThenFinally, 1, kReceiver, kValue) \
|
||||
TFJ(PromiseCatchFinally, 1, kReceiver, kReason) \
|
||||
TFJ(PromiseValueThunkFinally, 0, kReceiver) \
|
||||
TFJ(PromiseThrowerFinally, 0, kReceiver) \
|
||||
/* ES #sec-promise.all */ \
|
||||
TFJ(PromiseAll, 1, kReceiver, kIterable) \
|
||||
TFJ(PromiseAllResolveElementClosure, 1, kReceiver, kValue) \
|
||||
|
@ -221,48 +221,6 @@ PromiseBuiltinsAssembler::AllocatePromiseResolveThenableJobTask(
|
||||
return CAST(microtask);
|
||||
}
|
||||
|
||||
template <typename... TArgs>
|
||||
TNode<Object> PromiseBuiltinsAssembler::InvokeThen(Node* native_context,
|
||||
Node* receiver,
|
||||
TArgs... args) {
|
||||
CSA_ASSERT(this, IsNativeContext(native_context));
|
||||
|
||||
TVARIABLE(Object, var_result);
|
||||
Label if_fast(this), if_slow(this, Label::kDeferred), done(this, &var_result);
|
||||
GotoIf(TaggedIsSmi(receiver), &if_slow);
|
||||
const TNode<Map> receiver_map = LoadMap(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<Object> 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<Object> 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();
|
||||
}
|
||||
|
||||
Node* PromiseBuiltinsAssembler::CallResolve(Node* native_context,
|
||||
Node* constructor, Node* resolve,
|
||||
Node* value, Label* if_exception,
|
||||
@ -438,46 +396,6 @@ TF_BUILTIN(PromiseConstructorLazyDeoptContinuation, PromiseBuiltinsAssembler) {
|
||||
Return(promise);
|
||||
}
|
||||
|
||||
// ES#sec-promise.prototype.catch
|
||||
// Promise.prototype.catch ( onRejected )
|
||||
TF_BUILTIN(PromisePrototypeCatch, PromiseBuiltinsAssembler) {
|
||||
// 1. Let promise be the this value.
|
||||
Node* const receiver = Parameter(Descriptor::kReceiver);
|
||||
const TNode<Oddball> on_fulfilled = UndefinedConstant();
|
||||
Node* const on_rejected = Parameter(Descriptor::kOnRejected);
|
||||
Node* const context = Parameter(Descriptor::kContext);
|
||||
|
||||
// 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
|
||||
const TNode<NativeContext> native_context = LoadNativeContext(context);
|
||||
Return(InvokeThen(native_context, receiver, on_fulfilled, on_rejected));
|
||||
}
|
||||
|
||||
// ES6 #sec-getcapabilitiesexecutor-functions
|
||||
TF_BUILTIN(PromiseGetCapabilitiesExecutor, PromiseBuiltinsAssembler) {
|
||||
Node* const resolve = Parameter(Descriptor::kResolve);
|
||||
Node* const reject = Parameter(Descriptor::kReject);
|
||||
Node* const context = Parameter(Descriptor::kContext);
|
||||
|
||||
const TNode<PromiseCapability> capability =
|
||||
CAST(LoadContextElement(context, PromiseBuiltins::kCapabilitySlot));
|
||||
|
||||
Label if_alreadyinvoked(this, Label::kDeferred);
|
||||
GotoIfNot(IsUndefined(
|
||||
LoadObjectField(capability, PromiseCapability::kResolveOffset)),
|
||||
&if_alreadyinvoked);
|
||||
GotoIfNot(IsUndefined(
|
||||
LoadObjectField(capability, PromiseCapability::kRejectOffset)),
|
||||
&if_alreadyinvoked);
|
||||
|
||||
StoreObjectField(capability, PromiseCapability::kResolveOffset, resolve);
|
||||
StoreObjectField(capability, PromiseCapability::kRejectOffset, reject);
|
||||
|
||||
Return(UndefinedConstant());
|
||||
|
||||
BIND(&if_alreadyinvoked);
|
||||
ThrowTypeError(context, MessageTemplate::kPromiseExecutorAlreadyInvoked);
|
||||
}
|
||||
|
||||
std::pair<Node*, Node*> PromiseBuiltinsAssembler::CreatePromiseFinallyFunctions(
|
||||
Node* on_finally, Node* constructor, Node* native_context) {
|
||||
const TNode<Context> promise_context = AllocateSyntheticFunctionContext(
|
||||
@ -499,14 +417,6 @@ std::pair<Node*, Node*> PromiseBuiltinsAssembler::CreatePromiseFinallyFunctions(
|
||||
return std::make_pair(then_finally, catch_finally);
|
||||
}
|
||||
|
||||
TF_BUILTIN(PromiseValueThunkFinally, PromiseBuiltinsAssembler) {
|
||||
Node* const context = Parameter(Descriptor::kContext);
|
||||
|
||||
const TNode<Object> value =
|
||||
LoadContextElement(context, PromiseBuiltins::kValueSlot);
|
||||
Return(value);
|
||||
}
|
||||
|
||||
Node* PromiseBuiltinsAssembler::CreateValueThunkFunction(Node* value,
|
||||
Node* native_context) {
|
||||
const TNode<Context> value_thunk_context = AllocateSyntheticFunctionContext(
|
||||
@ -560,68 +470,6 @@ TF_BUILTIN(PromiseThenFinally, PromiseBuiltinsAssembler) {
|
||||
Return(InvokeThen(native_context, promise, value_thunk));
|
||||
}
|
||||
|
||||
TF_BUILTIN(PromiseThrowerFinally, PromiseBuiltinsAssembler) {
|
||||
Node* const context = Parameter(Descriptor::kContext);
|
||||
|
||||
const TNode<Object> reason =
|
||||
LoadContextElement(context, PromiseBuiltins::kValueSlot);
|
||||
CallRuntime(Runtime::kThrow, context, reason);
|
||||
Unreachable();
|
||||
}
|
||||
|
||||
Node* PromiseBuiltinsAssembler::CreateThrowerFunction(Node* reason,
|
||||
Node* native_context) {
|
||||
const TNode<Context> thrower_context = AllocateSyntheticFunctionContext(
|
||||
CAST(native_context),
|
||||
PromiseBuiltins::kPromiseValueThunkOrReasonContextLength);
|
||||
StoreContextElementNoWriteBarrier(thrower_context,
|
||||
PromiseBuiltins::kValueSlot, reason);
|
||||
const TNode<Map> map = CAST(LoadContextElement(
|
||||
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
|
||||
const TNode<SharedFunctionInfo> thrower_info = CAST(LoadContextElement(
|
||||
native_context, Context::PROMISE_THROWER_FINALLY_SHARED_FUN));
|
||||
const TNode<JSFunction> thrower =
|
||||
AllocateFunctionWithMapAndContext(map, thrower_info, thrower_context);
|
||||
return thrower;
|
||||
}
|
||||
|
||||
TF_BUILTIN(PromiseCatchFinally, PromiseBuiltinsAssembler) {
|
||||
CSA_ASSERT_JS_ARGC_EQ(this, 1);
|
||||
|
||||
Node* const reason = Parameter(Descriptor::kReason);
|
||||
Node* const context = Parameter(Descriptor::kContext);
|
||||
|
||||
// 1. Let onFinally be F.[[OnFinally]].
|
||||
const TNode<HeapObject> on_finally =
|
||||
CAST(LoadContextElement(context, PromiseBuiltins::kOnFinallySlot));
|
||||
|
||||
// 2. Assert: IsCallable(onFinally) is true.
|
||||
CSA_ASSERT(this, IsCallable(on_finally));
|
||||
|
||||
// 3. Let result be ? Call(onFinally).
|
||||
Node* result = CallJS(
|
||||
CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
|
||||
context, on_finally, UndefinedConstant());
|
||||
|
||||
// 4. Let C be F.[[Constructor]].
|
||||
const TNode<JSFunction> constructor =
|
||||
CAST(LoadContextElement(context, PromiseBuiltins::kConstructorSlot));
|
||||
|
||||
// 5. Assert: IsConstructor(C) is true.
|
||||
CSA_ASSERT(this, IsConstructor(constructor));
|
||||
|
||||
// 6. Let promise be ? PromiseResolve(C, result).
|
||||
const TNode<Object> promise =
|
||||
CallBuiltin(Builtins::kPromiseResolve, context, constructor, result);
|
||||
|
||||
// 7. Let thrower be equivalent to a function that throws reason.
|
||||
const TNode<NativeContext> native_context = LoadNativeContext(context);
|
||||
Node* const thrower = CreateThrowerFunction(reason, native_context);
|
||||
|
||||
// 8. Return ? Invoke(promise, "then", « thrower »).
|
||||
Return(InvokeThen(native_context, promise, thrower));
|
||||
}
|
||||
|
||||
TF_BUILTIN(PromisePrototypeFinally, PromiseBuiltinsAssembler) {
|
||||
CSA_ASSERT_JS_ARGC_EQ(this, 1);
|
||||
|
||||
@ -695,7 +543,7 @@ TF_BUILTIN(PromisePrototypeFinally, PromiseBuiltinsAssembler) {
|
||||
|
||||
// 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
|
||||
BIND(&perform_finally);
|
||||
Return(InvokeThen(native_context, receiver, var_then_finally.value(),
|
||||
Return(InvokeThen(native_context, CAST(receiver), var_then_finally.value(),
|
||||
var_catch_finally.value()));
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,47 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler {
|
||||
TNode<NativeContext> native_context, TNode<Map> promise_map,
|
||||
Label* if_fast, Label* if_slow);
|
||||
|
||||
template <typename... TArgs>
|
||||
TNode<Object> InvokeThen(TNode<NativeContext> native_context,
|
||||
TNode<Object> 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<Map> 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<Object> 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<Object> 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
|
||||
@ -103,16 +144,12 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler {
|
||||
// intrinsic, otherwise we use the given resolve function.
|
||||
Node* CallResolve(Node* native_context, Node* constructor, Node* resolve,
|
||||
Node* value, Label* if_exception, Variable* var_exception);
|
||||
template <typename... TArgs>
|
||||
TNode<Object> InvokeThen(Node* native_context, Node* receiver, TArgs... args);
|
||||
|
||||
std::pair<Node*, Node*> CreatePromiseFinallyFunctions(Node* on_finally,
|
||||
Node* constructor,
|
||||
Node* native_context);
|
||||
Node* CreateValueThunkFunction(Node* value, Node* native_context);
|
||||
|
||||
Node* CreateThrowerFunction(Node* reason, Node* native_context);
|
||||
|
||||
using PromiseAllResolvingElementFunction =
|
||||
std::function<TNode<Object>(TNode<Context> context, TNode<Smi> index,
|
||||
TNode<NativeContext> native_context,
|
||||
|
@ -504,4 +504,23 @@ namespace promise {
|
||||
}
|
||||
}
|
||||
|
||||
const kPromiseExecutorAlreadyInvoked: constexpr MessageTemplate
|
||||
generates 'MessageTemplate::kPromiseExecutorAlreadyInvoked';
|
||||
|
||||
// https://tc39.es/ecma262/#sec-getcapabilitiesexecutor-functions
|
||||
transitioning javascript builtin
|
||||
PromiseGetCapabilitiesExecutor(
|
||||
js-implicit context: NativeContext,
|
||||
receiver: JSAny)(resolve: JSAny, reject: JSAny): JSAny {
|
||||
const capability =
|
||||
UnsafeCast<PromiseCapability>(context[kPromiseBuiltinsCapabilitySlot]);
|
||||
if (capability.resolve != Undefined || capability.reject != Undefined)
|
||||
deferred {
|
||||
ThrowTypeError(kPromiseExecutorAlreadyInvoked);
|
||||
}
|
||||
|
||||
capability.resolve = resolve;
|
||||
capability.reject = reject;
|
||||
return Undefined;
|
||||
}
|
||||
}
|
||||
|
@ -102,4 +102,16 @@ namespace promise {
|
||||
if (isDebugActive) runtime::DebugPopPromise();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Promise.prototype.catch ( onRejected )
|
||||
// https://tc39.es/ecma262/#sec-promise.prototype.catch
|
||||
transitioning javascript builtin
|
||||
PromisePrototypeCatch(js-implicit context: Context, receiver: JSAny)(
|
||||
onRejected: JSAny): JSAny {
|
||||
// 1. Let promise be the this value.
|
||||
// 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
|
||||
const nativeContext = LoadNativeContext(context);
|
||||
return UnsafeCast<JSAny>(
|
||||
InvokeThen(nativeContext, receiver, Undefined, onRejected));
|
||||
}
|
||||
}
|
||||
|
83
src/builtins/promise-finally.tq
Normal file
83
src/builtins/promise-finally.tq
Normal file
@ -0,0 +1,83 @@
|
||||
// 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 promise {
|
||||
|
||||
// TODO(joshualitt): The below ContextSlots are only available on synthetic
|
||||
// contexts created by the promise pipeline for use in the promise pipeline.
|
||||
// However, with Torque we should type the context and its slots to prevent
|
||||
// accidentially using these slots on contexts which don't support them.
|
||||
const kPromiseBuiltinsValueSlot: constexpr ContextSlot
|
||||
generates 'PromiseBuiltins::kValueSlot';
|
||||
const kPromiseBuiltinsOnFinallySlot: constexpr ContextSlot
|
||||
generates 'PromiseBuiltins::kOnFinallySlot';
|
||||
const kPromiseBuiltinsConstructorSlot: constexpr ContextSlot
|
||||
generates 'PromiseBuiltins::kConstructorSlot';
|
||||
const kPromiseBuiltinsPromiseValueThunkOrReasonContextLength: constexpr int31
|
||||
generates 'PromiseBuiltins::kPromiseValueThunkOrReasonContextLength';
|
||||
|
||||
transitioning javascript builtin
|
||||
PromiseValueThunkFinally(js-implicit context: Context, receiver: JSAny)():
|
||||
JSAny {
|
||||
return UnsafeCast<JSAny>(context[kPromiseBuiltinsValueSlot]);
|
||||
}
|
||||
|
||||
transitioning javascript builtin
|
||||
PromiseThrowerFinally(js-implicit context: Context, receiver: JSAny)():
|
||||
never {
|
||||
const reason = UnsafeCast<JSAny>(context[kPromiseBuiltinsValueSlot]);
|
||||
Throw(reason);
|
||||
}
|
||||
|
||||
macro CreateThrowerFunction(implicit context: Context)(
|
||||
nativeContext: NativeContext, reason: JSAny): JSFunction {
|
||||
const throwerContext = AllocateSyntheticFunctionContext(
|
||||
nativeContext, kPromiseBuiltinsPromiseValueThunkOrReasonContextLength);
|
||||
throwerContext[kPromiseBuiltinsValueSlot] = reason;
|
||||
const map = UnsafeCast<Map>(
|
||||
nativeContext
|
||||
[NativeContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX]);
|
||||
const throwerInfo = UnsafeCast<SharedFunctionInfo>(
|
||||
nativeContext[NativeContextSlot::PROMISE_THROWER_FINALLY_SHARED_FUN]);
|
||||
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 {
|
||||
// 1. Let onFinally be F.[[OnFinally]].
|
||||
// 2. Assert: IsCallable(onFinally) is true.
|
||||
const onFinally =
|
||||
UnsafeCast<Callable>(context[kPromiseBuiltinsOnFinallySlot]);
|
||||
|
||||
// 3. Let result be ? Call(onFinally).
|
||||
const result = Call(context, onFinally, Undefined);
|
||||
|
||||
// 4. Let C be F.[[Constructor]].
|
||||
const constructor =
|
||||
UnsafeCast<JSFunction>(context[kPromiseBuiltinsConstructorSlot]);
|
||||
|
||||
// 5. Assert: IsConstructor(C) is true.
|
||||
assert(IsConstructor(constructor));
|
||||
|
||||
// 6. Let promise be ? PromiseResolve(C, result).
|
||||
const promise = PromiseResolve(constructor, result);
|
||||
|
||||
// 7. Let thrower be equivalent to a function that throws reason.
|
||||
const nativeContext = LoadNativeContext(context);
|
||||
const thrower = CreateThrowerFunction(nativeContext, reason);
|
||||
|
||||
// 8. Return ? Invoke(promise, "then", « thrower »).
|
||||
return UnsafeCast<JSAny>(InvokeThen(nativeContext, promise, thrower));
|
||||
}
|
||||
|
||||
}
|
@ -45,6 +45,7 @@ extern enum NativeContextSlot extends intptr constexpr 'Context::Field' {
|
||||
PROMISE_CAPABILITY_DEFAULT_REJECT_SHARED_FUN_INDEX,
|
||||
PROMISE_GET_CAPABILITIES_EXECUTOR_SHARED_FUN,
|
||||
PROMISE_PROTOTYPE_INDEX,
|
||||
PROMISE_THROWER_FINALLY_SHARED_FUN,
|
||||
...
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user