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,
InstallSpeciesGetter(promise_fun);
SimpleInstallFunction(promise_fun, "all", Builtins::kPromiseAll, 1, true,
DONT_ENUM);
SimpleInstallFunction(promise_fun, "resolve", Builtins::kPromiseResolve, 1,
true, DONT_ENUM);
......@@ -2222,6 +2225,16 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
info->set_length(1);
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
......
......@@ -718,6 +718,7 @@ namespace internal {
TFJ(PromiseResolveClosure, 1, kValue) \
/* ES #sec-promise-reject-functions */ \
TFJ(PromiseRejectClosure, 1, kValue) \
TFJ(PromiseAllResolveElementClosure, 1, kValue) \
/* ES #sec-promise.prototype.then */ \
TFJ(PromiseThen, 2, kOnFullfilled, kOnRejected) \
/* ES #sec-promise.prototype.catch */ \
......@@ -737,6 +738,8 @@ namespace internal {
TFJ(PromiseCatchFinally, 1, kReason) \
TFJ(PromiseValueThunkFinally, 0) \
TFJ(PromiseThrowerFinally, 0) \
/* ES #sec-promise.all */ \
TFJ(PromiseAll, 1, kIterable) \
\
/* Proxy */ \
CPP(ProxyConstructor) \
......@@ -1029,6 +1032,7 @@ namespace internal {
V(AsyncGeneratorAwaitCaught) \
V(AsyncGeneratorAwaitUncaught) \
V(PerformNativePromiseThen) \
V(PromiseAll) \
V(PromiseConstructor) \
V(PromiseHandle) \
V(PromiseResolve) \
......
This diff is collapsed.
......@@ -28,6 +28,25 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
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 {
kCapabilitySlot = Context::MIN_CONTEXT_SLOTS,
......@@ -80,6 +99,10 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* NewPromiseCapability(Node* context, Node* constructor,
Node* debug_event = nullptr);
// Load heap number value, increment/decrement, and return the value component
Node* MutableHeapNumberInc(Node* number);
Node* MutableHeapNumberDec(Node* number);
protected:
void PromiseInit(Node* promise);
......@@ -135,6 +158,10 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* CreateThrowerFunctionContext(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:
Node* AllocateJSPromise(Node* context);
};
......
......@@ -4642,6 +4642,161 @@ Node* CodeStubAssembler::ToInteger(Node* context, Node* input,
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,
uint32_t mask) {
return Word32Shr(Word32And(word32, Int32Constant(mask)),
......
......@@ -879,6 +879,35 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* ToInteger(Node* context, Node* input,
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
// field |T| in |word32|. Returns result as an uint32 node.
template <typename T>
......
......@@ -320,6 +320,8 @@ enum ContextLookupFlags {
promise_value_thunk_finally_shared_fun) \
V(PROMISE_THROWER_FINALLY_SHARED_FUN, SharedFunctionInfo, \
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(REGEXP_EXEC_FUNCTION_INDEX, JSFunction, regexp_exec_function) \
V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function) \
......
......@@ -23,70 +23,6 @@ var GlobalPromise = global.Promise;
// 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
// Promise.race ( iterable )
function PromiseRace(iterable) {
......@@ -125,7 +61,6 @@ function PromiseRace(iterable) {
// Install exported functions.
utils.InstallFunctions(GlobalPromise, DONT_ENUM, [
"all", PromiseAll,
"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