Reland "[promises] Port Promise.race to Torque."

Fixes clusterfuzz bug.

This is a reland of 15ec4a09d3

Original change's description:
> [promises] Port Promise.race to Torque.
>
> Bug: v8:9838
> Change-Id: Iee3bcaa3a7149309c01d16be67d189ccc56bd0e8
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1965919
> Commit-Queue: Joshua Litt <joshualitt@chromium.org>
> Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#65562}

Bug: v8:9838
Change-Id: Id295a12023195511289d92517936733ab22cdf4b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1988542
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Commit-Queue: Joshua Litt <joshualitt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65613}
This commit is contained in:
Joshua Litt 2020-01-07 08:27:46 -08:00 committed by Commit Bot
parent c4b1774942
commit 766aeb9966
10 changed files with 187 additions and 137 deletions

View File

@ -988,6 +988,7 @@ torque_files = [
"src/builtins/promise-abstract-operations.tq", "src/builtins/promise-abstract-operations.tq",
"src/builtins/promise-constructor.tq", "src/builtins/promise-constructor.tq",
"src/builtins/promise-finally.tq", "src/builtins/promise-finally.tq",
"src/builtins/promise-race.tq",
"src/builtins/promise-reaction-job.tq", "src/builtins/promise-reaction-job.tq",
"src/builtins/promise-resolve.tq", "src/builtins/promise-resolve.tq",
"src/builtins/promise-then.tq", "src/builtins/promise-then.tq",

View File

@ -352,6 +352,7 @@ extern macro LengthStringConstant(): String;
extern macro NanConstant(): NaN; extern macro NanConstant(): NaN;
extern macro IteratorSymbolConstant(): PublicSymbol; extern macro IteratorSymbolConstant(): PublicSymbol;
extern macro MatchSymbolConstant(): Symbol; extern macro MatchSymbolConstant(): Symbol;
extern macro ReturnStringConstant(): String;
const TheHole: TheHole = TheHoleConstant(); const TheHole: TheHole = TheHoleConstant();
const Null: Null = NullConstant(); const Null: Null = NullConstant();
@ -360,6 +361,8 @@ const True: True = TrueConstant();
const False: False = FalseConstant(); const False: False = FalseConstant();
const kEmptyString: EmptyString = EmptyStringConstant(); const kEmptyString: EmptyString = EmptyStringConstant();
const kLengthString: String = LengthStringConstant(); const kLengthString: String = LengthStringConstant();
const kReturnString: String = ReturnStringConstant();
const kNaN: NaN = NanConstant(); const kNaN: NaN = NanConstant();
const kZero: Zero = %RawDownCast<Zero>(SmiConstant(0)); const kZero: Zero = %RawDownCast<Zero>(SmiConstant(0));

View File

@ -717,8 +717,6 @@ namespace internal {
/* ES #sec-promise.all */ \ /* ES #sec-promise.all */ \
TFJ(PromiseAll, 1, kReceiver, kIterable) \ TFJ(PromiseAll, 1, kReceiver, kIterable) \
TFJ(PromiseAllResolveElementClosure, 1, kReceiver, kValue) \ TFJ(PromiseAllResolveElementClosure, 1, kReceiver, kValue) \
/* ES #sec-promise.race */ \
TFJ(PromiseRace, 1, kReceiver, kIterable) \
/* ES #sec-promise.allsettled */ \ /* ES #sec-promise.allsettled */ \
TFJ(PromiseAllSettled, 1, kReceiver, kIterable) \ TFJ(PromiseAllSettled, 1, kReceiver, kIterable) \
TFJ(PromiseAllSettledResolveElementClosure, 1, kReceiver, kValue) \ TFJ(PromiseAllSettledResolveElementClosure, 1, kReceiver, kValue) \

View File

@ -944,140 +944,5 @@ TF_BUILTIN(PromiseAllSettledRejectElementClosure, PromiseBuiltinsAssembler) {
}); });
} }
// ES#sec-promise.race
// Promise.race ( iterable )
TF_BUILTIN(PromiseRace, PromiseBuiltinsAssembler) {
IteratorBuiltinsAssembler iter_assembler(state());
TVARIABLE(Object, var_exception, TheHoleConstant());
Node* const receiver = Parameter(Descriptor::kReceiver);
const TNode<Context> context = CAST(Parameter(Descriptor::kContext));
ThrowIfNotJSReceiver(context, CAST(receiver),
MessageTemplate::kCalledOnNonObject, "Promise.race");
// Let promiseCapability be ? NewPromiseCapability(C).
// Don't fire debugEvent so that forwarding the rejection through all does not
// trigger redundant ExceptionEvents
const TNode<Oddball> debug_event = FalseConstant();
const TNode<PromiseCapability> capability = CAST(CallBuiltin(
Builtins::kNewPromiseCapability, context, receiver, debug_event));
const TNode<Object> resolve =
LoadObjectField(capability, PromiseCapability::kResolveOffset);
const TNode<Object> reject =
LoadObjectField(capability, PromiseCapability::kRejectOffset);
Label close_iterator(this, Label::kDeferred);
Label reject_promise(this, Label::kDeferred);
// For catch prediction, don't treat the .then calls as handling it;
// instead, recurse outwards.
SetForwardingHandlerIfTrue(context, IsDebugActive(), reject);
// Let iterator be GetIterator(iterable).
// IfAbruptRejectPromise(iterator, promiseCapability).
Node* const iterable = Parameter(Descriptor::kIterable);
IteratorRecord iterator = iter_assembler.GetIterator(
context, iterable, &reject_promise, &var_exception);
// Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability).
{
// We can skip the "resolve" lookup on {constructor} if it's the
// Promise constructor and the Promise.resolve protector is intact,
// as that guards the lookup path for the "resolve" property on the
// Promise constructor.
Label loop(this), break_loop(this), if_slow(this, Label::kDeferred);
const TNode<NativeContext> native_context = LoadNativeContext(context);
TVARIABLE(Object, var_promise_resolve_function, UndefinedConstant());
GotoIfNotPromiseResolveLookupChainIntact(native_context, receiver,
&if_slow);
Goto(&loop);
BIND(&if_slow);
{
// 3. Let _promiseResolve_ be ? Get(_constructor_, `"resolve"`).
TNode<Object> resolve =
GetProperty(native_context, receiver, factory()->resolve_string());
GotoIfException(resolve, &close_iterator, &var_exception);
// 4. If IsCallable(_promiseResolve_) is *false*, throw a *TypeError*
// exception.
ThrowIfNotCallable(context, resolve, "resolve");
var_promise_resolve_function = resolve;
Goto(&loop);
}
BIND(&loop);
{
const TNode<Map> fast_iterator_result_map = CAST(LoadContextElement(
native_context, Context::ITERATOR_RESULT_MAP_INDEX));
// Let next be IteratorStep(iteratorRecord.[[Iterator]]).
// If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// ReturnIfAbrupt(next).
const TNode<JSReceiver> next = iter_assembler.IteratorStep(
context, iterator, &break_loop, fast_iterator_result_map,
&reject_promise, &var_exception);
// Let nextValue be IteratorValue(next).
// If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to
// true.
// ReturnIfAbrupt(nextValue).
const TNode<Object> next_value =
iter_assembler.IteratorValue(context, next, fast_iterator_result_map,
&reject_promise, &var_exception);
// Let nextPromise be ? Call(constructor, _promiseResolve_, « nextValue
// »).
Node* const next_promise = CallResolve(
native_context, receiver, var_promise_resolve_function.value(),
next_value, &close_iterator, &var_exception);
// Perform ? Invoke(nextPromise, "then", « resolveElement,
// resultCapability.[[Reject]] »).
const TNode<Object> then =
GetProperty(context, next_promise, factory()->then_string());
GotoIfException(then, &close_iterator, &var_exception);
Node* const then_call =
CallJS(CodeFactory::Call(isolate(),
ConvertReceiverMode::kNotNullOrUndefined),
context, then, next_promise, resolve, reject);
GotoIfException(then_call, &close_iterator, &var_exception);
// For catch prediction, mark that rejections here are semantically
// handled by the combined Promise.
SetPromiseHandledByIfTrue(context, IsDebugActive(), then_call, [=]() {
// Load promiseCapability.[[Promise]]
return LoadObjectField(capability, PromiseCapability::kPromiseOffset);
});
Goto(&loop);
}
BIND(&break_loop);
Return(LoadObjectField(capability, PromiseCapability::kPromiseOffset));
}
BIND(&close_iterator);
{
CSA_ASSERT(this, IsNotTheHole(var_exception.value()));
iter_assembler.IteratorCloseOnException(context, iterator, &reject_promise,
&var_exception);
}
BIND(&reject_promise);
{
const TNode<Object> reject =
LoadObjectField(capability, PromiseCapability::kRejectOffset);
CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
context, reject, UndefinedConstant(), var_exception.value());
const TNode<Object> promise =
LoadObjectField(capability, PromiseCapability::kPromiseOffset);
Return(promise);
}
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8

