From a9ef8b60bf53bec9d16854eb31b723fa9d37083e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 19 May 2026 10:53:13 -0700 Subject: [PATCH 1/8] go --- scripts/fuzz_opt.py | 6 ++++++ src/ir/intrinsics.cpp | 46 +++++++++++++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 4173058abf8..6cf27c03622 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2984,6 +2984,12 @@ def get_random_opts(): echo " " $? +# Print the testcase handler. This is important as sometimes reduction modifies +# a testcase so much that more handlers can handle it (e.g. removing exports +# that prevent one from running). If more handles are possible, we might pick a +# different one, leading to the wrong handler running. +grep "running testcase handler:" o + # # You may want to print out part of "o" or "e", if the output matters and not # just the return code. For example, diff --git a/src/ir/intrinsics.cpp b/src/ir/intrinsics.cpp index 6dd4c072324..6f361a92ba7 100644 --- a/src/ir/intrinsics.cpp +++ b/src/ir/intrinsics.cpp @@ -15,6 +15,7 @@ */ #include "ir/intrinsics.h" +#include "ir/module-utils.h" #include "ir/find_all.h" #include "wasm-builder.h" @@ -102,28 +103,47 @@ std::vector Intrinsics::getConfigureAllFunctions(Call* call) { } std::vector Intrinsics::getJSCalledFunctions() { - std::vector ret; + using JSCalledSet = std::unordered_set; + + // Gather the js.called functions, and find the configureAll, which can add + // more. + JSCalledSet jsCalled; + Function* configureAll = nullptr; for (auto& func : module.functions) { if (getAnnotations(func.get()).jsCalled) { - ret.push_back(func->name); + jsCalled.insert(func->name); + } + + if (isConfigureAll(func.get())) { + configureAll = func.get(); } } - // ConfigureAlls in a start function make their functions callable. - if (module.start) { - auto* start = module.getFunction(module.start); - if (!start->imported()) { - FindAll calls(start->body); - for (auto* call : calls.list) { - if (isConfigureAll(call)) { - for (auto name : getConfigureAllFunctions(call)) { - ret.push_back(name); + // ConfigureAlls make their functions callable. To avoid always scanning the + // the entire module, only do so when we saw the proper import. + if (configureAll) { + ModuleUtils::ParallelFunctionAnalysis analysis( + module, [&](Function* func, JSCalledSet& jsCalled) { + if (func->imported()) { + return; + } + + FindAll calls(func->body); + for (auto* call : calls.list) { + if (isConfigureAll(call)) { + for (auto name : getConfigureAllFunctions(call)) { + jsCalled.insert(name); + } } } - } + }); + + for (auto& [_, set] : analysis.map) { + jsCalled.insert(set.begin(), set.end()); } } - return ret; + + return std::vector(jsCalled.begin(), jsCalled.end()); } } // namespace wasm From 075d889bc0c74e9175705c04ea32ec0a5e479cd9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 19 May 2026 11:13:26 -0700 Subject: [PATCH 2/8] fix compiler error --- src/ir/module-utils.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index 50b67df7cea..b6aaf0a9c35 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -280,7 +280,10 @@ struct ParallelFunctionAnalysis { using Func = std::function; - ParallelFunctionAnalysis(Module& wasm, Func work) : wasm(wasm) { + ParallelFunctionAnalysis( + std::conditional_t& wasm, + Func work) + : wasm(const_cast(wasm)) { // Fill in the map as we operate on it in parallel (each function to its own // entry). for (auto& func : wasm.functions) { From ab112ab618d675ae930aa3457b845ace97e5db83 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 19 May 2026 11:13:47 -0700 Subject: [PATCH 3/8] undo --- scripts/fuzz_opt.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 6cf27c03622..4173058abf8 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2984,12 +2984,6 @@ def get_random_opts(): echo " " $? -# Print the testcase handler. This is important as sometimes reduction modifies -# a testcase so much that more handlers can handle it (e.g. removing exports -# that prevent one from running). If more handles are possible, we might pick a -# different one, leading to the wrong handler running. -grep "running testcase handler:" o - # # You may want to print out part of "o" or "e", if the output matters and not # just the return code. For example, From 49cfe9102b473ccaf88a80d4a8a63bf1b313f568 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 19 May 2026 11:23:37 -0700 Subject: [PATCH 4/8] test --- test/lit/passes/gufa-configureAll.wast | 94 ++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/test/lit/passes/gufa-configureAll.wast b/test/lit/passes/gufa-configureAll.wast index 274ad8fa1d4..a66cd816fc3 100644 --- a/test/lit/passes/gufa-configureAll.wast +++ b/test/lit/passes/gufa-configureAll.wast @@ -101,3 +101,97 @@ ) ) ) + +;; As above, but now the configureAll is not in a start function. We should +;; still optimize in the same way as before. +(module + ;; CHECK: (type $externs (array (mut externref))) + (type $externs (array (mut externref))) + + ;; CHECK: (type $funcs (array (mut funcref))) + (type $funcs (array (mut funcref))) + + ;; CHECK: (type $bytes (array (mut i8))) + (type $bytes (array (mut i8))) + + ;; CHECK: (type $3 (func (param i32) (result i32))) + + ;; CHECK: (type $configureAll (func (param (ref null $externs) (ref null $funcs) (ref null $bytes) externref))) + (type $configureAll (func (param (ref null $externs)) (param (ref null $funcs)) (param (ref null $bytes)) (param externref))) + + ;; CHECK: (type $5 (func)) + + ;; CHECK: (import "wasm:js-prototypes" "configureAll" (func $configureAll (type $configureAll) (param (ref null $externs) (ref null $funcs) (ref null $bytes) externref))) + (import "wasm:js-prototypes" "configureAll" (func $configureAll (type $configureAll))) + + ;; CHECK: (data $bytes "12345678") + (data $bytes "12345678") + + ;; CHECK: (elem $externs externref (item (ref.null noextern))) + (elem $externs externref + (ref.null extern) + ) + + ;; CHECK: (elem $funcs func $configured) + (elem $funcs funcref + (ref.func $configured) + ) + + ;; CHECK: (elem $other func $unconfigured) + (elem $other funcref + (ref.func $unconfigured) + ) + + ;; CHECK: (func $start (type $5) + ;; CHECK-NEXT: (call $configureAll + ;; CHECK-NEXT: (array.new_elem $externs $externs + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.new_elem $funcs $funcs + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.new_data $bytes $bytes + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $start + (call $configureAll + (array.new_elem $externs $externs + (i32.const 0) (i32.const 1)) + (array.new_elem $funcs $funcs + (i32.const 0) (i32.const 1)) + (array.new_data $bytes $bytes + (i32.const 0) (i32.const 8)) + (ref.null extern) + ) + ) + + ;; CHECK: (func $configured (type $3) (param $x i32) (result i32) + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $configured (param $x i32) (result i32) + ;; This is not optimized out, because of the configureAll. + (i32.eqz + (local.get $x) + ) + ) + + ;; CHECK: (func $unconfigured (type $3) (param $x i32) (result i32) + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unconfigured (param $x i32) (result i32) + ;; This is optimized out, because configureAll does not refer to it. + (i32.eqz + (local.get $x) + ) + ) +) From a357a2c447ddb4f37505c65b0d9a35321a95abcf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 19 May 2026 11:25:27 -0700 Subject: [PATCH 5/8] finish --- test/lit/passes/gufa-configureAll.wast | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/lit/passes/gufa-configureAll.wast b/test/lit/passes/gufa-configureAll.wast index a66cd816fc3..c537d3aa15a 100644 --- a/test/lit/passes/gufa-configureAll.wast +++ b/test/lit/passes/gufa-configureAll.wast @@ -102,8 +102,10 @@ ) ) -;; As above, but now the configureAll is not in a start function. We should -;; still optimize in the same way as before. +;; As above, but now the configureAll is *not* in a start function. We should +;; still optimize in the same way as before. (In theory we could scan to see if +;; the configureAll is actually reached, but we assume if it was not optimized +;; out that it is.) (module ;; CHECK: (type $externs (array (mut externref))) (type $externs (array (mut externref))) @@ -142,7 +144,7 @@ (ref.func $unconfigured) ) - ;; CHECK: (func $start (type $5) + ;; CHECK: (func $do-configure (type $5) ;; CHECK-NEXT: (call $configureAll ;; CHECK-NEXT: (array.new_elem $externs $externs ;; CHECK-NEXT: (i32.const 0) @@ -159,7 +161,7 @@ ;; CHECK-NEXT: (ref.null noextern) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $start + (func $do-configure (call $configureAll (array.new_elem $externs $externs (i32.const 0) (i32.const 1)) From 1ccbe3c7722e0946184f7fa6acebe12f19c54412 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 19 May 2026 11:25:35 -0700 Subject: [PATCH 6/8] format --- src/ir/intrinsics.cpp | 2 +- src/ir/module-utils.h | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ir/intrinsics.cpp b/src/ir/intrinsics.cpp index 6f361a92ba7..57a9b4b8d2e 100644 --- a/src/ir/intrinsics.cpp +++ b/src/ir/intrinsics.cpp @@ -15,8 +15,8 @@ */ #include "ir/intrinsics.h" -#include "ir/module-utils.h" #include "ir/find_all.h" +#include "ir/module-utils.h" #include "wasm-builder.h" namespace wasm { diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index b6aaf0a9c35..8a6ab0209ed 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -281,8 +281,7 @@ struct ParallelFunctionAnalysis { using Func = std::function; ParallelFunctionAnalysis( - std::conditional_t& wasm, - Func work) + std::conditional_t& wasm, Func work) : wasm(const_cast(wasm)) { // Fill in the map as we operate on it in parallel (each function to its own // entry). From 7b1160466b515e6003228e358c01145a22ad439a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 19 May 2026 11:27:08 -0700 Subject: [PATCH 7/8] comment --- src/ir/module-utils.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index 8a6ab0209ed..b559294ee21 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -267,7 +267,8 @@ template inline void iterModuleItems(Module& wasm, T visitor) { // The operation performed should not modify the wasm module in any way, by // default - otherwise, set the Mutability to Mutable. (This is not enforced at // compile time - TODO find a way - but at runtime in pass-debug mode it is -// checked.) +// checked. At compile time either const or non-const Module can be passed in, +// depending on Mutability.) template using DefaultMap = std::map; template Date: Tue, 19 May 2026 11:34:24 -0700 Subject: [PATCH 8/8] sort --- src/ir/intrinsics.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ir/intrinsics.cpp b/src/ir/intrinsics.cpp index 57a9b4b8d2e..b6ed7894240 100644 --- a/src/ir/intrinsics.cpp +++ b/src/ir/intrinsics.cpp @@ -143,7 +143,10 @@ std::vector Intrinsics::getJSCalledFunctions() { } } - return std::vector(jsCalled.begin(), jsCalled.end()); + // Return the set as a sorted vector to avoid nondeterminism. + std::vector ret(jsCalled.begin(), jsCalled.end()); + std::sort(ret.begin(), ret.end()); + return ret; } } // namespace wasm