[promises] Port promiseAll to torque.

Bug: v8:9838
Change-Id: I04383cba6dcb5fc61c82cb8018160aff6fed3b15
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1988794
Commit-Queue: Joshua Litt <joshualitt@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65899}
This commit is contained in:
Joshua Litt 2020-01-21 07:15:35 -08:00 committed by Commit Bot
parent 365d7c80e2
commit 182b43d4d5
9 changed files with 576 additions and 606 deletions

241
BUILD.gn
View File

@ -834,9 +834,15 @@ template("asm_to_inline_asm") {
assert(emit_builtins_as_inline_asm)
script = "tools/snapshot/asm_to_inline_asm.py"
deps = [ ":run_mksnapshot_" + name ]
sources = [ "$target_gen_dir/embedded${suffix}.S" ]
outputs = [ "$target_gen_dir/embedded${suffix}.cc" ]
deps = [
":run_mksnapshot_" + name,
]
sources = [
"$target_gen_dir/embedded${suffix}.S",
]
outputs = [
"$target_gen_dir/embedded${suffix}.cc",
]
args = invoker.args
args += [
rebase_path("$target_gen_dir/embedded${suffix}.S", root_build_dir),
@ -850,7 +856,9 @@ if (is_android && enable_java_templates) {
if (v8_use_external_startup_data) {
# We don't support side-by-side snapshots on Android within Chromium.
assert(!v8_use_multi_snapshots)
deps = [ "//v8" ]
deps = [
"//v8",
]
renaming_sources = [ "$root_out_dir/snapshot_blob.bin" ]
if (current_cpu == "arm" || current_cpu == "x86" ||
current_cpu == "mipsel") {
@ -934,12 +942,16 @@ action("postmortem-metadata") {
"$target_gen_dir/torque-generated/instance-types-tq.h",
]
outputs = [ "$target_gen_dir/debug-support.cc" ]
outputs = [
"$target_gen_dir/debug-support.cc",
]
args = rebase_path(outputs, root_build_dir) +
rebase_path(sources, root_build_dir)
deps = [ ":run_torque" ]
deps = [
":run_torque",
]
}
torque_files = [
@ -983,6 +995,7 @@ torque_files = [
"src/builtins/object-fromentries.tq",
"src/builtins/object.tq",
"src/builtins/promise-abstract-operations.tq",
"src/builtins/promise-all.tq",
"src/builtins/promise-all-element-closure.tq",
"src/builtins/promise-constructor.tq",
"src/builtins/promise-finally.tq",
@ -1124,7 +1137,9 @@ template("run_torque") {
"test/cctest/:*",
]
deps = [ ":torque($toolchain)" ]
deps = [
":torque($toolchain)",
]
script = "tools/run.py"
@ -1210,13 +1225,17 @@ if (v8_verify_torque_generation_invariance) {
rebase_path("$target_gen_dir/torque-generated_x86", root_build_dir),
rebase_path(report_file, root_build_dir),
]
outputs = [ report_file ]
outputs = [
report_file,
]
}
}
group("v8_maybe_icu") {
if (v8_enable_i18n_support) {
public_deps = [ "//third_party/icu" ]
public_deps = [
"//third_party/icu",
]
}
}
@ -1228,7 +1247,9 @@ v8_source_set("torque_generated_initializers") {
":run_torque",
]
public_deps = [ ":v8_maybe_icu" ]
public_deps = [
":v8_maybe_icu",
]
sources = [
"$target_gen_dir/torque-generated/csa-types-tq.h",
@ -1256,7 +1277,9 @@ v8_source_set("torque_generated_definitions") {
":run_torque",
]
public_deps = [ ":v8_maybe_icu" ]
public_deps = [
":v8_maybe_icu",
]
sources = [
"$target_gen_dir/torque-generated/class-definitions-tq.cc",
@ -1270,8 +1293,12 @@ v8_source_set("torque_generated_definitions") {
action("generate_bytecode_builtins_list") {
script = "tools/run.py"
outputs = [ "$target_gen_dir/builtins-generated/bytecodes-builtins-list.h" ]
deps = [ ":bytecode_builtins_list_generator($v8_generator_toolchain)" ]
outputs = [
"$target_gen_dir/builtins-generated/bytecodes-builtins-list.h",
]
deps = [
":bytecode_builtins_list_generator($v8_generator_toolchain)",
]
args = [
"./" + rebase_path(
get_label_info(
@ -1303,7 +1330,9 @@ template("run_mksnapshot") {
action("run_mksnapshot_" + name) {
visibility = [ ":*" ] # Only targets in this file can depend on this.
deps = [ ":mksnapshot($v8_snapshot_toolchain)" ]
deps = [
":mksnapshot($v8_snapshot_toolchain)",
]
script = "tools/run.py"
@ -1434,7 +1463,9 @@ if (v8_use_multi_snapshots) {
action("v8_dump_build_config") {
script = "tools/testrunner/utils/dump_build_config.py"
outputs = [ "$root_out_dir/v8_build_config.json" ]
outputs = [
"$root_out_dir/v8_build_config.json",
]
is_gcov_coverage = v8_code_coverage && !is_clang
is_full_debug = v8_enable_debugging_features && !v8_optimized_debug
args = [
@ -1488,7 +1519,9 @@ v8_source_set("v8_snapshot") {
# Do not publicize any header to remove build dependency.
public = []
sources = [ "src/init/setup-isolate-deserialize.cc" ]
sources = [
"src/init/setup-isolate-deserialize.cc",
]
if (emit_builtins_as_inline_asm) {
deps += [ ":asm_to_inline_asm_default" ]
sources += [ "$target_gen_dir/embedded.cc" ]
@ -1528,7 +1561,9 @@ v8_source_set("v8_initializers") {
"test/cctest:*",
]
deps = [ ":torque_generated_initializers" ]
deps = [
":torque_generated_initializers",
]
sources = [
### gcmole(all) ###
@ -1651,14 +1686,18 @@ v8_source_set("v8_initializers") {
v8_source_set("v8_init") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
deps = [ ":v8_initializers" ]
deps = [
":v8_initializers",
]
sources = [
### gcmole(all) ###
"src/init/setup-isolate-full.cc",
]
public_deps = [ ":v8_maybe_icu" ]
public_deps = [
":v8_maybe_icu",
]
configs = [ ":internal_config" ]
}
@ -1695,7 +1734,9 @@ v8_header_set("v8_headers") {
"include/v8-wasm-trap-handler-win.h",
]
deps = [ ":v8_version" ]
deps = [
":v8_version",
]
}
# This is split out to share basic headers with Torque.
@ -1703,9 +1744,13 @@ v8_header_set("v8_shared_internal_headers") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
configs = [ ":internal_config" ]
sources = [ "src/common/globals.h" ]
sources = [
"src/common/globals.h",
]
deps = [ ":v8_headers" ]
deps = [
":v8_headers",
]
}
v8_compiler_sources = [
@ -1986,9 +2031,13 @@ v8_source_set("v8_compiler") {
group("v8_compiler_for_mksnapshot") {
if (is_debug && !v8_optimized_debug && v8_enable_fast_mksnapshot) {
deps = [ ":v8_compiler_opt" ]
deps = [
":v8_compiler_opt",
]
} else {
deps = [ ":v8_compiler" ]
deps = [
":v8_compiler",
]
}
}
@ -3439,9 +3488,13 @@ v8_source_set("torque_base") {
"src/torque/utils.h",
]
deps = [ ":v8_shared_internal_headers" ]
deps = [
":v8_shared_internal_headers",
]
public_deps = [ ":v8_libbase" ]
public_deps = [
":v8_libbase",
]
# The use of exceptions for Torque in violation of the Chromium style-guide
# is justified by the fact that it is only used from the non-essential
@ -3484,7 +3537,9 @@ v8_source_set("torque_ls_base") {
"src/torque/ls/message.h",
]
public_deps = [ ":torque_base" ]
public_deps = [
":torque_base",
]
# The use of exceptions for Torque in violation of the Chromium style-guide
# is justified by the fact that it is only used from the non-essential
@ -3585,7 +3640,9 @@ v8_component("v8_libbase") {
public_configs = [ ":libbase_config" ]
deps = [ ":v8_headers" ]
deps = [
":v8_headers",
]
public_deps = []
@ -3771,7 +3828,9 @@ v8_source_set("v8_libsampler") {
public_configs = [ ":libsampler_config" ]
deps = [ ":v8_libbase" ]
deps = [
":v8_libbase",
]
}
v8_source_set("fuzzer_support") {
@ -3784,7 +3843,9 @@ v8_source_set("fuzzer_support") {
configs = [ ":internal_config_base" ]
deps = [ ":v8" ]
deps = [
":v8",
]
public_deps = [
":v8_libbase",
@ -3906,7 +3967,9 @@ if (current_toolchain == v8_snapshot_toolchain) {
v8_executable("torque") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
sources = [ "src/torque/torque.cc" ]
sources = [
"src/torque/torque.cc",
]
deps = [
":torque_base",
@ -3936,7 +3999,9 @@ if (current_toolchain == v8_snapshot_toolchain) {
v8_executable("torque-language-server") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
sources = [ "src/torque/ls/torque-language-server.cc" ]
sources = [
"src/torque/ls/torque-language-server.cc",
]
deps = [
":torque_base",
@ -3968,7 +4033,9 @@ if (v8_enable_i18n_support) {
v8_executable("gen-regexp-special-case") {
visibility = [ ":*" ] # Only targets in this file can depend on this.
sources = [ "src/regexp/gen-regexp-special-case.cc" ]
sources = [
"src/regexp/gen-regexp-special-case.cc",
]
deps = [
":v8_libbase",
@ -3985,11 +4052,15 @@ if (v8_enable_i18n_support) {
script = "tools/run.py"
deps = [ ":gen-regexp-special-case($v8_generator_toolchain)" ]
deps = [
":gen-regexp-special-case($v8_generator_toolchain)",
]
output_file = "$target_gen_dir/src/regexp/special-case.cc"
outputs = [ output_file ]
outputs = [
output_file,
]
args = [
"./" + rebase_path(
@ -4035,13 +4106,17 @@ group("gn_all") {
}
group("v8_python_base") {
data = [ ".vpython" ]
data = [
".vpython",
]
}
group("v8_clusterfuzz") {
testonly = true
deps = [ ":d8" ]
deps = [
":d8",
]
if (v8_multi_arch_build) {
deps += [
@ -4057,7 +4132,9 @@ group("v8_clusterfuzz") {
group("v8_archive") {
testonly = true
deps = [ ":d8" ]
deps = [
":d8",
]
if (!is_win) {
# On windows, cctest doesn't link with v8_static_library.
@ -4100,7 +4177,9 @@ group("v8_fuzzers") {
if (is_component_build) {
v8_component("v8") {
sources = [ "src/utils/v8dll-main.cc" ]
sources = [
"src/utils/v8dll-main.cc",
]
public_deps = [
":v8_base",
@ -4115,7 +4194,9 @@ if (is_component_build) {
v8_component("v8_for_testing") {
testonly = true
sources = [ "src/utils/v8dll-main.cc" ]
sources = [
"src/utils/v8dll-main.cc",
]
public_deps = [
":torque_base",
@ -4204,7 +4285,9 @@ v8_executable("d8") {
}
v8_executable("v8_hello_world") {
sources = [ "samples/hello-world.cc" ]
sources = [
"samples/hello-world.cc",
]
configs = [
# Note: don't use :internal_config here because this target will get
@ -4222,7 +4305,9 @@ v8_executable("v8_hello_world") {
}
v8_executable("v8_sample_process") {
sources = [ "samples/process.cc" ]
sources = [
"samples/process.cc",
]
configs = [
# Note: don't use :internal_config here because this target will get
@ -4241,7 +4326,9 @@ v8_executable("v8_sample_process") {
if (want_v8_shell) {
v8_executable("v8_shell") {
sources = [ "samples/shell.cc" ]
sources = [
"samples/shell.cc",
]
configs = [
# Note: don't use :internal_config here because this target will get
@ -4268,16 +4355,22 @@ template("v8_fuzzer") {
"//build/win:default_exe_manifest",
]
sources = [ "test/fuzzer/fuzzer.cc" ]
sources = [
"test/fuzzer/fuzzer.cc",
]
configs = [ ":external_config" ]
}
}
v8_source_set("json_fuzzer") {
sources = [ "test/fuzzer/json.cc" ]
sources = [
"test/fuzzer/json.cc",
]
deps = [ ":fuzzer_support" ]
deps = [
":fuzzer_support",
]
configs = [
":external_config",
@ -4289,9 +4382,13 @@ v8_fuzzer("json_fuzzer") {
}
v8_source_set("multi_return_fuzzer") {
sources = [ "test/fuzzer/multi-return.cc" ]
sources = [
"test/fuzzer/multi-return.cc",
]
deps = [ ":fuzzer_support" ]
deps = [
":fuzzer_support",
]
configs = [
":external_config",
@ -4303,9 +4400,13 @@ v8_fuzzer("multi_return_fuzzer") {
}
v8_source_set("parser_fuzzer") {
sources = [ "test/fuzzer/parser.cc" ]
sources = [
"test/fuzzer/parser.cc",
]
deps = [ ":fuzzer_support" ]
deps = [
":fuzzer_support",
]
configs = [
":external_config",
@ -4322,7 +4423,9 @@ v8_source_set("regexp_builtins_fuzzer") {
"test/fuzzer/regexp_builtins/mjsunit.js.h",
]
deps = [ ":fuzzer_support" ]
deps = [
":fuzzer_support",
]
configs = [
":external_config",
@ -4334,9 +4437,13 @@ v8_fuzzer("regexp_builtins_fuzzer") {
}
v8_source_set("regexp_fuzzer") {
sources = [ "test/fuzzer/regexp.cc" ]
sources = [
"test/fuzzer/regexp.cc",
]
deps = [ ":fuzzer_support" ]
deps = [
":fuzzer_support",
]
configs = [
":external_config",
@ -4358,7 +4465,9 @@ v8_source_set("wasm_module_runner") {
":run_torque",
]
public_deps = [ ":v8_maybe_icu" ]
public_deps = [
":v8_maybe_icu",
]
configs = [
":external_config",
@ -4367,7 +4476,9 @@ v8_source_set("wasm_module_runner") {
}
v8_source_set("wasm_fuzzer") {
sources = [ "test/fuzzer/wasm.cc" ]
sources = [
"test/fuzzer/wasm.cc",
]
deps = [
":fuzzer_support",
@ -4385,7 +4496,9 @@ v8_fuzzer("wasm_fuzzer") {
}
v8_source_set("wasm_async_fuzzer") {
sources = [ "test/fuzzer/wasm-async.cc" ]
sources = [
"test/fuzzer/wasm-async.cc",
]
deps = [
":fuzzer_support",
@ -4434,7 +4547,9 @@ v8_source_set("lib_wasm_fuzzer_common") {
":run_torque",
]
public_deps = [ ":v8_maybe_icu" ]
public_deps = [
":v8_maybe_icu",
]
configs = [
":external_config",
@ -4559,7 +4674,9 @@ if (!build_with_chromium && v8_use_perfetto) {
# This target should be used only by the protoc compiler and by test targets.
source_set("protobuf_full") {
deps = [ ":protobuf_lite" ]
deps = [
":protobuf_lite",
]
sources = [
"third_party/protobuf/src/google/protobuf/any.cc",
"third_party/protobuf/src/google/protobuf/any.pb.cc",
@ -4629,7 +4746,9 @@ if (!build_with_chromium && v8_use_perfetto) {
if (current_toolchain == host_toolchain) {
source_set("protoc_lib") {
deps = [ ":protobuf_full" ]
deps = [
":protobuf_full",
]
sources = [
"third_party/protobuf/src/google/protobuf/compiler/code_generator.cc",
"third_party/protobuf/src/google/protobuf/compiler/command_line_interface.cc",
@ -4668,7 +4787,9 @@ if (!build_with_chromium && v8_use_perfetto) {
":protoc_lib",
"//build/win:default_exe_manifest",
]
sources = [ "src/protobuf/protobuf-compiler-main.cc" ]
sources = [
"src/protobuf/protobuf-compiler-main.cc",
]
configs -= [ "//build/config/compiler:chromium_code" ]
configs += [ "//build/config/compiler:no_chromium_code" ]
}

View File

@ -285,6 +285,7 @@ extern enum MessageTemplate {
kPromiseNonCallable,
kNotAPromise,
kResolverNotAFunction,
kTooManyElementsInPromiseAll,
kToRadixFormatRange,
kCalledOnNonObject,
kRegExpGlobalInvokedOnNonGlobal,

View File

@ -710,10 +710,6 @@ namespace internal {
\
/* Promise */ \
CPP(IsPromise) \
/* ES #sec-promise.all */ \
TFJ(PromiseAll, 1, kReceiver, kIterable) \
/* ES #sec-promise.allsettled */ \
TFJ(PromiseAllSettled, 1, kReceiver, kIterable) \
\
/* Reflect */ \
ASM(ReflectApply, JSTrampoline) \

View File

@ -99,67 +99,6 @@ TNode<JSPromise> PromiseBuiltinsAssembler::AllocateAndSetJSPromise(
return instance;
}
Node* PromiseBuiltinsAssembler::CreatePromiseAllResolveElementContext(
Node* promise_capability, Node* native_context) {
CSA_ASSERT(this, IsNativeContext(native_context));
// TODO(bmeurer): Manually fold this into a single allocation.
TNode<Map> array_map = CAST(LoadContextElement(
native_context, Context::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX));
TNode<JSArray> values_array = AllocateJSArray(
PACKED_ELEMENTS, array_map, IntPtrConstant(0), SmiConstant(0));
const TNode<Context> context = AllocateSyntheticFunctionContext(
CAST(native_context), PromiseBuiltins::kPromiseAllResolveElementLength);
StoreContextElementNoWriteBarrier(
context, PromiseBuiltins::kPromiseAllResolveElementRemainingSlot,
SmiConstant(1));
StoreContextElementNoWriteBarrier(
context, PromiseBuiltins::kPromiseAllResolveElementCapabilitySlot,
promise_capability);
StoreContextElementNoWriteBarrier(
context, PromiseBuiltins::kPromiseAllResolveElementValuesArraySlot,
values_array);
return context;
}
TNode<JSFunction>
PromiseBuiltinsAssembler::CreatePromiseAllResolveElementFunction(
Node* context, TNode<Smi> index, Node* native_context, int slot_index) {
CSA_ASSERT(this, SmiGreaterThan(index, SmiConstant(0)));
CSA_ASSERT(this, SmiLessThanOrEqual(
index, SmiConstant(PropertyArray::HashField::kMax)));
CSA_ASSERT(this, IsNativeContext(native_context));
const TNode<Map> map = CAST(LoadContextElement(
native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX));
const TNode<SharedFunctionInfo> resolve_info =
CAST(LoadContextElement(native_context, slot_index));
TNode<JSFunction> resolve =
AllocateFunctionWithMapAndContext(map, resolve_info, CAST(context));
STATIC_ASSERT(PropertyArray::kNoHashSentinel == 0);
StoreObjectFieldNoWriteBarrier(resolve, JSFunction::kPropertiesOrHashOffset,
index);
return resolve;
}
TNode<Context> PromiseBuiltinsAssembler::CreatePromiseResolvingFunctionsContext(
TNode<JSPromise> promise, TNode<Object> debug_event,
TNode<NativeContext> native_context) {
const TNode<Context> context = AllocateSyntheticFunctionContext(
native_context, PromiseBuiltins::kPromiseContextLength);
StoreContextElementNoWriteBarrier(context, PromiseBuiltins::kPromiseSlot,
promise);
StoreContextElementNoWriteBarrier(
context, PromiseBuiltins::kAlreadyResolvedSlot, FalseConstant());
StoreContextElementNoWriteBarrier(context, PromiseBuiltins::kDebugEventSlot,
debug_event);
return context;
}
Node* PromiseBuiltinsAssembler::PromiseHasHandler(Node* promise) {
const TNode<Smi> flags =
CAST(LoadObjectField(promise, JSPromise::kFlagsOffset));
@ -221,46 +160,6 @@ PromiseBuiltinsAssembler::AllocatePromiseResolveThenableJobTask(
return CAST(microtask);
}
Node* PromiseBuiltinsAssembler::CallResolve(Node* native_context,
Node* constructor, Node* resolve,
Node* value, Label* if_exception,
Variable* var_exception) {
CSA_ASSERT(this, IsNativeContext(native_context));
CSA_ASSERT(this, IsConstructor(constructor));
VARIABLE(var_result, MachineRepresentation::kTagged);
Label if_fast(this), if_slow(this, Label::kDeferred), done(this, &var_result);
// Undefined can never be a valid value for the resolve function,
// instead it is used as a special marker for the fast path.
Branch(IsUndefined(resolve), &if_fast, &if_slow);
BIND(&if_fast);
{
const TNode<Object> result = CallBuiltin(
Builtins::kPromiseResolve, native_context, constructor, value);
GotoIfException(result, if_exception, var_exception);
var_result.Bind(result);
Goto(&done);
}
BIND(&if_slow);
{
CSA_ASSERT(this, IsCallable(resolve));
Node* const result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
native_context, resolve, constructor, value);
GotoIfException(result, if_exception, var_exception);
var_result.Bind(result);
Goto(&done);
}
BIND(&done);
return var_result.value();
}
void PromiseBuiltinsAssembler::BranchIfPromiseResolveLookupChainIntact(
Node* native_context, SloppyTNode<Object> constructor, Label* if_fast,
Label* if_slow) {
@ -353,392 +252,5 @@ void PromiseBuiltinsAssembler::BranchIfAccessCheckFailed(
BIND(&has_access);
}
void PromiseBuiltinsAssembler::SetForwardingHandlerIfTrue(Node* context,
Node* condition,
Node* object) {
Label done(this);
GotoIfNot(condition, &done);
SetPropertyStrict(
CAST(context), CAST(object),
HeapConstant(factory()->promise_forwarding_handler_symbol()),
TrueConstant());
Goto(&done);
BIND(&done);
}
void PromiseBuiltinsAssembler::SetPromiseHandledByIfTrue(
Node* context, Node* condition, Node* promise,
const NodeGenerator<Object>& handled_by) {
Label done(this);
GotoIfNot(condition, &done);
GotoIf(TaggedIsSmi(promise), &done);
GotoIfNot(HasInstanceType(promise, JS_PROMISE_TYPE), &done);
SetPropertyStrict(CAST(context), CAST(promise),
HeapConstant(factory()->promise_handled_by_symbol()),
handled_by());
Goto(&done);
BIND(&done);
}
TNode<Object> PromiseBuiltinsAssembler::PerformPromiseAll(
Node* context, Node* constructor, Node* capability,
const IteratorRecord& iterator,
const PromiseAllResolvingElementFunction& create_resolve_element_function,
const PromiseAllResolvingElementFunction& create_reject_element_function,
Label* if_exception, TVariable<Object>* var_exception) {
IteratorBuiltinsAssembler iter_assembler(state());
TNode<NativeContext> native_context = LoadNativeContext(context);
// For catch prediction, don't treat the .then calls as handling it;
// instead, recurse outwards.
SetForwardingHandlerIfTrue(
native_context, IsDebugActive(),
LoadObjectField(capability, PromiseCapability::kRejectOffset));
TNode<Context> resolve_element_context =
Cast(CreatePromiseAllResolveElementContext(capability, native_context));
TVARIABLE(Smi, var_index, SmiConstant(1));
Label loop(this, &var_index), done_loop(this),
too_many_elements(this, Label::kDeferred),
close_iterator(this, Label::kDeferred), if_slow(this, Label::kDeferred);
// 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.
TVARIABLE(Object, var_promise_resolve_function, UndefinedConstant());
GotoIfNotPromiseResolveLookupChainIntact(native_context, constructor,
&if_slow);
Goto(&loop);
BIND(&if_slow);
{
// 5. Let _promiseResolve_ be ? Get(_constructor_, `"resolve"`).
TNode<Object> resolve =
GetProperty(native_context, constructor, factory()->resolve_string());
GotoIfException(resolve, &close_iterator, var_exception);
// 6. If IsCallable(_promiseResolve_) is *false*, throw a *TypeError*
// exception.
ThrowIfNotCallable(CAST(context), resolve, "resolve");
var_promise_resolve_function = resolve;
Goto(&loop);
}
BIND(&loop);
{
// Let next be IteratorStep(iteratorRecord.[[Iterator]]).
// If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// ReturnIfAbrupt(next).
const TNode<Map> fast_iterator_result_map = CAST(
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX));
const TNode<JSReceiver> next = iter_assembler.IteratorStep(
native_context, iterator, &done_loop, fast_iterator_result_map,
if_exception, 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(
native_context, next, fast_iterator_result_map, if_exception,
var_exception);
// Check if we reached the limit.
const TNode<Smi> index = var_index.value();
GotoIf(SmiEqual(index, SmiConstant(PropertyArray::HashField::kMax)),
&too_many_elements);
// Set index to index + 1.
var_index = SmiAdd(index, SmiConstant(1));
// Set remainingElementsCount.[[Value]] to
// remainingElementsCount.[[Value]] + 1.
const TNode<Smi> remaining_elements_count = CAST(LoadContextElement(
resolve_element_context,
PromiseBuiltins::kPromiseAllResolveElementRemainingSlot));
StoreContextElementNoWriteBarrier(
resolve_element_context,
PromiseBuiltins::kPromiseAllResolveElementRemainingSlot,
SmiAdd(remaining_elements_count, SmiConstant(1)));
// Let resolveElement be CreateBuiltinFunction(steps,
// « [[AlreadyCalled]],
// [[Index]],
// [[Values]],
// [[Capability]],
// [[RemainingElements]] »).
// Set resolveElement.[[AlreadyCalled]] to a Record { [[Value]]: false }.
// Set resolveElement.[[Index]] to index.
// Set resolveElement.[[Values]] to values.
// Set resolveElement.[[Capability]] to resultCapability.
// Set resolveElement.[[RemainingElements]] to remainingElementsCount.
const TNode<Object> resolve_element_fun = create_resolve_element_function(
resolve_element_context, index, native_context, Cast(capability));
const TNode<Object> reject_element_fun = create_reject_element_function(
resolve_element_context, index, native_context, Cast(capability));
// We can skip the "resolve" lookup on the {constructor} as well as the
// "then" lookup on the result of the "resolve" call, and immediately
// chain continuation onto the {next_value} if:
//
// (a) The {constructor} is the intrinsic %Promise% function, and
// looking up "resolve" on {constructor} yields the initial
// Promise.resolve() builtin, and
// (b) the promise @@species protector cell is valid, meaning that
// no one messed with the Symbol.species property on any
// intrinsic promise or on the Promise.prototype, and
// (c) the {next_value} is a JSPromise whose [[Prototype]] field
// contains the intrinsic %PromisePrototype%, and
// (d) we're not running with async_hooks or DevTools enabled.
//
// In that case we also don't need to allocate a chained promise for
// the PromiseReaction (aka we can pass undefined to PerformPromiseThen),
// since this is only necessary for DevTools and PromiseHooks.
Label if_fast(this), if_slow(this);
GotoIfNot(IsUndefined(var_promise_resolve_function.value()), &if_slow);
GotoIf(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
&if_slow);
GotoIf(IsPromiseSpeciesProtectorCellInvalid(), &if_slow);
GotoIf(TaggedIsSmi(next_value), &if_slow);
const TNode<Map> next_value_map = LoadMap(CAST(next_value));
BranchIfPromiseThenLookupChainIntact(native_context, next_value_map,
&if_fast, &if_slow);
BIND(&if_fast);
{
// Register the PromiseReaction immediately on the {next_value}, not
// passing any chained promise since neither async_hooks nor DevTools
// are enabled, so there's no use of the resulting promise.
PerformPromiseThenImpl(native_context, CAST(next_value),
CAST(resolve_element_fun),
CAST(reject_element_fun), UndefinedConstant());
Goto(&loop);
}
BIND(&if_slow);
{
// Let nextPromise be ? Call(constructor, _promiseResolve_, « nextValue
// »).
Node* const next_promise = CallResolve(
native_context, constructor, var_promise_resolve_function.value(),
next_value, &close_iterator, var_exception);
// Perform ? Invoke(nextPromise, "then", « resolveElement,
// resultCapability.[[Reject]] »).
const TNode<Object> then =
GetProperty(native_context, next_promise, factory()->then_string());
GotoIfException(then, &close_iterator, var_exception);
Node* const then_call =
CallJS(CodeFactory::Call(isolate(),
ConvertReceiverMode::kNotNullOrUndefined),
native_context, then, next_promise, resolve_element_fun,
reject_element_fun);
GotoIfException(then_call, &close_iterator, var_exception);
// For catch prediction, mark that rejections here are semantically
// handled by the combined Promise.
SetPromiseHandledByIfTrue(
native_context, IsDebugActive(), then_call, [=]() {
// Load promiseCapability.[[Promise]]
return LoadObjectField(capability,
PromiseCapability::kPromiseOffset);
});
Goto(&loop);
}
}
BIND(&too_many_elements);
{
// If there are too many elements (currently more than 2**21-1), raise a
// RangeError here (which is caught directly and turned into a rejection)
// of the resulting promise. We could gracefully handle this case as well
// and support more than this number of elements by going to a separate
// function and pass the larger indices via a separate context, but it
// doesn't seem likely that we need this, and it's unclear how the rest
// of the system deals with 2**21 live Promises anyways.
const TNode<Object> result =
CallRuntime(Runtime::kThrowRangeError, native_context,
SmiConstant(MessageTemplate::kTooManyElementsInPromiseAll));
GotoIfException(result, &close_iterator, var_exception);
Unreachable();
}
BIND(&close_iterator);
{
// Exception must be bound to a JS value.
CSA_ASSERT(this, IsNotTheHole(var_exception->value()));
iter_assembler.IteratorCloseOnException(native_context, iterator,
if_exception, var_exception);
}
BIND(&done_loop);
{
Label resolve_promise(this, Label::kDeferred), return_promise(this);
// Set iteratorRecord.[[Done]] to true.
// Set remainingElementsCount.[[Value]] to
// remainingElementsCount.[[Value]] - 1.
TNode<Smi> remaining_elements_count = CAST(LoadContextElement(
resolve_element_context,
PromiseBuiltins::kPromiseAllResolveElementRemainingSlot));
remaining_elements_count = SmiSub(remaining_elements_count, SmiConstant(1));
StoreContextElementNoWriteBarrier(
resolve_element_context,
PromiseBuiltins::kPromiseAllResolveElementRemainingSlot,
remaining_elements_count);
GotoIf(SmiEqual(remaining_elements_count, SmiConstant(0)),
&resolve_promise);
// Pre-allocate the backing store for the {values_array} to the desired
// capacity here. We may already have elements here in case of some
// fancy Thenable that calls the resolve callback immediately, so we need
// to handle that correctly here.
const TNode<JSArray> values_array = CAST(LoadContextElement(
resolve_element_context,
PromiseBuiltins::kPromiseAllResolveElementValuesArraySlot));
const TNode<FixedArrayBase> old_elements = LoadElements(values_array);
const TNode<Smi> old_capacity = LoadFixedArrayBaseLength(old_elements);
const TNode<Smi> new_capacity = var_index.value();
GotoIf(SmiGreaterThanOrEqual(old_capacity, new_capacity), &return_promise);
const TNode<FixedArrayBase> new_elements =
AllocateFixedArray(PACKED_ELEMENTS, new_capacity,
AllocationFlag::kAllowLargeObjectAllocation);
CopyFixedArrayElements(PACKED_ELEMENTS, old_elements, PACKED_ELEMENTS,
new_elements, SmiConstant(0), old_capacity,
new_capacity, UPDATE_WRITE_BARRIER, SMI_PARAMETERS);
StoreObjectField(values_array, JSArray::kElementsOffset, new_elements);
Goto(&return_promise);
// If remainingElementsCount.[[Value]] is 0, then
// Let valuesArray be CreateArrayFromList(values).
// Perform ? Call(resultCapability.[[Resolve]], undefined,
// « valuesArray »).
BIND(&resolve_promise);
{
const TNode<Object> resolve =
LoadObjectField(capability, PromiseCapability::kResolveOffset);
const TNode<Object> values_array = LoadContextElement(
resolve_element_context,
PromiseBuiltins::kPromiseAllResolveElementValuesArraySlot);
Node* const resolve_call = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
native_context, resolve, UndefinedConstant(), values_array);
GotoIfException(resolve_call, if_exception, var_exception);
Goto(&return_promise);
}
// Return resultCapability.[[Promise]].
BIND(&return_promise);
}
const TNode<Object> promise =
LoadObjectField(capability, PromiseCapability::kPromiseOffset);
return promise;
}
void PromiseBuiltinsAssembler::Generate_PromiseAll(
TNode<Context> context, TNode<Object> receiver, TNode<Object> iterable,
const PromiseAllResolvingElementFunction& create_resolve_element_function,
const PromiseAllResolvingElementFunction& create_reject_element_function) {
IteratorBuiltinsAssembler iter_assembler(state());
// Let C be the this value.
// If Type(C) is not Object, throw a TypeError exception.
ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
"Promise.all");
// 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));
TVARIABLE(Object, var_exception, TheHoleConstant());
Label reject_promise(this, &var_exception, Label::kDeferred);
// Let iterator be GetIterator(iterable).
// IfAbruptRejectPromise(iterator, promiseCapability).
IteratorRecord iterator = iter_assembler.GetIterator(
context, iterable, &reject_promise, &var_exception);
// Let result be PerformPromiseAll(iteratorRecord, C, promiseCapability).
// If result is an abrupt completion, then
// If iteratorRecord.[[Done]] is false, let result be
// IteratorClose(iterator, result).
// IfAbruptRejectPromise(result, promiseCapability).
const TNode<Object> result = PerformPromiseAll(
context, receiver, capability, iterator, create_resolve_element_function,
create_reject_element_function, &reject_promise, &var_exception);
Return(result);
BIND(&reject_promise);
{
// Exception must be bound to a JS value.
CSA_SLOW_ASSERT(this, IsNotTheHole(var_exception.value()));
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);
}
}
// ES#sec-promise.all
// Promise.all ( iterable )
TF_BUILTIN(PromiseAll, PromiseBuiltinsAssembler) {
TNode<Object> receiver = Cast(Parameter(Descriptor::kReceiver));
TNode<Context> context = Cast(Parameter(Descriptor::kContext));
TNode<Object> iterable = Cast(Parameter(Descriptor::kIterable));
Generate_PromiseAll(
context, receiver, iterable,
[this](TNode<Context> context, TNode<Smi> index,
TNode<NativeContext> native_context,
TNode<PromiseCapability> capability) {
return CreatePromiseAllResolveElementFunction(
context, index, native_context,
Context::PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN);
},
[this](TNode<Context> context, TNode<Smi> index,
TNode<NativeContext> native_context,
TNode<PromiseCapability> capability) {
return LoadObjectField(capability, PromiseCapability::kRejectOffset);
});
}
// ES#sec-promise.allsettled
// Promise.allSettled ( iterable )
TF_BUILTIN(PromiseAllSettled, PromiseBuiltinsAssembler) {
TNode<Object> receiver = Cast(Parameter(Descriptor::kReceiver));
TNode<Context> context = Cast(Parameter(Descriptor::kContext));
TNode<Object> iterable = Cast(Parameter(Descriptor::kIterable));
Generate_PromiseAll(
context, receiver, iterable,
[this](TNode<Context> context, TNode<Smi> index,
TNode<NativeContext> native_context,
TNode<PromiseCapability> capability) {
return CreatePromiseAllResolveElementFunction(
context, index, native_context,
Context::PROMISE_ALL_SETTLED_RESOLVE_ELEMENT_SHARED_FUN);
},
[this](TNode<Context> context, TNode<Smi> index,
TNode<NativeContext> native_context,
TNode<PromiseCapability> capability) {
return CreatePromiseAllResolveElementFunction(
context, index, native_context,
Context::PROMISE_ALL_SETTLED_REJECT_ELEMENT_SHARED_FUN);
});
}
} // namespace internal
} // namespace v8

View File

@ -47,24 +47,6 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler {
TNode<JSReceiver> thenable, TNode<Context> context);
Node* PromiseHasHandler(Node* promise);
// Creates the context used by all Promise.all resolve element closures,
// together with the values array. Since all closures for a single Promise.all
// call use the same context, we need to store the indices for the individual
// closures somewhere else (we put them into the identity hash field of the
// closures), and we also need to have a separate marker for when the closure
// was called already (we slap the native context onto the closure in that
// case to mark it's done).
Node* CreatePromiseAllResolveElementContext(Node* promise_capability,
Node* native_context);
TNode<JSFunction> CreatePromiseAllResolveElementFunction(Node* context,
TNode<Smi> index,
Node* native_context,
int slot_index);
TNode<Context> CreatePromiseResolvingFunctionsContext(
TNode<JSPromise> promise, TNode<Object> debug_event,
TNode<NativeContext> native_context);
void BranchIfAccessCheckFailed(SloppyTNode<Context> context,
SloppyTNode<Context> native_context,
TNode<Object> promise_constructor,
@ -140,33 +122,7 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* receiver_map, Label* if_fast,
Label* if_slow);
// If resolve is Undefined, we use the builtin %PromiseResolve%
// 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);
using PromiseAllResolvingElementFunction =
std::function<TNode<Object>(TNode<Context> context, TNode<Smi> index,
TNode<NativeContext> native_context,
TNode<PromiseCapability> capability)>;
TNode<Object> PerformPromiseAll(
Node* context, Node* constructor, Node* capability,
const TorqueStructIteratorRecord& record,
const PromiseAllResolvingElementFunction& create_resolve_element_function,
const PromiseAllResolvingElementFunction& create_reject_element_function,
Label* if_exception, TVariable<Object>* var_exception);
void SetForwardingHandlerIfTrue(Node* context, Node* condition, Node* object);
void SetPromiseHandledByIfTrue(Node* context, Node* condition, Node* promise,
const NodeGenerator<Object>& handled_by);
TNode<JSPromise> AllocateJSPromise(TNode<Context> context);
void Generate_PromiseAll(
TNode<Context> context, TNode<Object> receiver, TNode<Object> iterable,
const PromiseAllResolvingElementFunction& create_resolve_element_function,
const PromiseAllResolvingElementFunction& create_reject_element_function);
};
} // namespace internal

View File

@ -248,9 +248,6 @@ namespace promise {
extern macro
PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Context): JSPromise;
extern macro
PromiseBuiltinsAssembler::CreatePromiseResolvingFunctionsContext(
JSPromise, Object, NativeContext): Context;
@export
macro CreatePromiseCapabilitiesExecutorContext(

384
src/builtins/promise-all.tq Normal file
View File

@ -0,0 +1,384 @@
// 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 {
const kPromiseBuiltinsPromiseContextLength: constexpr int31
generates 'PromiseBuiltins::kPromiseContextLength';
// Creates the context used by all Promise.all resolve element closures,
// together with the values array. Since all closures for a single Promise.all
// call use the same context, we need to store the indices for the individual
// closures somewhere else (we put them into the identity hash field of the
// closures), and we also need to have a separate marker for when the closure
// was called already (we slap the native context onto the closure in that
// case to mark it's done).
macro CreatePromiseAllResolveElementContext(implicit context: Context)(
capability: PromiseCapability, nativeContext: NativeContext): Context {
// TODO(bmeurer): Manually fold this into a single allocation.
const arrayMap = UnsafeCast<Map>(
nativeContext[NativeContextSlot::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX]);
const valuesArray = AllocateJSArray(
ElementsKind::PACKED_ELEMENTS, arrayMap, IntPtrConstant(0),
SmiConstant(0));
const resolveContext = AllocateSyntheticFunctionContext(
nativeContext,
PromiseAllResolveElementContextSlots::kPromiseAllResolveElementLength);
resolveContext[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementRemainingSlot] = SmiConstant(1);
resolveContext[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementCapabilitySlot] = capability;
resolveContext[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementValuesArraySlot] = valuesArray;
return resolveContext;
}
macro CreatePromiseAllResolveElementFunction(implicit context: Context)(
resolveElementContext: Context, index: Smi, nativeContext: NativeContext,
slotIndex: constexpr NativeContextSlot): JSFunction {
assert(index > 0);
assert(index < kPropertyArrayHashFieldMax);
const map = UnsafeCast<Map>(
nativeContext
[NativeContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX]);
const resolveInfo =
UnsafeCast<SharedFunctionInfo>(nativeContext[slotIndex]);
const resolve = AllocateFunctionWithMapAndContext(
map, resolveInfo, resolveElementContext);
assert(kPropertyArrayNoHashSentinel == 0);
resolve.properties_or_hash = index;
return resolve;
}
@export
macro CreatePromiseResolvingFunctionsContext(implicit context: Context)(
promise: JSPromise, debugEvent: Object, nativeContext: NativeContext):
Context {
const resolveContext = AllocateSyntheticFunctionContext(
nativeContext, kPromiseBuiltinsPromiseContextLength);
resolveContext[kPromiseBuiltinsPromiseSlot] = promise;
resolveContext[kPromiseBuiltinsAlreadyResolvedSlot] = False;
resolveContext[kPromiseBuiltinsDebugEventSlot] = debugEvent;
return resolveContext;
}
macro IsPromiseThenLookupChainIntact(implicit context: Context)(
nativeContext: NativeContext, receiverMap: Map): bool {
if (IsForceSlowPath()) return false;
if (!IsJSPromiseMap(receiverMap)) return false;
if (receiverMap.prototype !=
nativeContext[NativeContextSlot::PROMISE_PROTOTYPE_INDEX])
return false;
return !IsPromiseThenProtectorCellInvalid();
}
struct PromiseAllResolveElementFunctor {
macro Call(implicit context: Context)(
resolveElementContext: Context, nativeContext: NativeContext,
index: Smi, _capability: PromiseCapability): Callable {
return CreatePromiseAllResolveElementFunction(
resolveElementContext, index, nativeContext,
NativeContextSlot::PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN);
}
}
struct PromiseAllRejectElementFunctor {
macro Call(implicit context: Context)(
_resolveElementContext: Context, _nativeContext: NativeContext,
_index: Smi, capability: PromiseCapability): Callable {
return UnsafeCast<Callable>(capability.reject);
}
}
struct PromiseAllSettledResolveElementFunctor {
macro Call(implicit context: Context)(
resolveElementContext: Context, nativeContext: NativeContext,
index: Smi, _capability: PromiseCapability): Callable {
return CreatePromiseAllResolveElementFunction(
resolveElementContext, index, nativeContext,
NativeContextSlot::PROMISE_ALL_SETTLED_RESOLVE_ELEMENT_SHARED_FUN);
}
}
struct PromiseAllSettledRejectElementFunctor {
macro Call(implicit context: Context)(
resolveElementContext: Context, nativeContext: NativeContext,
index: Smi, _capability: PromiseCapability): Callable {
return CreatePromiseAllResolveElementFunction(
resolveElementContext, index, nativeContext,
NativeContextSlot::PROMISE_ALL_SETTLED_REJECT_ELEMENT_SHARED_FUN);
}
}
transitioning macro PerformPromiseAll<F1: type, F2: type>(implicit context:
Context)(
constructor: JSReceiver, capability: PromiseCapability,
iter: iterator::IteratorRecord, createResolveElementFunctor: F1,
createRejectElementFunctor: F2): JSAny labels Reject(Object) {
const nativeContext = LoadNativeContext(context);
const promise = capability.promise;
const resolve = capability.resolve;
const reject = capability.reject;
// For catch prediction, don't treat the .then calls as handling it;
// instead, recurse outwards.
if (IsDebugActive()) deferred {
SetPropertyStrict(
context, reject, kPromiseForwardingHandlerSymbol, True);
}
const resolveElementContext =
CreatePromiseAllResolveElementContext(capability, nativeContext);
let index: Smi = 1;
// 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.
let promiseResolveFunction: JSAny = Undefined;
try {
try {
if (!IsPromiseResolveLookupChainIntact(nativeContext, constructor)) {
// 5. Let _promiseResolve_ be ? Get(_constructor_, `"resolve"`).
let promiseResolve: JSAny;
try {
promiseResolve = GetProperty(constructor, kResolveString);
} catch (e) deferred {
iterator::IteratorCloseOnException(iter, e) otherwise Reject;
}
// 6. If IsCallable(_promiseResolve_) is *false*, throw a *TypeError*
// exception.
promiseResolveFunction = Cast<Callable>(promiseResolve)
otherwise ThrowTypeError(
MessageTemplate::kCalledNonCallable, 'resolve');
}
const fastIteratorResultMap = UnsafeCast<Map>(
nativeContext[NativeContextSlot::ITERATOR_RESULT_MAP_INDEX]);
while (true) {
let nextValue: JSAny;
// Let next be IteratorStep(iteratorRecord.[[Iterator]]).
// If next is an abrupt completion, set iteratorRecord.[[Done]] to
// true. ReturnIfAbrupt(next).
const next: JSReceiver = iterator::IteratorStep(
iter, fastIteratorResultMap) otherwise goto Done;
// Let nextValue be IteratorValue(next).
// If nextValue is an abrupt completion, set iteratorRecord.[[Done]]
// to true.
// ReturnIfAbrupt(nextValue).
nextValue = iterator::IteratorValue(next, fastIteratorResultMap);
// Check if we reached the limit.
if (index == kPropertyArrayHashFieldMax) {
// If there are too many elements (currently more than 2**21-1),
// raise a RangeError here (which is caught directly and turned into
// a rejection) of the resulting promise. We could gracefully handle
// this case as well and support more than this number of elements
// by going to a separate function and pass the larger indices via a
// separate context, but it doesn't seem likely that we need this,
// and it's unclear how the rest of the system deals with 2**21 live
// Promises anyways.
try {
ThrowRangeError(MessageTemplate::kTooManyElementsInPromiseAll);
} catch (e) deferred {
iterator::IteratorCloseOnException(iter, e) otherwise Reject;
}
}
// Set remainingElementsCount.[[Value]] to
// remainingElementsCount.[[Value]] + 1.
const remainingElementsCount =
UnsafeCast<Smi>(resolveElementContext
[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementRemainingSlot]);
resolveElementContext[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementRemainingSlot] =
remainingElementsCount + 1;
// Let resolveElement be CreateBuiltinFunction(steps,
// « [[AlreadyCalled]],
// [[Index]],
// [[Values]],
// [[Capability]],
// [[RemainingElements]]
// »).
// Set resolveElement.[[AlreadyCalled]] to a Record { [[Value]]: false
// }. Set resolveElement.[[Index]] to index. Set
// resolveElement.[[Values]] to values. Set
// resolveElement.[[Capability]] to resultCapability. Set
// resolveElement.[[RemainingElements]] to remainingElementsCount.
const resolveElementFun = createResolveElementFunctor.Call(
resolveElementContext, nativeContext, index, capability);
const rejectElementFun = createRejectElementFunctor.Call(
resolveElementContext, nativeContext, index, capability);
// We can skip the "resolve" lookup on the {constructor} as well as
// the "then" lookup on the result of the "resolve" call, and
// immediately chain continuation onto the {next_value} if:
//
// (a) The {constructor} is the intrinsic %Promise% function, and
// looking up "resolve" on {constructor} yields the initial
// Promise.resolve() builtin, and
// (b) the promise @@species protector cell is valid, meaning that
// no one messed with the Symbol.species property on any
// intrinsic promise or on the Promise.prototype, and
// (c) the {next_value} is a JSPromise whose [[Prototype]] field
// contains the intrinsic %PromisePrototype%, and
// (d) we're not running with async_hooks or DevTools enabled.
//
// In that case we also don't need to allocate a chained promise for
// the PromiseReaction (aka we can pass undefined to
// PerformPromiseThen), since this is only necessary for DevTools and
// PromiseHooks.
if (promiseResolveFunction != Undefined ||
IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||
IsPromiseSpeciesProtectorCellInvalid() || Is<Smi>(nextValue) ||
!IsPromiseThenLookupChainIntact(
nativeContext, UnsafeCast<HeapObject>(nextValue).map)) {
try {
// Let nextPromise be ? Call(constructor, _promiseResolve_, «
// nextValue »).
const nextPromise = CallResolve(
UnsafeCast<Constructor>(constructor), promiseResolveFunction,
nextValue);
// Perform ? Invoke(nextPromise, "then", « resolveElement,
// resultCapability.[[Reject]] »).
const then = GetProperty(nextPromise, kThenString);
const thenResult = Call(
nativeContext, then, nextPromise, resolveElementFun,
rejectElementFun);
// For catch prediction, mark that rejections here are
// semantically handled by the combined Promise.
if (IsDebugActive() && Is<JSPromise>(thenResult)) deferred {
SetPropertyStrict(
context, thenResult, kPromiseHandledBySymbol, promise);
}
} catch (e) deferred {
iterator::IteratorCloseOnException(iter, e) otherwise Reject;
}
} else {
PerformPromiseThenImpl(
UnsafeCast<JSPromise>(nextValue), resolveElementFun,
rejectElementFun, Undefined);
}
// Set index to index + 1.
index += 1;
}
}
}
label Done {}
// Set iteratorRecord.[[Done]] to true.
// Set remainingElementsCount.[[Value]] to
// remainingElementsCount.[[Value]] - 1.
let remainingElementsCount = UnsafeCast<Smi>(
resolveElementContext[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementRemainingSlot]);
remainingElementsCount -= 1;
resolveElementContext[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementRemainingSlot] =
remainingElementsCount;
if (remainingElementsCount > 0) {
// Pre-allocate the backing store for the {values_array} to the desired
// capacity here. We may already have elements here in case of some
// fancy Thenable that calls the resolve callback immediately, so we need
// to handle that correctly here.
const valuesArray = UnsafeCast<JSArray>(
resolveElementContext[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementValuesArraySlot]);
const oldElements = UnsafeCast<FixedArray>(valuesArray.elements);
const oldCapacity = oldElements.length_intptr;
const newCapacity = SmiUntag(index);
if (oldCapacity < newCapacity) {
valuesArray.elements =
ExtractFixedArray(oldElements, 0, oldCapacity, newCapacity);
}
} else
deferred {
// If remainingElementsCount.[[Value]] is 0, then
// Let valuesArray be CreateArrayFromList(values).
// Perform ? Call(resultCapability.[[Resolve]], undefined,
// « valuesArray »).
assert(remainingElementsCount == 0);
const valuesArray = UnsafeCast<JSAny>(
resolveElementContext
[PromiseAllResolveElementContextSlots::
kPromiseAllResolveElementValuesArraySlot]);
Call(nativeContext, UnsafeCast<JSAny>(resolve), Undefined, valuesArray);
}
// Return resultCapability.[[Promise]].
return promise;
}
transitioning macro GeneratePromiseAll<F1: type, F2: type>(implicit context:
Context)(
receiver: JSAny, iterable: JSAny, createResolveElementFunctor: F1,
createRejectElementFunctor: F2): JSAny {
// Let C be the this value.
// If Type(C) is not Object, throw a TypeError exception.
const receiver = Cast<JSReceiver>(receiver)
otherwise ThrowTypeError(
MessageTemplate::kCalledOnNonObject, 'Promise.all');
// 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);
try {
try {
// Let iterator be GetIterator(iterable).
// IfAbruptRejectPromise(iterator, promiseCapability).
let i = iterator::GetIterator(iterable);
// Let result be PerformPromiseAll(iteratorRecord, C,
// promiseCapability). If result is an abrupt completion, then
// If iteratorRecord.[[Done]] is false, let result be
// IteratorClose(iterator, result).
// IfAbruptRejectPromise(result, promiseCapability).
return PerformPromiseAll(
receiver, capability, i, createResolveElementFunctor,
createRejectElementFunctor) otherwise Reject;
} catch (e) deferred {
goto Reject(e);
}
}
label Reject(e: Object) deferred {
// Exception must be bound to a JS value.
const e = UnsafeCast<JSAny>(e);
const reject = UnsafeCast<JSAny>(capability.reject);
Call(context, reject, Undefined, e);
return capability.promise;
}
}
// ES#sec-promise.all
transitioning javascript builtin PromiseAll(
js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny {
return GeneratePromiseAll(
receiver, iterable, PromiseAllResolveElementFunctor{},
PromiseAllRejectElementFunctor{});
}
// ES#sec-promise.allsettled
// Promise.allSettled ( iterable )
transitioning javascript builtin PromiseAllSettled(
js-implicit context: Context, receiver: JSAny)(iterable: JSAny): JSAny {
return GeneratePromiseAll(
receiver, iterable, PromiseAllSettledResolveElementFunctor{},
PromiseAllSettledRejectElementFunctor{});
}
}

View File

@ -47,7 +47,9 @@ extern enum NativeContextSlot extends intptr constexpr 'Context::Field' {
PROMISE_FUNCTION_INDEX,
PROMISE_THEN_INDEX,
STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX,
PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN,
PROMISE_ALL_SETTLED_REJECT_ELEMENT_SHARED_FUN,
PROMISE_ALL_SETTLED_RESOLVE_ELEMENT_SHARED_FUN,
PROMISE_CAPABILITY_DEFAULT_RESOLVE_SHARED_FUN_INDEX,
PROMISE_CAPABILITY_DEFAULT_REJECT_SHARED_FUN_INDEX,
PROMISE_CATCH_FINALLY_SHARED_FUN,
@ -56,6 +58,7 @@ extern enum NativeContextSlot extends intptr constexpr 'Context::Field' {
PROMISE_THROWER_FINALLY_SHARED_FUN,
PROMISE_THEN_FINALLY_SHARED_FUN,
PROMISE_VALUE_THUNK_FINALLY_SHARED_FUN,
STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX,
...
}

View File

@ -2554,13 +2554,13 @@ TEST(CreatePromiseResolvingFunctionsContext) {
CodeAssemblerTester asm_tester(isolate, kNumParams);
PromiseBuiltinsAssembler m(asm_tester.state());
Node* const context = m.Parameter(kNumParams + 2);
const TNode<Context> context = m.CAST(m.Parameter(kNumParams + 2));
const TNode<NativeContext> native_context = m.LoadNativeContext(context);
const TNode<JSPromise> promise =
m.AllocateAndInitJSPromise(m.CAST(context), m.UndefinedConstant());
m.AllocateAndInitJSPromise(context, m.UndefinedConstant());
const TNode<Context> promise_context =
m.CreatePromiseResolvingFunctionsContext(
promise, m.BooleanConstant(false), native_context);
context, promise, m.BooleanConstant(false), native_context);
m.Return(promise_context);
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
@ -2672,12 +2672,12 @@ TEST(AllocateFunctionWithMapAndContext) {
CodeAssemblerTester asm_tester(isolate, kNumParams);
PromiseBuiltinsAssembler m(asm_tester.state());
Node* const context = m.Parameter(kNumParams + 2);
const TNode<Context> context = m.CAST(m.Parameter(kNumParams + 2));
const TNode<NativeContext> native_context = m.LoadNativeContext(context);
const TNode<JSPromise> promise =
m.AllocateAndInitJSPromise(m.CAST(context), m.UndefinedConstant());
m.AllocateAndInitJSPromise(context, m.UndefinedConstant());
TNode<Context> promise_context = m.CreatePromiseResolvingFunctionsContext(
promise, m.BooleanConstant(false), native_context);
context, promise, m.BooleanConstant(false), native_context);
TNode<Object> resolve_info = m.LoadContextElement(
native_context,
Context::PROMISE_CAPABILITY_DEFAULT_RESOLVE_SHARED_FUN_INDEX);