View File

@ -72,4 +72,31 @@ namespace iterator {
otherwise ThrowCalledNonCallable(iteratorMethod); otherwise ThrowCalledNonCallable(iteratorMethod);
return Call(context, iteratorCallable, receiver); return Call(context, iteratorCallable, receiver);
} }
transitioning
macro IteratorCloseOnException(implicit context: Context)(
iterator: IteratorRecord, exception: Object): never labels
IfException(Object) {
// Let return be ? GetMethod(iterator, "return").
let method: JSAny;
try {
method = GetProperty(iterator.object, kReturnString);
} catch (e) {
goto IfException(e);
}
// If return is undefined, return Completion(completion).
if (method == Undefined || method == Null) goto IfException(exception);
// Let innerResult be Call(return, iterator, « »).
// If an exception occurs, the original exception remains bound
try {
Call(context, UnsafeCast<Callable>(method), iterator.object);
} catch (_e) {
goto IfException(exception);
}
// (If completion.[[Type]] is throw) return Completion(completion).
goto IfException(exception);
}
} }

View File

@ -523,4 +523,16 @@ namespace promise {
capability.reject = reject; capability.reject = reject;
return Undefined; return Undefined;
} }
transitioning macro CallResolve(implicit context: Context)(
constructor: Constructor, resolve: JSAny, value: JSAny): JSAny {
// Undefined can never be a valid value for the resolve function,
// instead it is used as a special marker for the fast path.
if (resolve == Undefined) {
return PromiseResolve(constructor, value);
} else
deferred {
return Call(context, UnsafeCast<Callable>(resolve), constructor, value);
}
}
} }

