From 3a436dd546e4a89d7d2fe22f85072a48591b9e17 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 3 Oct 2025 14:21:42 -0700 Subject: [PATCH 01/21] go --- src/tools/execution-results.h | 73 +++++++++++++++++++++-------------- src/tools/wasm-opt.cpp | 20 +++++++++- 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 5d2864cd9ca..a568a875163 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -279,35 +279,22 @@ struct ExecutionResults { // If set, we should ignore this and not compare it to anything. bool ignore = false; - // get results of execution - void get(Module& wasm) { - LoggingExternalInterface interface(loggings, wasm); + // Get results of executing a module. Optionally, provide a second module to + // link with it (like fuzz_shell's second module). + void get(Module& wasm, std::optional second=std::nullopt) { try { - ModuleRunner instance(wasm, &interface); - // This is not an optimization: we want to execute anything, even relaxed - // SIMD instructions. - instance.setRelaxedBehavior(ModuleRunner::RelaxedBehavior::Execute); - instance.instantiate(); - interface.setModuleRunner(&instance); - // execute all exported methods (that are therefore preserved through - // opts) - for (auto& exp : wasm.exports) { - if (exp->kind != ExternalKind::Function) { - continue; - } - std::cout << "[fuzz-exec] calling " << exp->name << "\n"; - auto* func = wasm.getFunction(*exp->getInternalName()); - FunctionResult ret = run(func, wasm, instance); - results[exp->name] = ret; - if (auto* values = std::get_if(&ret)) { - // ignore the result if we hit an unreachable and returned no value - if (values->size() > 0) { - std::cout << "[fuzz-exec] note result: " << exp->name << " => "; - for (auto value : *values) { - printValue(value); - } - } - } + // Run the first module. + LoggingExternalInterface interface(loggings, wasm); + auto instance = std::make_shared(wasm, &interface); + runModule(wasm, *instance, interface); + + if (second) { + // Link and run the second module. + LoggingExternalInterface secondInterface(loggings, *second); + std::map> linkedInstances; + linkedInstances["primary"] = instance; + auto secondInstance = std::make_shared(*second, &secondInterface, linkedInstances); + runModule(*second, *secondInstance, secondInterface); } } catch (const TrapException&) { // May throw in instance creation (init of offsets). @@ -319,6 +306,36 @@ struct ExecutionResults { } } + void runModule(Module& wasm, + ModuleRunner& instance, + LoggingExternalInterface& interface) { + // This is not an optimization: we want to execute anything, even relaxed + // SIMD instructions. + instance.setRelaxedBehavior(ModuleRunner::RelaxedBehavior::Execute); + instance.instantiate(); + interface.setModuleRunner(&instance); + // execute all exported methods (that are therefore preserved through + // opts) + for (auto& exp : wasm.exports) { + if (exp->kind != ExternalKind::Function) { + continue; + } + std::cout << "[fuzz-exec] calling " << exp->name << "\n"; + auto* func = wasm.getFunction(*exp->getInternalName()); + FunctionResult ret = run(func, wasm, instance); + results[exp->name] = ret; + if (auto* values = std::get_if(&ret)) { + // ignore the result if we hit an unreachable and returned no value + if (values->size() > 0) { + std::cout << "[fuzz-exec] note result: " << exp->name << " => "; + for (auto value : *values) { + printValue(value); + } + } + } + } + } + void printValue(Literal value) { // Unwrap an externalized GC value to get the actual value, but not strings, // which are normally a subtype of ext. diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index 5edd2a15b83..29b179bd7f5 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -79,6 +79,7 @@ int main(int argc, const char* argv[]) { bool converge = false; bool fuzzExecBefore = false; bool fuzzExecAfter = false; + std::string fuzzExecSecond; std::string extraFuzzCommand; bool translateToFuzz = false; std::string initialFuzz; @@ -148,6 +149,14 @@ For more on how to optimize effectively, see [&](Options* o, const std::string& arguments) { fuzzExecBefore = fuzzExecAfter = true; }) + .add( + "--fuzz-exec-second", + "", + "A second module to link with the first, for fuzz-exec-before (only " + "before, as optimizations are not applied to it)", + WasmOptOption, + Options::Arguments::One, + [&](Options* o, const std::string& arguments) { fuzzExecSecond = arguments; }) .add("--extra-fuzz-command", "-efc", "An extra command to run on the output before and after optimizing. " @@ -345,7 +354,16 @@ For more on how to optimize effectively, see ExecutionResults results; if (fuzzExecBefore) { - results.get(wasm); + if (fuzzExecSecond.empty()) { + results.get(wasm); + } else { + // Add the second module. + Module second; + second.features = wasm.features; + ModuleReader().read(fuzzExecSecond, second); + + results.get(wasm, second); + } } if (emitSpecWrapper.size() > 0) { From 73579e8f9492b5a15fa27778e0787fe7a1e629a7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 3 Oct 2025 14:27:46 -0700 Subject: [PATCH 02/21] go --- src/tools/execution-results.h | 2 +- src/tools/wasm-opt.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index a568a875163..a81f167a517 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -281,7 +281,7 @@ struct ExecutionResults { // Get results of executing a module. Optionally, provide a second module to // link with it (like fuzz_shell's second module). - void get(Module& wasm, std::optional second=std::nullopt) { + void get(Module& wasm, Module* second=nullptr) { try { // Run the first module. LoggingExternalInterface interface(loggings, wasm); diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index 29b179bd7f5..089537b1591 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -362,7 +362,7 @@ For more on how to optimize effectively, see second.features = wasm.features; ModuleReader().read(fuzzExecSecond, second); - results.get(wasm, second); + results.get(wasm, &second); } } From 7bd42a1c7685fe7263b2f96b72283f34bde7d774 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 3 Oct 2025 14:39:35 -0700 Subject: [PATCH 03/21] work --- src/shell-interface.h | 2 +- src/tools/execution-results.h | 9 ++++++--- test/lit/exec/second.wast | 15 +++++++++++++++ test/lit/exec/second.wast.second.wast | 11 +++++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 test/lit/exec/second.wast create mode 100644 test/lit/exec/second.wast.second.wast diff --git a/src/shell-interface.h b/src/shell-interface.h index b4dee08d662..d881459fb39 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -105,7 +105,7 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { ModuleRunner* getImportInstance(Importable* import) { auto it = linkedInstances.find(import->module); if (it == linkedInstances.end()) { - Fatal() << "importGlobals: unknown import: " << import->module.str << "." + Fatal() << "getImportInstance: unknown import: " << import->module.str << "." << import->base.str; } return it->second.get(); diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index a81f167a517..97b713b6eb2 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -55,8 +55,9 @@ struct LoggingExternalInterface : public ShellExternalInterface { ModuleRunner* instance = nullptr; public: - LoggingExternalInterface(Loggings& loggings, Module& wasm) - : loggings(loggings), wasm(wasm) { + LoggingExternalInterface(Loggings& loggings, Module& wasm, + std::map> linkedInstances_ = {}) + : ShellExternalInterface(linkedInstances_), loggings(loggings), wasm(wasm) { for (auto& exp : wasm.exports) { if (exp->kind == ExternalKind::Table && exp->name == "table") { exportedTable = *exp->getInternalName(); @@ -185,6 +186,8 @@ struct LoggingExternalInterface : public ShellExternalInterface { } else if (import->base == "getTempRet0") { return {Literal(state.tempRet0)}; } + } else if (auto* inst = getImportInstance(import)) { + return inst->callExport(import->base, arguments); } std::cerr << "[LoggingExternalInterface ignoring an unknown import " << import->module << " . " << import->base << '\n'; @@ -290,9 +293,9 @@ struct ExecutionResults { if (second) { // Link and run the second module. - LoggingExternalInterface secondInterface(loggings, *second); std::map> linkedInstances; linkedInstances["primary"] = instance; + LoggingExternalInterface secondInterface(loggings, *second, linkedInstances); auto secondInstance = std::make_shared(*second, &secondInterface, linkedInstances); runModule(*second, *secondInstance, secondInterface); } diff --git a/test/lit/exec/second.wast b/test/lit/exec/second.wast new file mode 100644 index 00000000000..9665f108533 --- /dev/null +++ b/test/lit/exec/second.wast @@ -0,0 +1,15 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second.wast -q -o /dev/null 2>&1 | filecheck %s + +;; Test that the fuzz_shell.js file will run a second wasm file that is +;; provided, and call its exports as well as the first module's. + +(module + ;; CHECK: [fuzz-exec] calling first + ;; CHECK-NEXT: [fuzz-exec] note result: first => 42 + (func $first (export "first") (result i32) + (i32.const 42) + ) +) + diff --git a/test/lit/exec/second.wast.second.wast b/test/lit/exec/second.wast.second.wast new file mode 100644 index 00000000000..b6d36c3507b --- /dev/null +++ b/test/lit/exec/second.wast.second.wast @@ -0,0 +1,11 @@ +(module + (import "primary" "first" (func $first-func (result i32))) + + (func $second (export "second") (result i32) + (i32.add + (call $first-func) + (i32.const 1295) + ) + ) +) + From c6c0727d4b5ea643068aad5f0071db66a27b6914 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 3 Oct 2025 14:41:21 -0700 Subject: [PATCH 04/21] work --- test/lit/exec/second.wast | 14 ++++++++------ test/lit/exec/second.wast.second.wast | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/test/lit/exec/second.wast b/test/lit/exec/second.wast index 9665f108533..7d54223b69a 100644 --- a/test/lit/exec/second.wast +++ b/test/lit/exec/second.wast @@ -1,15 +1,17 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. ;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second.wast -q -o /dev/null 2>&1 | filecheck %s -;; Test that the fuzz_shell.js file will run a second wasm file that is -;; provided, and call its exports as well as the first module's. - (module - ;; CHECK: [fuzz-exec] calling first - ;; CHECK-NEXT: [fuzz-exec] note result: first => 42 (func $first (export "first") (result i32) (i32.const 42) ) ) +;; Test that the fuzz_shell.js file will run a second wasm file that is +;; provided, and call its exports as well as the first module's. + +;; CHECK: [fuzz-exec] calling first +;; CHECK-NEXT: [fuzz-exec] note result: first => 42 +;; CHECK: [fuzz-exec] calling second +;; CHECK-NEXT: [fuzz-exec] note result: second => 1337 + diff --git a/test/lit/exec/second.wast.second.wast b/test/lit/exec/second.wast.second.wast index b6d36c3507b..de36a5f14ca 100644 --- a/test/lit/exec/second.wast.second.wast +++ b/test/lit/exec/second.wast.second.wast @@ -2,6 +2,7 @@ (import "primary" "first" (func $first-func (result i32))) (func $second (export "second") (result i32) + ;; Test we can call the first module, linked as "primary." (i32.add (call $first-func) (i32.const 1295) From 48e038ff5e68f0e819e9bcad231d2a646af677bb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 3 Oct 2025 14:42:22 -0700 Subject: [PATCH 05/21] format --- src/shell-interface.h | 4 ++-- src/tools/execution-results.h | 12 ++++++++---- src/tools/wasm-opt.cpp | 17 +++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/shell-interface.h b/src/shell-interface.h index d881459fb39..1e68c6955ae 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -105,8 +105,8 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { ModuleRunner* getImportInstance(Importable* import) { auto it = linkedInstances.find(import->module); if (it == linkedInstances.end()) { - Fatal() << "getImportInstance: unknown import: " << import->module.str << "." - << import->base.str; + Fatal() << "getImportInstance: unknown import: " << import->module.str + << "." << import->base.str; } return it->second.get(); } diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 97b713b6eb2..1aa3fbad998 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -55,7 +55,9 @@ struct LoggingExternalInterface : public ShellExternalInterface { ModuleRunner* instance = nullptr; public: - LoggingExternalInterface(Loggings& loggings, Module& wasm, + LoggingExternalInterface( + Loggings& loggings, + Module& wasm, std::map> linkedInstances_ = {}) : ShellExternalInterface(linkedInstances_), loggings(loggings), wasm(wasm) { for (auto& exp : wasm.exports) { @@ -284,7 +286,7 @@ struct ExecutionResults { // Get results of executing a module. Optionally, provide a second module to // link with it (like fuzz_shell's second module). - void get(Module& wasm, Module* second=nullptr) { + void get(Module& wasm, Module* second = nullptr) { try { // Run the first module. LoggingExternalInterface interface(loggings, wasm); @@ -295,8 +297,10 @@ struct ExecutionResults { // Link and run the second module. std::map> linkedInstances; linkedInstances["primary"] = instance; - LoggingExternalInterface secondInterface(loggings, *second, linkedInstances); - auto secondInstance = std::make_shared(*second, &secondInterface, linkedInstances); + LoggingExternalInterface secondInterface( + loggings, *second, linkedInstances); + auto secondInstance = std::make_shared( + *second, &secondInterface, linkedInstances); runModule(*second, *secondInstance, secondInterface); } } catch (const TrapException&) { diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index 089537b1591..97bb835cd9d 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -149,14 +149,15 @@ For more on how to optimize effectively, see [&](Options* o, const std::string& arguments) { fuzzExecBefore = fuzzExecAfter = true; }) - .add( - "--fuzz-exec-second", - "", - "A second module to link with the first, for fuzz-exec-before (only " - "before, as optimizations are not applied to it)", - WasmOptOption, - Options::Arguments::One, - [&](Options* o, const std::string& arguments) { fuzzExecSecond = arguments; }) + .add("--fuzz-exec-second", + "", + "A second module to link with the first, for fuzz-exec-before (only " + "before, as optimizations are not applied to it)", + WasmOptOption, + Options::Arguments::One, + [&](Options* o, const std::string& arguments) { + fuzzExecSecond = arguments; + }) .add("--extra-fuzz-command", "-efc", "An extra command to run on the output before and after optimizing. " From 63f54be8d4df59dc9a45eb54ef1ca51d101cd497 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 3 Oct 2025 14:43:34 -0700 Subject: [PATCH 06/21] help --- test/lit/help/wasm-opt.test | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 3ee35a69bb2..9258c6ffedc 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -38,6 +38,11 @@ ;; CHECK-NEXT: after optimization, helping ;; CHECK-NEXT: fuzzing find bugs ;; CHECK-NEXT: +;; CHECK-NEXT: --fuzz-exec-second A second module to link with the +;; CHECK-NEXT: first, for fuzz-exec-before +;; CHECK-NEXT: (only before, as optimizations +;; CHECK-NEXT: are not applied to it) +;; CHECK-NEXT: ;; CHECK-NEXT: --extra-fuzz-command,-efc An extra command to run on the ;; CHECK-NEXT: output before and after ;; CHECK-NEXT: optimizing. The output is From c896f7cc5001c82a1d91338dd31434e487a26ecb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 3 Oct 2025 16:11:06 -0700 Subject: [PATCH 07/21] fix --- src/tools/execution-results.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 1aa3fbad998..0afdff0cde8 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -188,9 +188,11 @@ struct LoggingExternalInterface : public ShellExternalInterface { } else if (import->base == "getTempRet0") { return {Literal(state.tempRet0)}; } - } else if (auto* inst = getImportInstance(import)) { - return inst->callExport(import->base, arguments); + } else if (linkedInstances.count(import->module)) { + // This is from a recognized module. + return getImportInstance(import)->callExport(import->base, arguments); } + // Anything else, we ignore. std::cerr << "[LoggingExternalInterface ignoring an unknown import " << import->module << " . " << import->base << '\n'; return {}; From eb7c349fd3e5681ae2465f2f152a490218d27f63 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 3 Oct 2025 16:17:19 -0700 Subject: [PATCH 08/21] fix --- test/lit/exec/second.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/exec/second.wast b/test/lit/exec/second.wast index 7d54223b69a..5ced23bef21 100644 --- a/test/lit/exec/second.wast +++ b/test/lit/exec/second.wast @@ -7,7 +7,7 @@ ) ) -;; Test that the fuzz_shell.js file will run a second wasm file that is +;; Test that --fuzz-exec-second will run a second wasm file that is ;; provided, and call its exports as well as the first module's. ;; CHECK: [fuzz-exec] calling first From 157b75bad244f9686daa3d512e7daf475047aa7d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 6 Oct 2025 09:22:03 -0700 Subject: [PATCH 09/21] fix lit by avoiding .wast in a non-test file --- test/lit/exec/second.wast | 2 +- test/lit/exec/{second.wast.second.wast => second.wast.second} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename test/lit/exec/{second.wast.second.wast => second.wast.second} (100%) diff --git a/test/lit/exec/second.wast b/test/lit/exec/second.wast index 5ced23bef21..f94b1d82d76 100644 --- a/test/lit/exec/second.wast +++ b/test/lit/exec/second.wast @@ -1,5 +1,5 @@ -;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second.wast -q -o /dev/null 2>&1 | filecheck %s +;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second -q -o /dev/null 2>&1 | filecheck %s (module (func $first (export "first") (result i32) diff --git a/test/lit/exec/second.wast.second.wast b/test/lit/exec/second.wast.second similarity index 100% rename from test/lit/exec/second.wast.second.wast rename to test/lit/exec/second.wast.second From 17c51f9e1fb6b045e4af1b37c695480c3c0c3df6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 14 Oct 2025 09:26:24 -0700 Subject: [PATCH 10/21] go --- scripts/fuzz_opt.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index e0950b58c9c..1b2628d2189 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1786,32 +1786,32 @@ def ensure(self): # Tests linking two wasm files at runtime, and that optimizations do not break # anything. This is similar to Split(), but rather than split a wasm file into # two and link them at runtime, this starts with two separate wasm files. +# +# TODO: Document fuzzing class Two(TestCaseHandler): # Run at relatively high priority, as this is the main place we check cross- # module interactions. frequency = 1 def handle(self, wasm): - # Generate a second wasm file, unless we were given one (useful during - # reduction). + # Generate a second wasm file. (For fuzzing, we may be given one, but we + # still generate it to avoid changes to random numbers later; we just + # discard it after.) second_wasm = abspath('second.wasm') + second_input = abspath('second_input.dat') + make_random_input(second_size, second_input) + args = [second_input, '-ttf'] + # Most of the time, use the first wasm as an import to the second. + if random.random() < 0.8: + args += ['--fuzz-import=' + wasm] + given = os.environ.get('BINARYEN_SECOND_WASM') - if given: - # TODO: should we de-nan this etc. as with the primary? - shutil.copyfile(given, second_wasm) + if not given: + # Finish generating it. + run([in_bin('wasm-opt'), '-o', second_wasm] + args + GEN_ARGS + FEATURE_OPTS) else: - # generate a second wasm file to merge. pick a smaller size when - # the main wasm file is smaller, so reduction shrinks this too. - wasm_size = os.stat(wasm).st_size - second_size = min(wasm_size, random_size()) - - second_input = abspath('second_input.dat') - make_random_input(second_size, second_input) - args = [second_input, '-ttf', '-o', second_wasm] - # Most of the time, use the first wasm as an import to the second. - if random.random() < 0.8: - args += ['--fuzz-import=' + wasm] - run([in_bin('wasm-opt')] + args + GEN_ARGS + FEATURE_OPTS) + # Use the given one. + shutil.copyfile(given, second_wasm) # Run the wasm. # From 01ed1fa165ffec5b35114ebdca1f1e024b46ee4c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 14 Oct 2025 09:27:48 -0700 Subject: [PATCH 11/21] go --- scripts/fuzz_opt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 1b2628d2189..2f3b6f7dee1 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1799,7 +1799,7 @@ def handle(self, wasm): # discard it after.) second_wasm = abspath('second.wasm') second_input = abspath('second_input.dat') - make_random_input(second_size, second_input) + make_random_input(random_size(), second_input) args = [second_input, '-ttf'] # Most of the time, use the first wasm as an import to the second. if random.random() < 0.8: From e3bb71cc8933d7fa4f67c9a750ab65bf735b4504 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 14 Oct 2025 09:28:54 -0700 Subject: [PATCH 12/21] go --- scripts/fuzz_opt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 2f3b6f7dee1..3ed9468f57b 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1807,10 +1807,10 @@ def handle(self, wasm): given = os.environ.get('BINARYEN_SECOND_WASM') if not given: - # Finish generating it. + print('Generate second wasm') run([in_bin('wasm-opt'), '-o', second_wasm] + args + GEN_ARGS + FEATURE_OPTS) else: - # Use the given one. + print('Use given second wasm') shutil.copyfile(given, second_wasm) # Run the wasm. From b2014775d26c6df1f24001acdafa93b499fa0327 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 14 Oct 2025 09:29:57 -0700 Subject: [PATCH 13/21] go --- scripts/fuzz_opt.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 3ed9468f57b..9bcd8ecb2db 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1799,7 +1799,12 @@ def handle(self, wasm): # discard it after.) second_wasm = abspath('second.wasm') second_input = abspath('second_input.dat') - make_random_input(random_size(), second_input) + + # XXX + wasm_size = os.stat(wasm).st_size + second_size = min(wasm_size, random_size()) + + make_random_input(second_size, second_input) args = [second_input, '-ttf'] # Most of the time, use the first wasm as an import to the second. if random.random() < 0.8: From 3c9f5902857e0630e7dac4c7d112788c296b794b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 14 Oct 2025 09:35:12 -0700 Subject: [PATCH 14/21] go --- scripts/fuzz_opt.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 9bcd8ecb2db..58356471aba 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1787,7 +1787,14 @@ def ensure(self): # anything. This is similar to Split(), but rather than split a wasm file into # two and link them at runtime, this starts with two separate wasm files. # -# TODO: Document fuzzing +# Fuzzing failures here is a little trickier, as there are two wasm files. +# First, reduce on the primary file, by finding the secondary one in the log +# (usually out/test/second.wasm), copy that to the side, and add +# +# BINARYEN_SECOND_WASM=${saved_second} +# +# in the env. That will keep the secondary wasm fixed as you reduce the primary +# one. class Two(TestCaseHandler): # Run at relatively high priority, as this is the main place we check cross- # module interactions. @@ -1815,7 +1822,7 @@ def handle(self, wasm): print('Generate second wasm') run([in_bin('wasm-opt'), '-o', second_wasm] + args + GEN_ARGS + FEATURE_OPTS) else: - print('Use given second wasm') + print(f'Use given second wasm {given}') shutil.copyfile(given, second_wasm) # Run the wasm. From 592b021c8f7f5f3665c6a2fd13b186e2cc819865 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 14 Oct 2025 13:53:55 -0700 Subject: [PATCH 15/21] simpl --- scripts/fuzz_opt.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 58356471aba..a819a863e8b 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1806,11 +1806,7 @@ def handle(self, wasm): # discard it after.) second_wasm = abspath('second.wasm') second_input = abspath('second_input.dat') - - # XXX - wasm_size = os.stat(wasm).st_size - second_size = min(wasm_size, random_size()) - + second_size = random_size() make_random_input(second_size, second_input) args = [second_input, '-ttf'] # Most of the time, use the first wasm as an import to the second. From 3b8ffa4f7e89b929f6baa8a2966f1994bc673c8a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 14 Oct 2025 14:13:49 -0700 Subject: [PATCH 16/21] work --- scripts/fuzz_opt.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index a819a863e8b..8a8289aa747 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1795,6 +1795,10 @@ def ensure(self): # # in the env. That will keep the secondary wasm fixed as you reduce the primary # one. +# +# It is better to reduce the second one first, as then it will import less from +# the first (otherwise, when the second one imports many things, the first will +# fail to remove exports that are used). class Two(TestCaseHandler): # Run at relatively high priority, as this is the main place we check cross- # module interactions. From ff450c70b3465006bf962522198e15ece4e4ca7e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 13:50:28 -0700 Subject: [PATCH 17/21] undo --- src/tools/execution-results.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 4f23c4ef306..a59b5deee4f 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -83,9 +83,6 @@ struct LoggingExternalInterface : public ShellExternalInterface { if (imported->module == tag->module && imported->base == tag->base) { return imported; } - } else if (linkedInstances.count(import->module)) { - // This is from a recognized module. - return getImportInstance(import)->callExport(import->base, arguments); } Fatal() << "missing host tag " << tag->module << '.' << tag->base; } From 76829f8d99c1f6ad6298eaa47dfb8c6c118915fc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 13:52:48 -0700 Subject: [PATCH 18/21] comments --- scripts/fuzz_opt.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 8a8289aa747..b86f88a4a5f 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1788,7 +1788,7 @@ def ensure(self): # two and link them at runtime, this starts with two separate wasm files. # # Fuzzing failures here is a little trickier, as there are two wasm files. -# First, reduce on the primary file, by finding the secondary one in the log +# You can reduce the primary file by finding the secondary one in the log # (usually out/test/second.wasm), copy that to the side, and add # # BINARYEN_SECOND_WASM=${saved_second} @@ -1796,9 +1796,12 @@ def ensure(self): # in the env. That will keep the secondary wasm fixed as you reduce the primary # one. # -# It is better to reduce the second one first, as then it will import less from +# Note it may be better to reduce the second one first, so it imports less from # the first (otherwise, when the second one imports many things, the first will -# fail to remove exports that are used). +# fail to remove exports that are used). To reduce the second one, use a reducer +# script that sets BINARYEN_SECOND_WASM with the file being reduced, and keep +# the primary module fixed by providing a wasm file as an argument to this +# script. class Two(TestCaseHandler): # Run at relatively high priority, as this is the main place we check cross- # module interactions. From ab7d9b0a239696d15940019809f98eb56be3e82c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 16:52:31 -0700 Subject: [PATCH 19/21] work --- scripts/fuzz_opt.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index b86f88a4a5f..b40d3f2ee56 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1798,10 +1798,16 @@ def ensure(self): # # Note it may be better to reduce the second one first, so it imports less from # the first (otherwise, when the second one imports many things, the first will -# fail to remove exports that are used). To reduce the second one, use a reducer -# script that sets BINARYEN_SECOND_WASM with the file being reduced, and keep -# the primary module fixed by providing a wasm file as an argument to this -# script. +# fail to remove exports that are used). To reduce the second one, set +# +# BINARYEN_FIRST_WASM=${saved_first} +# +# The reduce.sh script will then do the right thing, using that as the first +# wasm, and reducing on the second one, if you replace "original.wasm" in the +# command with "second.wasm" as needed. +# +# In both cases, make sure to copy the files to a saved location first (do not +# use a path to the scratch files that get constantly overwritten). class Two(TestCaseHandler): # Run at relatively high priority, as this is the main place we check cross- # module interactions. @@ -2605,9 +2611,18 @@ def get_random_opts(): %(wasm_opt)s %(features)s %(temp_wasm)s echo " " $? -# run the command echo "The following value should be 1:" -./scripts/fuzz_opt.py %(auto_init)s --binaryen-bin %(bin)s %(seed)d %(temp_wasm)s > o 2> e + +if [ -z "$BINARYEN_FIRST_WASM" ]; then + # run the command normally + ./scripts/fuzz_opt.py %(auto_init)s --binaryen-bin %(bin)s %(seed)d %(temp_wasm)s > o 2> e +else + # BINARYEN_FIRST_WASM was provided so we should actually reduce the *second* + # file. pass the first one in as the main file, and use the env var for the + # second. + BINARYEN_SECOND_WASM=%(temp_wasm)s ./scripts/fuzz_opt.py %(auto_init)s --binaryen-bin %(bin)s %(seed)d $BINARYEN_FIRST_WASM > o 2> e +fi + echo " " $? # From 9511513c8f19b3e5060be06104ff905460b5b0b6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Oct 2025 08:37:05 -0700 Subject: [PATCH 20/21] clarify --- scripts/fuzz_opt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index b40d3f2ee56..4d13997d193 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1804,7 +1804,8 @@ def ensure(self): # # The reduce.sh script will then do the right thing, using that as the first # wasm, and reducing on the second one, if you replace "original.wasm" in the -# command with "second.wasm" as needed. +# reduction command (the command that this fuzzer script recommended that you +# run) with "second.wasm" as needed. # # In both cases, make sure to copy the files to a saved location first (do not # use a path to the scratch files that get constantly overwritten). From acb2dbb3d6205c55d05f3461a456a7ce50a087a0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 30 Oct 2025 08:38:45 -0700 Subject: [PATCH 21/21] clarify --- scripts/fuzz_opt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 4d13997d193..f3c81af42ad 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1816,8 +1816,8 @@ class Two(TestCaseHandler): def handle(self, wasm): # Generate a second wasm file. (For fuzzing, we may be given one, but we - # still generate it to avoid changes to random numbers later; we just - # discard it after.) + # still do the work to prepare to generate it, as that consumes random + # values, and we don't want that to affect anything later.) second_wasm = abspath('second.wasm') second_input = abspath('second_input.dat') second_size = random_size()