Commit bba473db authored by Caitlin Potter's avatar Caitlin Potter Committed by Commit Bot

[builtins] port Promise.race to CSA

- Implements the Promise.race algorithm using CodeStubAssembler.
- Delete src/js/promise.js, which is no longer needed.
- Migrate Promise constructor from slow to fast object in bootstrapper
  (per v8:5902)

Increases size of snapshot_blob.bin on an x64.release build by 1.27kb.

BUG=v8:5343
R=gsathya@chromium.org, cbruni@chromium.org

Change-Id: I751e7389bd6ba410109640fcd7960b6021540f2f
Reviewed-on: https://chromium-review.googlesource.com/535041
Commit-Queue: Caitlin Potter <caitp@igalia.com>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#46489}
parent d433911c
......@@ -551,7 +551,6 @@ action("js2c") {
"src/js/typedarray.js",
"src/js/collection.js",
"src/js/weak-collection.js",
"src/js/promise.js",
"src/js/messages.js",
"src/js/templates.js",
"src/js/spread.js",
......
......@@ -2072,6 +2072,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
SimpleInstallFunction(promise_fun, "all", Builtins::kPromiseAll, 1, true);
SimpleInstallFunction(promise_fun, "race", Builtins::kPromiseRace, 1, true);
SimpleInstallFunction(promise_fun, "resolve", Builtins::kPromiseResolve, 1,
true);
......@@ -2096,6 +2098,15 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
prototype, "catch", Builtins::kPromiseCatch, 1, true);
native_context()->set_promise_catch(*promise_catch);
// Force the Promise constructor to fast properties, so that we can use the
// fast paths for various things like
//
// x instanceof Promise
//
// etc. We should probably come up with a more principled approach once
// the JavaScript builtins are gone.
JSObject::MigrateSlowToFast(Handle<JSObject>::cast(promise_fun), 0,
"Bootstrapping");
Handle<Map> prototype_map(prototype->map());
Map::SetShouldBeFastPrototypeMap(prototype_map, true, isolate);
......
......@@ -787,6 +787,8 @@ namespace internal {
TFJ(PromiseThrowerFinally, 0) \
/* ES #sec-promise.all */ \
TFJ(PromiseAll, 1, kIterable) \
/* ES #sec-promise.race */ \
TFJ(PromiseRace, 1, kIterable) \
\
/* Proxy */ \
CPP(ProxyConstructor) \
......@@ -1111,6 +1113,7 @@ namespace internal {
V(PromiseAll) \
V(PromiseConstructor) \
V(PromiseHandle) \
V(PromiseRace) \
V(PromiseResolve) \
V(PromiseResolveClosure) \
V(RejectNativePromise) \
......
......@@ -996,6 +996,31 @@ void PromiseBuiltinsAssembler::InternalPromiseReject(Node* context,
PromiseFulfill(context, promise, value, v8::Promise::kRejected);
}
void PromiseBuiltinsAssembler::SetForwardingHandlerIfTrue(
Node* context, Node* condition, const NodeGenerator& object) {
Label done(this);
GotoIfNot(condition, &done);
CallRuntime(Runtime::kSetProperty, context, object(),
HeapConstant(factory()->promise_forwarding_handler_symbol()),
TrueConstant(), SmiConstant(STRICT));
Goto(&done);
BIND(&done);
}
void PromiseBuiltinsAssembler::SetPromiseHandledByIfTrue(
Node* context, Node* condition, Node* promise,
const NodeGenerator& handled_by) {
Label done(this);
GotoIfNot(condition, &done);
GotoIf(TaggedIsSmi(promise), &done);
GotoIfNot(HasInstanceType(promise, JS_PROMISE_TYPE), &done);
CallRuntime(Runtime::kSetProperty, context, promise,
HeapConstant(factory()->promise_handled_by_symbol()),
handled_by(), SmiConstant(STRICT));
Goto(&done);
BIND(&done);
}
// ES#sec-promise-reject-functions
// Promise Reject Functions
TF_BUILTIN(PromiseRejectClosure, PromiseBuiltinsAssembler) {
......@@ -1832,16 +1857,9 @@ Node* PromiseBuiltinsAssembler::PerformPromiseAll(
// For catch prediction, don't treat the .then calls as handling it;
// instead, recurse outwards.
{
Label did_set_forwarding_handler(this);
GotoIfNot(instrumenting, &did_set_forwarding_handler);
CallRuntime(Runtime::kSetProperty, context,
LoadObjectField(capability, JSPromiseCapability::kRejectOffset),
HeapConstant(factory()->promise_forwarding_handler_symbol()),
TrueConstant(), SmiConstant(STRICT));
Goto(&did_set_forwarding_handler);
BIND(&did_set_forwarding_handler);
}
SetForwardingHandlerIfTrue(
context, instrumenting,
LoadObjectField(capability, JSPromiseCapability::kRejectOffset));
Node* const native_context = LoadNativeContext(context);
Node* const array_map = LoadContextElement(
......@@ -1935,17 +1953,10 @@ Node* PromiseBuiltinsAssembler::PerformPromiseAll(
// For catch prediction, mark that rejections here are semantically
// handled by the combined Promise.
Label did_set_handled_by(this);
GotoIfNot(instrumenting, &did_set_handled_by);
GotoIf(TaggedIsSmi(then_call), &did_set_handled_by);
GotoIfNot(HasInstanceType(then_call, JS_PROMISE_TYPE), &did_set_handled_by);
CallRuntime(
Runtime::kSetProperty, context, then_call,
HeapConstant(factory()->promise_handled_by_symbol()),
LoadObjectField(capability, JSPromiseCapability::kPromiseOffset),
SmiConstant(STRICT));
Goto(&did_set_handled_by);
BIND(&did_set_handled_by);
SetPromiseHandledByIfTrue(context, instrumenting, then_call, [=]() {
// Load promiseCapability.[[Promise]]
return LoadObjectField(capability, JSPromiseCapability::kPromiseOffset);
});
// Set index to index + 1
var_index.Bind(NumberInc(var_index.value()));
......@@ -2146,5 +2157,120 @@ TF_BUILTIN(PromiseAllResolveElementClosure, PromiseBuiltinsAssembler) {
Return(UndefinedConstant());
}
// ES#sec-promise.race
// Promise.race ( iterable )
TF_BUILTIN(PromiseRace, PromiseBuiltinsAssembler) {
IteratorBuiltinsAssembler iter_assembler(state());
VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant());
Node* const receiver = Parameter(Descriptor::kReceiver);
Node* const context = Parameter(Descriptor::kContext);
ThrowIfNotJSReceiver(context, 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
Node* const debug_event = FalseConstant();
Node* const capability = NewPromiseCapability(context, receiver, debug_event);
Node* const resolve =
LoadObjectField(capability, JSPromiseCapability::kResolveOffset);
Node* const reject =
LoadObjectField(capability, JSPromiseCapability::kRejectOffset);
Node* const instrumenting = IsDebugActive();
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, instrumenting, reject);
// Let iterator be GetIterator(iterable).
// IfAbruptRejectPromise(iterator, promiseCapability).
Node* const iterable = Parameter(Descriptor::kIterable);
Node* const iterator = iter_assembler.GetIterator(
context, iterable, &reject_promise, &var_exception);
// Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability).
{
Label loop(this), break_loop(this);
Goto(&loop);
BIND(&loop);
{
Node* const native_context = LoadNativeContext(context);
Node* const fast_iterator_result_map = 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).
Node* const 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).
Node* const next_value =
iter_assembler.IteratorValue(context, next, fast_iterator_result_map,
&reject_promise, &var_exception);
// Let nextPromise be ? Invoke(constructor, "resolve", « nextValue »).
Node* const promise_resolve =
GetProperty(context, receiver, factory()->resolve_string());
GotoIfException(promise_resolve, &close_iterator, &var_exception);
Node* const next_promise = CallJS(CodeFactory::Call(isolate()), context,
promise_resolve, receiver, next_value);
GotoIfException(next_promise, &close_iterator, &var_exception);
// Perform ? Invoke(nextPromise, "then", « resolveElement,
// resultCapability.[[Reject]] »).
Node* const then =
GetProperty(context, next_promise, factory()->then_string());
GotoIfException(then, &close_iterator, &var_exception);
Node* const then_call = CallJS(CodeFactory::Call(isolate()), 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, instrumenting, then_call, [=]() {
// Load promiseCapability.[[Promise]]
return LoadObjectField(capability, JSPromiseCapability::kPromiseOffset);
});
Goto(&loop);
}
BIND(&break_loop);
Return(LoadObjectField(capability, JSPromiseCapability::kPromiseOffset));
}
BIND(&close_iterator);
{
CSA_ASSERT(this, IsNotTheHole(var_exception.value()));
iter_assembler.IteratorCloseOnException(context, iterator, &reject_promise,
&var_exception);
}
BIND(&reject_promise);
{
Node* const reject =
LoadObjectField(capability, JSPromiseCapability::kRejectOffset);
Callable callable = CodeFactory::Call(isolate());
CallJS(callable, context, reject, UndefinedConstant(),
var_exception.value());
Node* const promise =
LoadObjectField(capability, JSPromiseCapability::kPromiseOffset);
Return(promise);
}
}
} // namespace internal
} // namespace v8
......@@ -164,6 +164,16 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* IncrementSmiCell(Node* cell, Label* if_overflow = nullptr);
Node* DecrementSmiCell(Node* cell);
void SetForwardingHandlerIfTrue(Node* context, Node* condition,
const NodeGenerator& object);
inline void SetForwardingHandlerIfTrue(Node* context, Node* condition,
Node* object) {
return SetForwardingHandlerIfTrue(context, condition,
[object]() -> Node* { return object; });
}
void SetPromiseHandledByIfTrue(Node* context, Node* condition, Node* promise,
const NodeGenerator& handled_by);
private:
Node* AllocateJSPromise(Node* context);
};
......
// Copyright 2012 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.
(function(global, utils, extrasUtils) {
"use strict";
%CheckIsBootstrapping();
// -------------------------------------------------------------------
// Imports
var InternalArray = utils.InternalArray;
var promiseHandledBySymbol =
utils.ImportNow("promise_handled_by_symbol");
var promiseForwardingHandlerSymbol =
utils.ImportNow("promise_forwarding_handler_symbol");
var GlobalPromise = global.Promise;
// -------------------------------------------------------------------
// Define exported functions.
// Combinators.
// ES#sec-promise.race
// Promise.race ( iterable )
DEFINE_METHOD(
GlobalPromise,
race(iterable) {
if (!IS_RECEIVER(this)) {
throw %make_type_error(kCalledOnNonObject, this);
}
// false debugEvent so that forwarding the rejection through race does not
// trigger redundant ExceptionEvents
var deferred = %new_promise_capability(this, false);
// For catch prediction, don't treat the .then calls as handling it;
// instead, recurse outwards.
var instrumenting = DEBUG_IS_ACTIVE;
if (instrumenting) {
SET_PRIVATE(deferred.reject, promiseForwardingHandlerSymbol, true);
}
try {
for (var value of iterable) {
var throwawayPromise = this.resolve(value).then(deferred.resolve,
deferred.reject);
// For catch prediction, mark that rejections here are semantically
// handled by the combined Promise.
if (instrumenting && %is_promise(throwawayPromise)) {
SET_PRIVATE(throwawayPromise, promiseHandledBySymbol, deferred.promise);
}
}
} catch (e) {
%_Call(deferred.reject, UNDEFINED, e);
}
return deferred.promise;
}
);
})
......@@ -2338,7 +2338,6 @@
'js/typedarray.js',
'js/collection.js',
'js/weak-collection.js',
'js/promise.js',
'js/messages.js',
'js/templates.js',
'js/spread.js',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment