From 182b43d4d56a0449e834868f4af4a673b46f6f2b Mon Sep 17 00:00:00 2001 From: Joshua Litt Date: Tue, 21 Jan 2020 07:15:35 -0800 Subject: [PATCH] [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 Reviewed-by: Jakob Gruber Cr-Commit-Position: refs/heads/master@{#65899} --- BUILD.gn | 241 +++++++--- src/builtins/base.tq | 1 + src/builtins/builtins-definitions.h | 4 - src/builtins/builtins-promise-gen.cc | 488 -------------------- src/builtins/builtins-promise-gen.h | 44 -- src/builtins/promise-abstract-operations.tq | 3 - src/builtins/promise-all.tq | 384 +++++++++++++++ src/objects/contexts.tq | 5 +- test/cctest/test-code-stub-assembler.cc | 12 +- 9 files changed, 576 insertions(+), 606 deletions(-) create mode 100644 src/builtins/promise-all.tq diff --git a/BUILD.gn b/BUILD.gn index 027fb148f8..abca30fc04 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -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" ] } diff --git a/src/builtins/base.tq b/src/builtins/base.tq index 5feae9a6d0..c05da69786 100644 --- a/src/builtins/base.tq +++ b/src/builtins/base.tq @@ -285,6 +285,7 @@ extern enum MessageTemplate { kPromiseNonCallable, kNotAPromise, kResolverNotAFunction, + kTooManyElementsInPromiseAll, kToRadixFormatRange, kCalledOnNonObject, kRegExpGlobalInvokedOnNonGlobal, diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index f0dacafcd5..1e2cfb9a31 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -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) \ diff --git a/src/builtins/builtins-promise-gen.cc b/src/builtins/builtins-promise-gen.cc index 1a5da48304..815eefaa71 100644 --- a/src/builtins/builtins-promise-gen.cc +++ b/src/builtins/builtins-promise-gen.cc @@ -99,67 +99,6 @@ TNode 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 array_map = CAST(LoadContextElement( - native_context, Context::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX)); - TNode values_array = AllocateJSArray( - PACKED_ELEMENTS, array_map, IntPtrConstant(0), SmiConstant(0)); - - const TNode 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 -PromiseBuiltinsAssembler::CreatePromiseAllResolveElementFunction( - Node* context, TNode 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 = CAST(LoadContextElement( - native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX)); - const TNode resolve_info = - CAST(LoadContextElement(native_context, slot_index)); - TNode resolve = - AllocateFunctionWithMapAndContext(map, resolve_info, CAST(context)); - - STATIC_ASSERT(PropertyArray::kNoHashSentinel == 0); - StoreObjectFieldNoWriteBarrier(resolve, JSFunction::kPropertiesOrHashOffset, - index); - - return resolve; -} - -TNode PromiseBuiltinsAssembler::CreatePromiseResolvingFunctionsContext( - TNode promise, TNode debug_event, - TNode native_context) { - const TNode 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 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 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 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& 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 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* var_exception) { - IteratorBuiltinsAssembler iter_assembler(state()); - - TNode 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 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 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 fast_iterator_result_map = CAST( - LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX)); - const TNode 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 next_value = iter_assembler.IteratorValue( - native_context, next, fast_iterator_result_map, if_exception, - var_exception); - - // Check if we reached the limit. - const TNode 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 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 resolve_element_fun = create_resolve_element_function( - resolve_element_context, index, native_context, Cast(capability)); - const TNode 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 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 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 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 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 values_array = CAST(LoadContextElement( - resolve_element_context, - PromiseBuiltins::kPromiseAllResolveElementValuesArraySlot)); - const TNode old_elements = LoadElements(values_array); - const TNode old_capacity = LoadFixedArrayBaseLength(old_elements); - const TNode new_capacity = var_index.value(); - GotoIf(SmiGreaterThanOrEqual(old_capacity, new_capacity), &return_promise); - const TNode 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 resolve = - LoadObjectField(capability, PromiseCapability::kResolveOffset); - const TNode 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 promise = - LoadObjectField(capability, PromiseCapability::kPromiseOffset); - return promise; -} - -void PromiseBuiltinsAssembler::Generate_PromiseAll( - TNode context, TNode receiver, TNode 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 debug_event = FalseConstant(); - const TNode 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 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 reject = - LoadObjectField(capability, PromiseCapability::kRejectOffset); - CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined), - context, reject, UndefinedConstant(), var_exception.value()); - - const TNode promise = - LoadObjectField(capability, PromiseCapability::kPromiseOffset); - Return(promise); - } -} - -// ES#sec-promise.all -// Promise.all ( iterable ) -TF_BUILTIN(PromiseAll, PromiseBuiltinsAssembler) { - TNode receiver = Cast(Parameter(Descriptor::kReceiver)); - TNode context = Cast(Parameter(Descriptor::kContext)); - TNode iterable = Cast(Parameter(Descriptor::kIterable)); - Generate_PromiseAll( - context, receiver, iterable, - [this](TNode context, TNode index, - TNode native_context, - TNode capability) { - return CreatePromiseAllResolveElementFunction( - context, index, native_context, - Context::PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN); - }, - [this](TNode context, TNode index, - TNode native_context, - TNode capability) { - return LoadObjectField(capability, PromiseCapability::kRejectOffset); - }); -} - -// ES#sec-promise.allsettled -// Promise.allSettled ( iterable ) -TF_BUILTIN(PromiseAllSettled, PromiseBuiltinsAssembler) { - TNode receiver = Cast(Parameter(Descriptor::kReceiver)); - TNode context = Cast(Parameter(Descriptor::kContext)); - TNode iterable = Cast(Parameter(Descriptor::kIterable)); - Generate_PromiseAll( - context, receiver, iterable, - [this](TNode context, TNode index, - TNode native_context, - TNode capability) { - return CreatePromiseAllResolveElementFunction( - context, index, native_context, - Context::PROMISE_ALL_SETTLED_RESOLVE_ELEMENT_SHARED_FUN); - }, - [this](TNode context, TNode index, - TNode native_context, - TNode capability) { - return CreatePromiseAllResolveElementFunction( - context, index, native_context, - Context::PROMISE_ALL_SETTLED_REJECT_ELEMENT_SHARED_FUN); - }); -} - } // namespace internal } // namespace v8 diff --git a/src/builtins/builtins-promise-gen.h b/src/builtins/builtins-promise-gen.h index 502928a1bc..b275f58cfb 100644 --- a/src/builtins/builtins-promise-gen.h +++ b/src/builtins/builtins-promise-gen.h @@ -47,24 +47,6 @@ class V8_EXPORT_PRIVATE PromiseBuiltinsAssembler : public CodeStubAssembler { TNode thenable, TNode 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 CreatePromiseAllResolveElementFunction(Node* context, - TNode index, - Node* native_context, - int slot_index); - - TNode CreatePromiseResolvingFunctionsContext( - TNode promise, TNode debug_event, - TNode native_context); - void BranchIfAccessCheckFailed(SloppyTNode context, SloppyTNode native_context, TNode 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 context, TNode index, - TNode native_context, - TNode capability)>; - - TNode 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* var_exception); - - void SetForwardingHandlerIfTrue(Node* context, Node* condition, Node* object); - void SetPromiseHandledByIfTrue(Node* context, Node* condition, Node* promise, - const NodeGenerator& handled_by); - TNode AllocateJSPromise(TNode context); - - void Generate_PromiseAll( - TNode context, TNode receiver, TNode iterable, - const PromiseAllResolvingElementFunction& create_resolve_element_function, - const PromiseAllResolvingElementFunction& create_reject_element_function); }; } // namespace internal diff --git a/src/builtins/promise-abstract-operations.tq b/src/builtins/promise-abstract-operations.tq index d3a5792297..cd0d4a935d 100644 --- a/src/builtins/promise-abstract-operations.tq +++ b/src/builtins/promise-abstract-operations.tq @@ -248,9 +248,6 @@ namespace promise { extern macro PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Context): JSPromise; - extern macro - PromiseBuiltinsAssembler::CreatePromiseResolvingFunctionsContext( - JSPromise, Object, NativeContext): Context; @export macro CreatePromiseCapabilitiesExecutorContext( diff --git a/src/builtins/promise-all.tq b/src/builtins/promise-all.tq new file mode 100644 index 0000000000..19a16d8da8 --- /dev/null +++ b/src/builtins/promise-all.tq @@ -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( + 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( + nativeContext + [NativeContextSlot::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX]); + const resolveInfo = + UnsafeCast(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(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(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(promiseResolve) + otherwise ThrowTypeError( + MessageTemplate::kCalledNonCallable, 'resolve'); + } + + const fastIteratorResultMap = UnsafeCast( + 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(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(nextValue) || + !IsPromiseThenLookupChainIntact( + nativeContext, UnsafeCast(nextValue).map)) { + try { + // Let nextPromise be ? Call(constructor, _promiseResolve_, « + // nextValue »). + const nextPromise = CallResolve( + UnsafeCast(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(thenResult)) deferred { + SetPropertyStrict( + context, thenResult, kPromiseHandledBySymbol, promise); + } + } catch (e) deferred { + iterator::IteratorCloseOnException(iter, e) otherwise Reject; + } + } else { + PerformPromiseThenImpl( + UnsafeCast(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( + 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( + resolveElementContext[PromiseAllResolveElementContextSlots:: + kPromiseAllResolveElementValuesArraySlot]); + const oldElements = UnsafeCast(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( + resolveElementContext + [PromiseAllResolveElementContextSlots:: + kPromiseAllResolveElementValuesArraySlot]); + Call(nativeContext, UnsafeCast(resolve), Undefined, valuesArray); + } + + // Return resultCapability.[[Promise]]. + return promise; + } + + transitioning macro GeneratePromiseAll(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(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(e); + const reject = UnsafeCast(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{}); + } +} diff --git a/src/objects/contexts.tq b/src/objects/contexts.tq index 0266f90977..5af3011d51 100644 --- a/src/objects/contexts.tq +++ b/src/objects/contexts.tq @@ -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, ... } diff --git a/test/cctest/test-code-stub-assembler.cc b/test/cctest/test-code-stub-assembler.cc index 61fe77a096..4c9dcc3689 100644 --- a/test/cctest/test-code-stub-assembler.cc +++ b/test/cctest/test-code-stub-assembler.cc @@ -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 = m.CAST(m.Parameter(kNumParams + 2)); const TNode native_context = m.LoadNativeContext(context); const TNode promise = - m.AllocateAndInitJSPromise(m.CAST(context), m.UndefinedConstant()); + m.AllocateAndInitJSPromise(context, m.UndefinedConstant()); const TNode 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 = m.CAST(m.Parameter(kNumParams + 2)); const TNode native_context = m.LoadNativeContext(context); const TNode promise = - m.AllocateAndInitJSPromise(m.CAST(context), m.UndefinedConstant()); + m.AllocateAndInitJSPromise(context, m.UndefinedConstant()); TNode promise_context = m.CreatePromiseResolvingFunctionsContext( - promise, m.BooleanConstant(false), native_context); + context, promise, m.BooleanConstant(false), native_context); TNode resolve_info = m.LoadContextElement( native_context, Context::PROMISE_CAPABILITY_DEFAULT_RESOLVE_SHARED_FUN_INDEX);