View File

@ -0,0 +1,132 @@
// 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-gen.h'
namespace promise {
extern macro PromiseForwardingHandlerSymbolConstant(): Symbol;
const kPromiseForwardingHandlerSymbol: Symbol =
PromiseForwardingHandlerSymbolConstant();
extern macro PromiseHandledBySymbolConstant(): Symbol;
const kPromiseHandledBySymbol: Symbol = PromiseHandledBySymbolConstant();
extern macro ResolveStringConstant(): String;
const kResolveString: String = ResolveStringConstant();
extern macro SetPropertyStrict(Context, Object, Object, Object): Object;
extern macro IsPromiseResolveProtectorCellInvalid(): bool;
macro IsPromiseResolveLookupChainIntact(implicit context: Context)(
nativeContext: NativeContext, constructor: JSReceiver): bool {
if (IsForceSlowPath()) return false;
const promiseFun = UnsafeCast<JSFunction>(
nativeContext[NativeContextSlot::PROMISE_FUNCTION_INDEX]);
return promiseFun == constructor && !IsPromiseResolveProtectorCellInvalid();
}
// https://tc39.es/ecma262/#sec-promise.race
transitioning javascript builtin
PromiseRace(js-implicit context: Context, receiver: JSAny)(iterable: JSAny):
JSAny {
const receiver = Cast<JSReceiver>(receiver)
otherwise ThrowTypeError(
MessageTemplate::kCalledOnNonObject, 'Promise.race');
// Let promiseCapability be ? NewPromiseCapability(C).
// Don't fire debugEvent so that forwarding the rejection through all does
// not trigger redundant ExceptionEvents
const capability = NewPromiseCapability(receiver, False);
const resolve = capability.resolve;
const reject = capability.reject;
const promise = capability.promise;
// For catch prediction, don't treat the .then calls as handling it;
// instead, recurse outwards.
if (IsDebugActive()) deferred {
SetPropertyStrict(
context, reject, kPromiseForwardingHandlerSymbol, True);
}
try {
// Let iterator be GetIterator(iterable).
// IfAbruptRejectPromise(iterator, promiseCapability).
let i: iterator::IteratorRecord;
try {
i = iterator::GetIterator(iterable);
} catch (e) deferred {
goto Reject(e);
}
// Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability).
try {
// We can skip the "resolve" lookup on {constructor} if it's the
// Promise constructor and the Promise.resolve protector is intact,
// as that guards the lookup path for the "resolve" property on the
// Promise constructor.
const nativeContext = LoadNativeContext(context);
let promiseResolveFunction: JSAny = Undefined;
if (!IsPromiseResolveLookupChainIntact(nativeContext, receiver))
deferred {
// 3. Let _promiseResolve_ be ? Get(_constructor_, `"resolve"`).
const resolve = GetProperty(receiver, kResolveString);
// 4. If IsCallable(_promiseResolve_) is *false*, throw a
// *TypeError* exception.
promiseResolveFunction = Cast<Callable>(resolve)
otherwise ThrowTypeError(
MessageTemplate::kCalledNonCallable, 'resolve');
}
const fastIteratorResultMap = UnsafeCast<Map>(
nativeContext[NativeContextSlot::ITERATOR_RESULT_MAP_INDEX]);
while (true) {
let nextValue: JSAny;
try {
// Let next be IteratorStep(iteratorRecord.[[Iterator]]).
// If next is an abrupt completion, set iteratorRecord.[[Done]] to
// true. ReturnIfAbrupt(next).
const next: JSReceiver = iterator::IteratorStep(
i, fastIteratorResultMap) otherwise return promise;
// Let nextValue be IteratorValue(next).
// If nextValue is an abrupt completion, set iteratorRecord.[[Done]]
// to true.
// ReturnIfAbrupt(nextValue).
nextValue = iterator::IteratorValue(next, fastIteratorResultMap);
} catch (e) {
goto Reject(e);
}
// Let nextPromise be ? Call(constructor, _promiseResolve_, «
// nextValue »).
const nextPromise = CallResolve(
UnsafeCast<Constructor>(receiver), promiseResolveFunction,
nextValue);
// Perform ? Invoke(nextPromise, "then", « resolveElement,
// resultCapability.[[Reject]] »).
const then = GetProperty(nextPromise, kThenString);
const thenResult = Call(
context, then, nextPromise, UnsafeCast<JSAny>(resolve),
UnsafeCast<JSAny>(reject));
// For catch prediction, mark that rejections here are semantically
// handled by the combined Promise.
if (IsDebugActive() && !Is<JSPromise>(promise)) deferred {
SetPropertyStrict(
context, thenResult, kPromiseHandledBySymbol,
capability.promise);
}
}
} catch (e) deferred {
iterator::IteratorCloseOnException(i, e) otherwise Reject;
}
}
label Reject(exception: Object) deferred {
Call(
context, UnsafeCast<Callable>(reject), Undefined,
UnsafeCast<JSAny>(exception));
return promise;
}
unreachable;
}
}

View File

@ -120,8 +120,12 @@ enum class PrimitiveType { kBoolean, kNumber, kString, kSymbol };
V(OnePointerFillerMap, one_pointer_filler_map, OnePointerFillerMap) \ V(OnePointerFillerMap, one_pointer_filler_map, OnePointerFillerMap) \
V(PreparseDataMap, preparse_data_map, PreparseDataMap) \ V(PreparseDataMap, preparse_data_map, PreparseDataMap) \
V(PromiseCapabilityMap, promise_capability_map, PromiseCapabilityMap) \ V(PromiseCapabilityMap, promise_capability_map, PromiseCapabilityMap) \
V(promise_forwarding_handler_symbol, promise_forwarding_handler_symbol, \
PromiseForwardingHandlerSymbol) \
V(PromiseFulfillReactionJobTaskMap, promise_fulfill_reaction_job_task_map, \ V(PromiseFulfillReactionJobTaskMap, promise_fulfill_reaction_job_task_map, \
PromiseFulfillReactionJobTaskMap) \ PromiseFulfillReactionJobTaskMap) \
V(promise_handled_by_symbol, promise_handled_by_symbol, \
PromiseHandledBySymbol) \
V(PromiseReactionMap, promise_reaction_map, PromiseReactionMap) \ V(PromiseReactionMap, promise_reaction_map, PromiseReactionMap) \
V(PromiseRejectReactionJobTaskMap, promise_reject_reaction_job_task_map, \ V(PromiseRejectReactionJobTaskMap, promise_reject_reaction_job_task_map, \
PromiseRejectReactionJobTaskMap) \ PromiseRejectReactionJobTaskMap) \
@ -130,6 +134,7 @@ enum class PrimitiveType { kBoolean, kNumber, kString, kSymbol };
V(replace_symbol, replace_symbol, ReplaceSymbol) \ V(replace_symbol, replace_symbol, ReplaceSymbol) \
V(regexp_to_string, regexp_to_string, RegexpToString) \ V(regexp_to_string, regexp_to_string, RegexpToString) \
V(resolve_string, resolve_string, ResolveString) \ V(resolve_string, resolve_string, ResolveString) \
V(return_string, return_string, ReturnString) \
V(SharedFunctionInfoMap, shared_function_info_map, SharedFunctionInfoMap) \ V(SharedFunctionInfoMap, shared_function_info_map, SharedFunctionInfoMap) \
V(SloppyArgumentsElementsMap, sloppy_arguments_elements_map, \ V(SloppyArgumentsElementsMap, sloppy_arguments_elements_map, \
SloppyArgumentsElementsMap) \ SloppyArgumentsElementsMap) \

View File

@ -5,6 +5,7 @@
@generateCppClass @generateCppClass
extern class PromiseCapability extends Struct { extern class PromiseCapability extends Struct {
promise: JSReceiver|Undefined; promise: JSReceiver|Undefined;
// TODO(joshualitt): Can these be typed more specifically.
resolve: Object; resolve: Object;
reject: Object; reject: Object;
} }

View File

@ -0,0 +1,6 @@
// 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.
Promise.resolve = function() { return {}; };
Promise.race([function() {}]);