Commit 7ef1df85 authored by Caitlin Potter's avatar Caitlin Potter Committed by Commit Bot

[builtins] port Promise.all to CSA

Introduces CodeStubAssembler helpers for common Iterator operations
(GetIterator, IteratorStep, IteratorClose).

Moves the Promise.all resolveElement closure and it's caller to
builtins-promise-gen.cc.

Instead of creating an internal array (and copying its elements into a result
array), a single JSArray is allocated, and appended with BuildAppendJSArray(),
falling back to %CreateDataProperty(), and elements are updated in the resolve
closure the same way. This should always be unobservable.

This CL increases the size of snapshot_blob.bin on an x64.debug build by 11.44kb

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

Change-Id: Id69b7f76866b29caccd97f35870154c4be85f418
Reviewed-on: https://chromium-review.googlesource.com/497974
Commit-Queue: Caitlin Potter <caitp@igalia.com>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarSathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#45306}
parent 7be0159e
...@@ -2140,6 +2140,9 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object, ...@@ -2140,6 +2140,9 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
InstallSpeciesGetter(promise_fun); InstallSpeciesGetter(promise_fun);
SimpleInstallFunction(promise_fun, "all", Builtins::kPromiseAll, 1, true,
DONT_ENUM);
SimpleInstallFunction(promise_fun, "resolve", Builtins::kPromiseResolve, 1, SimpleInstallFunction(promise_fun, "resolve", Builtins::kPromiseResolve, 1,
true, DONT_ENUM); true, DONT_ENUM);
...@@ -2222,6 +2225,16 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object, ...@@ -2222,6 +2225,16 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
info->set_length(1); info->set_length(1);
native_context()->set_promise_reject_shared_fun(*info); native_context()->set_promise_reject_shared_fun(*info);
} }
{
Handle<Code> code =
isolate->builtins()->PromiseAllResolveElementClosure();
Handle<SharedFunctionInfo> info =
factory->NewSharedFunctionInfo(factory->empty_string(), code, false);
info->set_internal_formal_parameter_count(1);
info->set_length(1);
native_context()->set_promise_all_resolve_element_shared_fun(*info);
}
} }
{ // -- R e g E x p { // -- R e g E x p
......
...@@ -718,6 +718,7 @@ namespace internal { ...@@ -718,6 +718,7 @@ namespace internal {
TFJ(PromiseResolveClosure, 1, kValue) \ TFJ(PromiseResolveClosure, 1, kValue) \
/* ES #sec-promise-reject-functions */ \ /* ES #sec-promise-reject-functions */ \
TFJ(PromiseRejectClosure, 1, kValue) \ TFJ(PromiseRejectClosure, 1, kValue) \
TFJ(PromiseAllResolveElementClosure, 1, kValue) \
/* ES #sec-promise.prototype.then */ \ /* ES #sec-promise.prototype.then */ \
TFJ(PromiseThen, 2, kOnFullfilled, kOnRejected) \ TFJ(PromiseThen, 2, kOnFullfilled, kOnRejected) \
/* ES #sec-promise.prototype.catch */ \ /* ES #sec-promise.prototype.catch */ \
...@@ -737,6 +738,8 @@ namespace internal { ...@@ -737,6 +738,8 @@ namespace internal {
TFJ(PromiseCatchFinally, 1, kReason) \ TFJ(PromiseCatchFinally, 1, kReason) \
TFJ(PromiseValueThunkFinally, 0) \ TFJ(PromiseValueThunkFinally, 0) \
TFJ(PromiseThrowerFinally, 0) \ TFJ(PromiseThrowerFinally, 0) \
/* ES #sec-promise.all */ \
TFJ(PromiseAll, 1, kIterable) \
\ \
/* Proxy */ \ /* Proxy */ \
CPP(ProxyConstructor) \ CPP(ProxyConstructor) \
...@@ -1029,6 +1032,7 @@ namespace internal { ...@@ -1029,6 +1032,7 @@ namespace internal {
V(AsyncGeneratorAwaitCaught) \ V(AsyncGeneratorAwaitCaught) \
V(AsyncGeneratorAwaitUncaught) \ V(AsyncGeneratorAwaitUncaught) \
V(PerformNativePromiseThen) \ V(PerformNativePromiseThen) \
V(PromiseAll) \
V(PromiseConstructor) \ V(PromiseConstructor) \
V(PromiseHandle) \ V(PromiseHandle) \
V(PromiseResolve) \ V(PromiseResolve) \
......
This diff is collapsed.
...@@ -28,6 +28,25 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { ...@@ -28,6 +28,25 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
kPromiseContextLength, kPromiseContextLength,
}; };
enum class PromiseAllResolveElementContext {
// Whether the resolve callback was already called.
kAlreadyVisitedSlot = Context::MIN_CONTEXT_SLOTS,
// Index into the values array
kIndexSlot,
// Remaining elements count (mutable HeapNumber)
kRemainingElementsSlot,
// Promise capability from Promise.all
kCapabilitySlot,
// Values array from Promise.all
kValuesArraySlot,
kLength
};
enum FunctionContextSlot { enum FunctionContextSlot {
kCapabilitySlot = Context::MIN_CONTEXT_SLOTS, kCapabilitySlot = Context::MIN_CONTEXT_SLOTS,
...@@ -80,6 +99,10 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { ...@@ -80,6 +99,10 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* NewPromiseCapability(Node* context, Node* constructor, Node* NewPromiseCapability(Node* context, Node* constructor,
Node* debug_event = nullptr); Node* debug_event = nullptr);
// Load heap number value, increment/decrement, and return the value component
Node* MutableHeapNumberInc(Node* number);
Node* MutableHeapNumberDec(Node* number);
protected: protected:
void PromiseInit(Node* promise); void PromiseInit(Node* promise);
...@@ -135,6 +158,10 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { ...@@ -135,6 +158,10 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* CreateThrowerFunctionContext(Node* reason, Node* native_context); Node* CreateThrowerFunctionContext(Node* reason, Node* native_context);
Node* CreateThrowerFunction(Node* reason, Node* native_context); Node* CreateThrowerFunction(Node* reason, Node* native_context);
Node* PerformPromiseAll(Node* context, Node* constructor, Node* capability,
Node* iterator, Label* if_exception,
Variable* var_exception);
private: private:
Node* AllocateJSPromise(Node* context); Node* AllocateJSPromise(Node* context);
}; };
......
...@@ -4642,6 +4642,161 @@ Node* CodeStubAssembler::ToInteger(Node* context, Node* input, ...@@ -4642,6 +4642,161 @@ Node* CodeStubAssembler::ToInteger(Node* context, Node* input,
return var_arg.value(); return var_arg.value();
} }
#define HANDLE_EXCEPTION(__node__) \
do { \
if (if_exception != nullptr) { \
Node* const node = (__node__); \
GotoIfException(node, if_exception, exception); \
} \
} while (0)
Node* CodeStubAssembler::GetIterator(Node* context, Node* object,
Label* if_exception, Variable* exception) {
Node* method = GetProperty(context, object, factory()->iterator_symbol());
HANDLE_EXCEPTION(method);
Callable callable = CodeFactory::Call(isolate());
Node* iterator = CallJS(callable, context, method, object);
HANDLE_EXCEPTION(iterator);
Label done(this), if_notobject(this, Label::kDeferred);
Branch(IsJSReceiver(iterator), &done, &if_notobject);
BIND(&if_notobject);
{
Node* ret =
CallRuntime(Runtime::kThrowTypeError, context,
SmiConstant(MessageTemplate::kNotAnIterator), iterator);
HANDLE_EXCEPTION(ret);
Goto(&done);
}
BIND(&done);
return iterator;
}
Node* CodeStubAssembler::IteratorStep(Node* context, Node* iterator,
Label* if_done,
Node* fast_iterator_result_map,
Label* if_exception,
Variable* exception) {
DCHECK_NOT_NULL(if_done);
Node* next_method = GetProperty(context, iterator, factory()->next_string());
HANDLE_EXCEPTION(next_method);
Callable callable = CodeFactory::Call(isolate());
Node* result = CallJS(callable, context, next_method, iterator);
HANDLE_EXCEPTION(result);
Label if_notobject(this, Label::kDeferred), return_result(this);
GotoIf(TaggedIsSmi(result), &if_notobject);
GotoIfNot(IsJSReceiver(result), &if_notobject);
auto IfFastIteratorResult = [=]() {
Node* done = LoadObjectField(result, JSIteratorResult::kDoneOffset);
CSA_ASSERT(this, IsBoolean(done));
return done;
};
auto IfGenericIteratorResult = [=]() {
Node* done = GetProperty(context, result, factory()->done_string());
HANDLE_EXCEPTION(done);
VARIABLE(var_done, MachineRepresentation::kTagged, done);
Label to_boolean(this, Label::kDeferred), return_result(this);
GotoIf(TaggedIsSmi(done), &to_boolean);
Branch(IsBoolean(done), &return_result, &to_boolean);
BIND(&to_boolean);
var_done.Bind(CallStub(CodeFactory::ToBoolean(isolate()), context, done));
Goto(&return_result);
BIND(&return_result);
return var_done.value();
};
Node* done;
if (fast_iterator_result_map != nullptr) {
Node* map = LoadMap(result);
done =
Select(WordEqual(map, fast_iterator_result_map), IfFastIteratorResult,
IfGenericIteratorResult, MachineRepresentation::kTagged);
} else {
done = IfGenericIteratorResult();
}
Goto(&return_result);
BIND(&if_notobject);
{
Node* ret =
CallRuntime(Runtime::kThrowIteratorResultNotAnObject, context, result);
HANDLE_EXCEPTION(ret);
Goto(if_done);
}
BIND(&return_result);
GotoIf(IsTrue(done), if_done);
return result;
}
Node* CodeStubAssembler::IteratorValue(Node* context, Node* result,
Node* fast_iterator_result_map,
Label* if_exception,
Variable* exception) {
CSA_ASSERT(this, IsJSReceiver(result));
auto IfFastIteratorResult = [=]() {
return LoadObjectField(result, JSIteratorResult::kValueOffset);
};
auto IfGenericIteratorResult = [=]() {
Node* value = GetProperty(context, result, factory()->value_string());
HANDLE_EXCEPTION(value);
return value;
};
if (fast_iterator_result_map != nullptr) {
Node* map = LoadMap(result);
return Select(WordEqual(map, fast_iterator_result_map),
IfFastIteratorResult, IfGenericIteratorResult,
MachineRepresentation::kTagged);
} else {
return IfGenericIteratorResult();
}
}
void CodeStubAssembler::IteratorClose(Node* context, Node* iterator,
Label* if_exception,
Variable* exception) {
CSA_ASSERT(this, IsJSReceiver(iterator));
Node* method = GetProperty(context, iterator, factory()->return_string());
HANDLE_EXCEPTION(method);
Label done(this);
GotoIf(IsNull(method), &done);
GotoIf(IsUndefined(method), &done);
Node* inner_result =
CallJS(CodeFactory::Call(isolate()), context, method, iterator);
HANDLE_EXCEPTION(inner_result);
Label if_notobject(this, Label::kDeferred);
GotoIf(TaggedIsSmi(inner_result), &if_notobject);
Branch(IsJSReceiver(inner_result), &done, &if_notobject);
BIND(&if_notobject);
{
Node* ret = CallRuntime(Runtime::kThrowIteratorResultNotAnObject, context,
inner_result);
HANDLE_EXCEPTION(ret);
Goto(&done);
}
BIND(&done);
}
#undef HANDLE_EXCEPTION
Node* CodeStubAssembler::DecodeWord32(Node* word32, uint32_t shift, Node* CodeStubAssembler::DecodeWord32(Node* word32, uint32_t shift,
uint32_t mask) { uint32_t mask) {
return Word32Shr(Word32And(word32, Int32Constant(mask)), return Word32Shr(Word32And(word32, Int32Constant(mask)),
......
...@@ -879,6 +879,35 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler { ...@@ -879,6 +879,35 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* ToInteger(Node* context, Node* input, Node* ToInteger(Node* context, Node* input,
ToIntegerTruncationMode mode = kNoTruncation); ToIntegerTruncationMode mode = kNoTruncation);
// https://tc39.github.io/ecma262/#sec-getiterator --- never used for
// @@asyncIterator.
Node* GetIterator(Node* context, Node* object, Label* if_exception = nullptr,
Variable* exception = nullptr);
// https://tc39.github.io/ecma262/#sec-iteratorstep
// Returns `false` if the iterator is done, otherwise returns an
// iterator result.
// `fast_iterator_result_map` refers to the map for the JSIteratorResult
// object, loaded from the native context.
Node* IteratorStep(Node* context, Node* iterator, Label* if_done,
Node* fast_iterator_result_map = nullptr,
Label* if_exception = nullptr,
Variable* exception = nullptr);
// https://tc39.github.io/ecma262/#sec-iteratorvalue
// Return the `value` field from an iterator.
// `fast_iterator_result_map` refers to the map for the JSIteratorResult
// object, loaded from the native context.
Node* IteratorValue(Node* context, Node* result,
Node* fast_iterator_result_map = nullptr,
Label* if_exception = nullptr,
Variable* exception = nullptr);
// https://tc39.github.io/ecma262/#sec-iteratorclose
void IteratorClose(Node* context, Node* iterator,
Label* if_exception = nullptr,
Variable* exception = nullptr);
// Returns a node that contains a decoded (unsigned!) value of a bit // Returns a node that contains a decoded (unsigned!) value of a bit
// field |T| in |word32|. Returns result as an uint32 node. // field |T| in |word32|. Returns result as an uint32 node.
template <typename T> template <typename T>
......
...@@ -320,6 +320,8 @@ enum ContextLookupFlags { ...@@ -320,6 +320,8 @@ enum ContextLookupFlags {
promise_value_thunk_finally_shared_fun) \ promise_value_thunk_finally_shared_fun) \
V(PROMISE_THROWER_FINALLY_SHARED_FUN, SharedFunctionInfo, \ V(PROMISE_THROWER_FINALLY_SHARED_FUN, SharedFunctionInfo, \
promise_thrower_finally_shared_fun) \ promise_thrower_finally_shared_fun) \
V(PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN, SharedFunctionInfo, \
promise_all_resolve_element_shared_fun) \
V(PROMISE_PROTOTYPE_MAP_INDEX, Map, promise_prototype_map) \ V(PROMISE_PROTOTYPE_MAP_INDEX, Map, promise_prototype_map) \
V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \ V(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \
V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \ V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \
......
...@@ -23,70 +23,6 @@ var GlobalPromise = global.Promise; ...@@ -23,70 +23,6 @@ var GlobalPromise = global.Promise;
// Combinators. // Combinators.
// ES#sec-promise.all
// Promise.all ( iterable )
function PromiseAll(iterable) {
if (!IS_RECEIVER(this)) {
throw %make_type_error(kCalledOnNonObject, "Promise.all");
}
// false debugEvent so that forwarding the rejection through all does not
// trigger redundant ExceptionEvents
var deferred = %new_promise_capability(this, false);
var resolutions = new InternalArray();
var count;
// 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);
}
function CreateResolveElementFunction(index, values, promiseCapability) {
var alreadyCalled = false;
return (x) => {
if (alreadyCalled === true) return;
alreadyCalled = true;
values[index] = x;
if (--count === 0) {
var valuesArray = [];
%MoveArrayContents(values, valuesArray);
%_Call(promiseCapability.resolve, UNDEFINED, valuesArray);
}
};
}
try {
var i = 0;
count = 1;
for (var value of iterable) {
var nextPromise = this.resolve(value);
++count;
var throwawayPromise = nextPromise.then(
CreateResolveElementFunction(i, resolutions, deferred),
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);
}
++i;
}
// 6.d
if (--count === 0) {
var valuesArray = [];
%MoveArrayContents(resolutions, valuesArray);
%_Call(deferred.resolve, UNDEFINED, valuesArray);
}
} catch (e) {
%_Call(deferred.reject, UNDEFINED, e);
}
return deferred.promise;
}
// ES#sec-promise.race // ES#sec-promise.race
// Promise.race ( iterable ) // Promise.race ( iterable )
function PromiseRace(iterable) { function PromiseRace(iterable) {
...@@ -125,7 +61,6 @@ function PromiseRace(iterable) { ...@@ -125,7 +61,6 @@ function PromiseRace(iterable) {
// Install exported functions. // Install exported functions.
utils.InstallFunctions(GlobalPromise, DONT_ENUM, [ utils.InstallFunctions(GlobalPromise, DONT_ENUM, [
"all", PromiseAll,
"race", PromiseRace, "race", PromiseRace,
]); ]);
......
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