[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:
Joshua Litt 2019-12-18 15:47:23 -08:00 committed by Commit Bot
parent 3e177c79bf
commit 5b74763e9a
9 changed files with 159 additions and 163 deletions

View File

@ -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",

View File

@ -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;

View File

@ -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) \

View File

@ -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()));
}

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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));
}
}

View 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));
}
}

View File

@ -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,
...
}