From 3a436dd546e4a89d7d2fe22f85072a48591b9e17 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 3 Oct 2025 14:21:42 -0700 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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