From a8029a3ff54975ccf9961a8fec4fd33822893e8c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Mar 2026 09:54:14 -0800 Subject: [PATCH 01/34] go --- scripts/fuzz_shell.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index f5c4bb54a56..0cfb73b281c 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -550,7 +550,7 @@ function build(binary, isSecond) { let tasks = []; for (let e of relevantExports) { let name, value; - if (typeof e === 'string') { + if (typeof e === 'string') { // XXX // We are given a string name to call. Look it up in the global namespace. name = e; value = exports[e]; @@ -562,7 +562,7 @@ function build(binary, isSecond) { } if (typeof value !== 'function') { - continue; + continue; // XXX can at least log the export, logRef or logValue } // A task is a name + a function to call. For an export, the function is From 778bc6204cd91da023188cc39da1e59587ad0210 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Mar 2026 15:43:19 -0800 Subject: [PATCH 02/34] go --- scripts/fuzz_shell.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 0cfb73b281c..2be9ed38f7c 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -550,19 +550,27 @@ function build(binary, isSecond) { let tasks = []; for (let e of relevantExports) { let name, value; - if (typeof e === 'string') { // XXX + if (typeof e === 'string') { // We are given a string name to call. Look it up in the global namespace. name = e; value = exports[e]; } else { - // We are given an object form exportList, which has both a name and a + // We are given an object from exportList, which has both a name and a // value. name = e.name; value = e.value; } if (typeof value !== 'function') { - continue; // XXX can at least log the export, logRef or logValue + // This is not a function, but we can still do some operations on this + // export, possibly finding interesting behavior in the VM. + if (typeof value === 'object') { + // An object allows more operations than an arbitrary value. + logRef(value); + } else { + logValue(value); + } + continue; } // A task is a name + a function to call. For an export, the function is From 33d66f6922feddec76a08134fa16ed13b85f8dd3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Mar 2026 16:13:16 -0800 Subject: [PATCH 03/34] go --- scripts/fuzz_opt.py | 3 ++- scripts/fuzz_shell.js | 17 +++++++++++++---- src/tools/execution-results.h | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index a24c7b158b9..00335d64dc7 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1965,6 +1965,7 @@ def compare_to_merged_output(self, output, merged_output): # Comparing the original output from two files to the output after # merging them is not trivial. First, remove the extra logging that # --fuzz-exec-second adds. + output = output.replace('[fuzz-exec] logging second module\n', '') output = output.replace('[fuzz-exec] running second module\n', '') # Fix up both outputs. @@ -2284,7 +2285,7 @@ def handle(self, wasm): Merge(), Split(), RoundtripText(), - ClusterFuzz(), + #ClusterFuzz(), Two(), PreserveImportsExports(), BranchHintPreservation(), diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 2be9ed38f7c..d8f1de18c69 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -562,11 +562,20 @@ function build(binary, isSecond) { } if (typeof value !== 'function') { - // This is not a function, but we can still do some operations on this - // export, possibly finding interesting behavior in the VM. + // This is not a function, but we can still log it and do other stuff. + console.log(`[fuzz-exec] logging ${name}`); if (typeof value === 'object') { - // An object allows more operations than an arbitrary value. - logRef(value); + // As in logRef, try some interesting operations to look for VM issues. + JSON.stringify(value); + if (value) { + value.foobar; + // Look at the exported value itself, not the global wrapper. + value = value.value; + } + } + if (typeof value === 'object') { + // logRef can do a little more than logValue, so use it when possible. + logRef(value.value); } else { logValue(value); } diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index cea9c1c5234..37589a9f1cf 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -442,6 +442,13 @@ struct ExecutionResults { instantiate(*secondInstance, *secondInterface); } + // Log non-function exports. + logExports(wasm, *instance); + if (second) { + std::cout << "[fuzz-exec] logging second module\n"; + logExports(*second, *secondInstance); + } + // Run. callExports(wasm, *instance); if (second) { @@ -467,6 +474,31 @@ struct ExecutionResults { interface.setModuleRunner(&instance); } + // Log all non-function exports. + void logExports(Module& wasm, ModuleRunner& instance) { + for (auto& exp : wasm.exports) { + Literals* value = nullptr; + switch (exp->kind) { + case ExternalKind::Function: { + continue; + } + case ExternalKind::Global: { + value = instance.getExportedGlobalOrNull(exp->name); + break; + } + default: { + Fatal() << "bad exported kind " << exp->kind << " : " << exp->name << '\n'; + } + } + std::cout << "[fuzz-exec] logging " << exp->name << "\n"; + assert(value); + assert(value->size() == 1); + std::cout << "[LoggingExternalInterface logging "; + printValue((*value)[0]); + std::cout << "]\n"; + } + } + void callExports(Module& wasm, ModuleRunner& instance) { // execute all exported methods (that are therefore preserved through // opts) From 97e494a81e7caeaea8f56fc1ffa2c0a5f4b1fe54 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Mar 2026 16:14:33 -0800 Subject: [PATCH 04/34] more --- scripts/fuzz_shell.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index d8f1de18c69..9db678028e8 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -575,7 +575,7 @@ function build(binary, isSecond) { } if (typeof value === 'object') { // logRef can do a little more than logValue, so use it when possible. - logRef(value.value); + logRef(value); } else { logValue(value); } From c2a459928f6de3e476ac9939774ab0b96b4f3b89 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Mar 2026 16:19:03 -0800 Subject: [PATCH 05/34] go --- scripts/fuzz_shell.js | 6 +++++- src/tools/execution-results.h | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 9db678028e8..60526b6e229 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -563,16 +563,20 @@ function build(binary, isSecond) { if (typeof value !== 'function') { // This is not a function, but we can still log it and do other stuff. - console.log(`[fuzz-exec] logging ${name}`); if (typeof value === 'object') { // As in logRef, try some interesting operations to look for VM issues. JSON.stringify(value); if (value) { value.foobar; + if (value instanceof WebAssembly.Table) { + // No value to log here. TODO: Perhaps log something? + continue; + } // Look at the exported value itself, not the global wrapper. value = value.value; } } + console.log(`[fuzz-exec] logging ${name}`); if (typeof value === 'object') { // logRef can do a little more than logValue, so use it when possible. logRef(value); diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 37589a9f1cf..c729451854a 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -486,6 +486,10 @@ struct ExecutionResults { value = instance.getExportedGlobalOrNull(exp->name); break; } + case ExternalKind::Table: { + // TODO: Perhaps we could print something here? + continue; + } default: { Fatal() << "bad exported kind " << exp->kind << " : " << exp->name << '\n'; } From 7bff825254e7ee7318c644b4d49149d21a403acd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Mar 2026 16:40:18 -0800 Subject: [PATCH 06/34] go --- scripts/fuzz_opt.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 00335d64dc7..58432f19a02 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -426,6 +426,9 @@ def pick_initial_contents(): # --fuzz-exec reports calls as [fuzz-exec] calling foo FUZZ_EXEC_CALL_PREFIX = '[fuzz-exec] calling' +# --fuzz-exec logs globals etc. as [fuzz-exec] logging foo +FUZZ_EXEC_LOG_PREFIX = '[fuzz-exec] logging' + # --fuzz-exec reports a stack limit using this notation STACK_LIMIT = '[trap stack limit]' @@ -1996,6 +1999,10 @@ def compare_to_merged_output(self, output, merged_output): # for different foo/bar. Just copy the original. assert b.startswith(FUZZ_EXEC_CALL_PREFIX) merged_output_lines[i] = output_lines[i] + elif a.startswith(FUZZ_EXEC_LOG_PREFIX): + # As above, but for logging. + assert b.startswith(FUZZ_EXEC_LOG_PREFIX) + merged_output_lines[i] = output_lines[i] elif a.startswith(FUZZ_EXEC_NOTE_RESULT): # Fix up # [fuzz-exec] note result: foo/bar => 42 From c26fb4c5ce09b87848d01947ad2a24e5f0a01081 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 5 Mar 2026 16:45:58 -0800 Subject: [PATCH 07/34] go --- scripts/fuzz_shell.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 60526b6e229..ad2d02576ac 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -573,7 +573,17 @@ function build(binary, isSecond) { continue; } // Look at the exported value itself, not the global wrapper. - value = value.value; + try { + value = value.value; + } catch (e) { + if (e.message.startsWith('get WebAssembly.Global.value')) { + // Just log a null instead of a value we cannot access from JS, + // like an exnref. + value = null; + } else { + throw e; + } + } } } console.log(`[fuzz-exec] logging ${name}`); From 86f8fad58d91ac7f56c684dd84664f44975a682a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 6 Mar 2026 09:40:06 -0800 Subject: [PATCH 08/34] go --- scripts/fuzz_shell.js | 61 +++++++++++++++-------------- src/tools/execution-results.h | 73 +++++++++++------------------------ 2 files changed, 54 insertions(+), 80 deletions(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index ad2d02576ac..563c9bd3ef6 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -561,38 +561,39 @@ function build(binary, isSecond) { value = e.value; } - if (typeof value !== 'function') { - // This is not a function, but we can still log it and do other stuff. - if (typeof value === 'object') { - // As in logRef, try some interesting operations to look for VM issues. - JSON.stringify(value); - if (value) { - value.foobar; - if (value instanceof WebAssembly.Table) { - // No value to log here. TODO: Perhaps log something? - continue; - } - // Look at the exported value itself, not the global wrapper. - try { - value = value.value; - } catch (e) { - if (e.message.startsWith('get WebAssembly.Global.value')) { - // Just log a null instead of a value we cannot access from JS, - // like an exnref. - value = null; - } else { - throw e; - } - } + if (value instanceof WebAssembly.Global) { + // We can log a global value and do other operations to check for bugs. + // First, do some operations on the Global wrapper itself. + JSON.stringify(value); + value.foobar; + // Look at the exported value itself, not the global wrapper. + let actualValue; + try { + actualValue = value.value; + } catch (e) { + if (e.message.startsWith('get WebAssembly.Global.value')) { + // Just log a null instead of a value we cannot access from JS, + // like an exnref. + actualValue = null; + } else { + throw e; } } - console.log(`[fuzz-exec] logging ${name}`); - if (typeof value === 'object') { - // logRef can do a little more than logValue, so use it when possible. - logRef(value); - } else { - logValue(value); - } + // Log the actual value, by building a lambda to be called along the + // functions. + value = () => { + console.log(`[fuzz-exec] logging ${name}`); + if (typeof actualValue === 'object') { + // logRef can do a little more than logValue, so use it when possible. + logRef(actualValue); + } else { + logValue(actualValue); + } + }; + } + + if (typeof value !== 'function') { + // Nothing we can call. continue; } diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index c729451854a..4e186c93b56 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -442,13 +442,6 @@ struct ExecutionResults { instantiate(*secondInstance, *secondInterface); } - // Log non-function exports. - logExports(wasm, *instance); - if (second) { - std::cout << "[fuzz-exec] logging second module\n"; - logExports(*second, *secondInstance); - } - // Run. callExports(wasm, *instance); if (second) { @@ -474,56 +467,36 @@ struct ExecutionResults { interface.setModuleRunner(&instance); } - // Log all non-function exports. - void logExports(Module& wasm, ModuleRunner& instance) { - for (auto& exp : wasm.exports) { - Literals* value = nullptr; - switch (exp->kind) { - case ExternalKind::Function: { - continue; - } - case ExternalKind::Global: { - value = instance.getExportedGlobalOrNull(exp->name); - break; - } - case ExternalKind::Table: { - // TODO: Perhaps we could print something here? - continue; - } - default: { - Fatal() << "bad exported kind " << exp->kind << " : " << exp->name << '\n'; - } - } - std::cout << "[fuzz-exec] logging " << exp->name << "\n"; - assert(value); - assert(value->size() == 1); - std::cout << "[LoggingExternalInterface logging "; - printValue((*value)[0]); - std::cout << "]\n"; - } - } - void callExports(Module& wasm, ModuleRunner& 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); - std::cout << '\n'; + if (exp->kind == ExternalKind::Function) { + 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); + std::cout << '\n'; + } } } + } else if (exp->kind == ExternalKind::Global) { + // Log the global's value. + std::cout << "[fuzz-exec] logging " << exp->name << "\n"; + Literals* value = instance.getExportedGlobalOrNull(exp->name); + assert(value); + assert(value->size() == 1); + std::cout << "[LoggingExternalInterface logging "; + printValue((*value)[0]); + std::cout << "]\n"; } + // Ignore other exports for now. TODO } } From a01f99a6b95ec9cbfdc948d839077329efbaaa1c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 6 Mar 2026 09:42:05 -0800 Subject: [PATCH 09/34] test --- test/lit/exec/fuzzing-api.wast | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index 00db75a19be..940a486503c 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -5,6 +5,8 @@ ;; Test the fuzzing-support module imports. (module + (type $struct (struct)) + (import "fuzzing-support" "log-i32" (func $log-i32 (param i32))) (import "fuzzing-support" "log-f64" (func $log-f64 (param f64))) (import "fuzzing-support" "log-anyref" (func $log-anyref (param anyref))) @@ -32,10 +34,17 @@ (table $table 10 20 funcref) + (global $global (mut i32) (i32.const 42)) + + (global $global-immref anyref (struct.new $struct)) + ;; Note that the exported table appears first here, but in the binary and in ;; the IR it is actually last, as we always add function exports first. (export "table" (table $table)) + (export "global" (global $global)) + (export "global-immref" (global $global-immref)) + ;; CHECK: [fuzz-exec] calling logging ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] @@ -450,6 +459,10 @@ ;; CHECK: [fuzz-exec] calling return-externref-exception ;; CHECK-NEXT: [fuzz-exec] note result: return-externref-exception => object + ;; CHECK-NEXT: [fuzz-exec] logging global + ;; CHECK-NEXT: [LoggingExternalInterface logging 42] + ;; CHECK-NEXT: [fuzz-exec] logging global-immref + ;; CHECK-NEXT: [LoggingExternalInterface logging object] ;; CHECK-NEXT: warning: no passes specified, not doing any work (func $return-externref-exception (export "return-externref-exception") (result externref) ;; Call JS table.set in a way that throws (on out of bounds). The JS exception @@ -582,6 +595,10 @@ ;; CHECK: [fuzz-exec] calling return-externref-exception ;; CHECK-NEXT: [fuzz-exec] note result: return-externref-exception => object +;; CHECK-NEXT: [fuzz-exec] logging global +;; CHECK-NEXT: [LoggingExternalInterface logging 42] +;; CHECK-NEXT: [fuzz-exec] logging global-immref +;; CHECK-NEXT: [LoggingExternalInterface logging object] ;; CHECK-NEXT: [fuzz-exec] comparing catch-js-tag ;; CHECK-NEXT: [fuzz-exec] comparing do-sleep ;; CHECK-NEXT: [fuzz-exec] comparing export.calling From f05bf56ff38403d7287e0f91057babc0bd91e69f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 6 Mar 2026 09:45:49 -0800 Subject: [PATCH 10/34] go --- scripts/fuzz_shell.js | 1 - src/tools/execution-results.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 563c9bd3ef6..04e1a549b8e 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -582,7 +582,6 @@ function build(binary, isSecond) { // Log the actual value, by building a lambda to be called along the // functions. value = () => { - console.log(`[fuzz-exec] logging ${name}`); if (typeof actualValue === 'object') { // logRef can do a little more than logValue, so use it when possible. logRef(actualValue); diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 4e186c93b56..f5af580dca6 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -488,7 +488,7 @@ struct ExecutionResults { } } else if (exp->kind == ExternalKind::Global) { // Log the global's value. - std::cout << "[fuzz-exec] logging " << exp->name << "\n"; + std::cout << "[fuzz-exec] calling " << exp->name << "\n"; Literals* value = instance.getExportedGlobalOrNull(exp->name); assert(value); assert(value->size() == 1); From 612b26b462409ae70505792712834beac7065376 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 6 Mar 2026 10:03:05 -0800 Subject: [PATCH 11/34] go --- scripts/fuzz_opt.py | 8 ++++---- src/passes/LegalizeJSInterface.cpp | 14 +++++++++++++- .../passes/legalize-and-prune-js-interface.wast | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 58432f19a02..451feaa43f4 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2288,12 +2288,12 @@ def handle(self, wasm): CheckDeterminism(), Wasm2JS(), TrapsNeverHappen(), - CtorEval(), - Merge(), - Split(), + #CtorEval(), + #Merge(), + #Split(), RoundtripText(), #ClusterFuzz(), - Two(), + #Two(), PreserveImportsExports(), BranchHintPreservation(), ] diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index 5eb43a36747..5a3d8841e03 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -414,7 +414,19 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { ReFinalize().run(getPassRunner(), module); ReFinalize().runOnModuleCode(getPassRunner(), module); - // TODO: globals etc. + // Globals. + std::vector illegalExports; + for (auto& exp : module->exports) { + if (exp->kind == ExternalKind::Global) { + auto name = *exp->getInternalName(); + if (isIllegal(module->getGlobal(name)->type)) { + illegalExports.push_back(exp->name); + } + } + } + for (auto name : illegalExports) { + module->removeExport(name); + } } bool isIllegal(Type type) { diff --git a/test/lit/passes/legalize-and-prune-js-interface.wast b/test/lit/passes/legalize-and-prune-js-interface.wast index c1221959194..dbd168edc55 100644 --- a/test/lit/passes/legalize-and-prune-js-interface.wast +++ b/test/lit/passes/legalize-and-prune-js-interface.wast @@ -245,8 +245,23 @@ ;; and also prune the export, so it remains neither an import nor an export. (export "imported-v128" (func $imported-v128)) ) + ;; CHECK: (type $0 (func (result v128))) ;; CHECK: (func $imported-v128 (type $0) (result v128) ;; CHECK-NEXT: (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000) ;; CHECK-NEXT: ) +(module + ;; CHECK: (global $i32 i32 (i32.const 42)) + (global $i32 i32 (i32.const 42)) + + ;; CHECK: (global $v128 v128 (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)) + (global $v128 v128 (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)) + + ;; The illegal export will vanish, but not the legal one. + + (export "illegal" (global $v128)) + ;; CHECK: (export "legal" (global $i32)) + (export "legal" (global $i32)) +) + From f35fcce3c14435a4fe92cd9b29c075d86786af28 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 6 Mar 2026 10:28:34 -0800 Subject: [PATCH 12/34] fix --- scripts/fuzz_shell.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 04e1a549b8e..4f003616dc9 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -143,7 +143,7 @@ function printed(x, y) { } else if (typeof x === 'bigint') { // Print bigints in legalized form, which is two 32-bit numbers of the low // and high bits. - return (Number(x) | 0) + ' ' + (Number(x >> 32n) | 0) + return (Number(x & 0xffffffffn) | 0) + ' ' + (Number(x >> 32n) | 0) } else if (typeof x !== 'number') { // Something that is not a number or string, like a reference. We can't // print a reference because it could look different after opts - imagine From b055b9fd0ebb7ad4ff37f051298baa7f0c27e5e3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 6 Mar 2026 10:35:24 -0800 Subject: [PATCH 13/34] go --- scripts/wasm2js.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/wasm2js.js b/scripts/wasm2js.js index 2a09eb91221..2e21a0eeb9f 100644 --- a/scripts/wasm2js.js +++ b/scripts/wasm2js.js @@ -23,6 +23,12 @@ var WebAssembly = { return ret; }, + Global: function(opts) { + return { + value: null + }; + }, + Module: function(binary) { // TODO: use the binary and info somehow - right now the wasm2js output is embedded in // the main JS From c77cd714742504d29ad137eb8bf17c43dd7d90d7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 6 Mar 2026 10:53:57 -0800 Subject: [PATCH 14/34] go --- scripts/wasm2js.js | 10 ++++++---- src/wasm2js.h | 7 ++++++- test/wasm2js/export_global.2asm.js | 4 ++-- test/wasm2js/export_global.2asm.js.opt | 4 ++-- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/scripts/wasm2js.js b/scripts/wasm2js.js index 2e21a0eeb9f..2fd76e99538 100644 --- a/scripts/wasm2js.js +++ b/scripts/wasm2js.js @@ -23,10 +23,12 @@ var WebAssembly = { return ret; }, - Global: function(opts) { - return { - value: null - }; + // Allow objects to become WebAssembly.Global instances. This is important in + // the fuzzer, which logs globals and functions but not other things. + Global: class Global {}, + function makeGlobal(obj) { + Object.setPrototypeOf(obj, WebAssembly.Global.prototype); + return obj; }, Module: function(binary) { diff --git a/src/wasm2js.h b/src/wasm2js.h index bc43e623885..569df36da75 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -835,8 +835,13 @@ void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) { object, IString("value"), setterParam, block); } + // Call WebAssembly.makeGlobal to make it a WebAssembly.Global instance. + Ref call = ValueBuilder::makeCall(ValueBuilder::makeDot( + ValueBuilder::makeName("WebAssembly"), ValueBuilder::makeName("makeGlobal"))); + call[2]->push_back(object); + ValueBuilder::appendToObjectWithQuotes( - exports, fromName(export_->name, NameScope::Export), object); + exports, fromName(export_->name, NameScope::Export), call); break; } diff --git a/test/wasm2js/export_global.2asm.js b/test/wasm2js/export_global.2asm.js index 61c28d76589..8fe1713430f 100644 --- a/test/wasm2js/export_global.2asm.js +++ b/test/wasm2js/export_global.2asm.js @@ -16,14 +16,14 @@ function asmFunc(imports) { } return { - "HELLO": { + "HELLO": WebAssembly.makeGlobal({ get value() { return global0; }, set value(_global0) { global0 = _global0; } - }, + }), "helloWorld": $0 }; } diff --git a/test/wasm2js/export_global.2asm.js.opt b/test/wasm2js/export_global.2asm.js.opt index 6706698f9cc..b70a917bdb2 100644 --- a/test/wasm2js/export_global.2asm.js.opt +++ b/test/wasm2js/export_global.2asm.js.opt @@ -16,14 +16,14 @@ function asmFunc(imports) { } return { - "HELLO": { + "HELLO": WebAssembly.makeGlobal({ get value() { return global0; }, set value(_global0) { global0 = _global0; } - }, + }), "helloWorld": $0 }; } From 12f9c58ca1fa4e736b3f0ee542c0815694aef1ad Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 6 Mar 2026 10:59:57 -0800 Subject: [PATCH 15/34] undo --- scripts/wasm2js.js | 8 -------- src/wasm2js.h | 7 +------ test/wasm2js/export_global.2asm.js | 4 ++-- test/wasm2js/export_global.2asm.js.opt | 4 ++-- 4 files changed, 5 insertions(+), 18 deletions(-) diff --git a/scripts/wasm2js.js b/scripts/wasm2js.js index 2fd76e99538..2a09eb91221 100644 --- a/scripts/wasm2js.js +++ b/scripts/wasm2js.js @@ -23,14 +23,6 @@ var WebAssembly = { return ret; }, - // Allow objects to become WebAssembly.Global instances. This is important in - // the fuzzer, which logs globals and functions but not other things. - Global: class Global {}, - function makeGlobal(obj) { - Object.setPrototypeOf(obj, WebAssembly.Global.prototype); - return obj; - }, - Module: function(binary) { // TODO: use the binary and info somehow - right now the wasm2js output is embedded in // the main JS diff --git a/src/wasm2js.h b/src/wasm2js.h index 569df36da75..bc43e623885 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -835,13 +835,8 @@ void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) { object, IString("value"), setterParam, block); } - // Call WebAssembly.makeGlobal to make it a WebAssembly.Global instance. - Ref call = ValueBuilder::makeCall(ValueBuilder::makeDot( - ValueBuilder::makeName("WebAssembly"), ValueBuilder::makeName("makeGlobal"))); - call[2]->push_back(object); - ValueBuilder::appendToObjectWithQuotes( - exports, fromName(export_->name, NameScope::Export), call); + exports, fromName(export_->name, NameScope::Export), object); break; } diff --git a/test/wasm2js/export_global.2asm.js b/test/wasm2js/export_global.2asm.js index 8fe1713430f..61c28d76589 100644 --- a/test/wasm2js/export_global.2asm.js +++ b/test/wasm2js/export_global.2asm.js @@ -16,14 +16,14 @@ function asmFunc(imports) { } return { - "HELLO": WebAssembly.makeGlobal({ + "HELLO": { get value() { return global0; }, set value(_global0) { global0 = _global0; } - }), + }, "helloWorld": $0 }; } diff --git a/test/wasm2js/export_global.2asm.js.opt b/test/wasm2js/export_global.2asm.js.opt index b70a917bdb2..6706698f9cc 100644 --- a/test/wasm2js/export_global.2asm.js.opt +++ b/test/wasm2js/export_global.2asm.js.opt @@ -16,14 +16,14 @@ function asmFunc(imports) { } return { - "HELLO": WebAssembly.makeGlobal({ + "HELLO": { get value() { return global0; }, set value(_global0) { global0 = _global0; } - }), + }, "helloWorld": $0 }; } From 9d27b0aa9bbc4d84bf641f64a163c636ba0a1cc6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 6 Mar 2026 11:03:57 -0800 Subject: [PATCH 16/34] go --- scripts/fuzz_shell.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index 4f003616dc9..abb7142a810 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -198,6 +198,11 @@ function callFunc(func) { return func.apply(null, args); } +// wasm2js does not define RuntimeError, so use that to check for it. wasm2js +// overrides the entire WebAssembly object with a polyfill, so we know exactly +// what it contains, and we need to handle some things differently below. +var wasm2js = !WebAssembly.RuntimeError; + // Calls a given function in a try-catch. Return 1 if an exception was thrown. // If |rethrow| is set, and an exception is thrown, it is caught and rethrown. // Wasm traps are not swallowed (see details below). @@ -213,11 +218,7 @@ function callFunc(func) { // We only want to catch exceptions, not wasm traps: traps should still // halt execution. Handling this requires different code in wasm2js, so - // check for that first (wasm2js does not define RuntimeError, so use - // that for the check - when wasm2js is run, we override the entire - // WebAssembly object with a polyfill, so we know exactly what it - // contains). - var wasm2js = !WebAssembly.RuntimeError; + // check for that first. if (!wasm2js) { // When running native wasm, we can detect wasm traps. if (e instanceof WebAssembly.RuntimeError) { @@ -561,7 +562,12 @@ function build(binary, isSecond) { value = e.value; } - if (value instanceof WebAssembly.Global) { + // Check for a global. Note we must be careful in wasm2js mode, where we + // can't do instanceof here (the wasm polyfill there doesn't have such + // things). But in wasm2js any non-function is a global, so things are + // simple there. + if ((wasm2js && typeof value !== 'function') || + (!wasm2js && (value instanceof WebAssembly.Global))) { // We can log a global value and do other operations to check for bugs. // First, do some operations on the Global wrapper itself. JSON.stringify(value); From 331e7a939937a4dc88f7608677a8a0b5244d1076 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 6 Mar 2026 11:30:04 -0800 Subject: [PATCH 17/34] work --- scripts/fuzz_shell.js | 7 ++-- src/passes/RemoveNonJSOps.cpp | 20 +++++++++--- .../passes/stub-unsupported-js-globals.wast | 32 +++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 test/lit/passes/stub-unsupported-js-globals.wast diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index abb7142a810..c07d6deb80a 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -564,10 +564,9 @@ function build(binary, isSecond) { // Check for a global. Note we must be careful in wasm2js mode, where we // can't do instanceof here (the wasm polyfill there doesn't have such - // things). But in wasm2js any non-function is a global, so things are - // simple there. - if ((wasm2js && typeof value !== 'function') || - (!wasm2js && (value instanceof WebAssembly.Global))) { + // things). In wasm2js we strip global exports to avoid needing to handle + // them here (using stub-unsupported-js). + if (!wasm2js && (value instanceof WebAssembly.Global)) { // We can log a global value and do other operations to check for bugs. // First, do some operations on the Global wrapper itself. JSON.stringify(value); diff --git a/src/passes/RemoveNonJSOps.cpp b/src/passes/RemoveNonJSOps.cpp index 90116f8f3c3..8af0d1070ee 100644 --- a/src/passes/RemoveNonJSOps.cpp +++ b/src/passes/RemoveNonJSOps.cpp @@ -337,11 +337,6 @@ struct RemoveNonJSOpsPass : public WalkerPass> { struct StubUnsupportedJSOpsPass : public WalkerPass> { - bool isFunctionParallel() override { return true; } - - std::unique_ptr create() override { - return std::make_unique(); - } void visitUnary(Unary* curr) { switch (curr->op) { @@ -386,6 +381,21 @@ struct StubUnsupportedJSOpsPass } replaceCurrent(replacement); } + + void visitModule(Module* module) { + // We remove global exports, as wasm2js doesn't emit them in a fully + // compatible form yet (they aren't instances of WebAssembly.Global). + // Globals. + std::vector badExports; + for (auto& exp : module->exports) { + if (exp->kind == ExternalKind::Global) { + badExports.push_back(exp->name); + } + } + for (auto name : badExports) { + module->removeExport(name); + } + } }; Pass* createRemoveNonJSOpsPass() { return new RemoveNonJSOpsPass(); } diff --git a/test/lit/passes/stub-unsupported-js-globals.wast b/test/lit/passes/stub-unsupported-js-globals.wast new file mode 100644 index 00000000000..8953531c7dc --- /dev/null +++ b/test/lit/passes/stub-unsupported-js-globals.wast @@ -0,0 +1,32 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --stub-unsupported-js -S -o - | filecheck %s + +;; We should remove global exports, as wasm2js doesn't emit them in a fully +;; compatible form yet (they aren't instances of WebAssembly.Global). All the +;; global exports below should vanish, but not the function export. + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (global $i32 i32 (i32.const 42)) + (global $i32 i32 (i32.const 42)) + + ;; CHECK: (global $v128 v128 (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)) + (global $v128 v128 (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)) + + ;; CHECK: (export "bad1" (global $v128)) + (export "bad1" (global $v128)) + + ;; CHECK: (export "bad2" (global $i32)) + (export "bad2" (global $i32)) + + ;; CHECK: (export "good" (func $func)) + (export "good" (func $func)) + + ;; CHECK: (func $func (type $0) + ;; CHECK-NEXT: ) + (func $func + ) +) + From 19843592f0742ddc02bcff9c55689528f670eae9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 6 Mar 2026 11:30:23 -0800 Subject: [PATCH 18/34] go --- test/lit/passes/stub-unsupported-js-globals.wast | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/lit/passes/stub-unsupported-js-globals.wast b/test/lit/passes/stub-unsupported-js-globals.wast index 8953531c7dc..a2ac2b8e578 100644 --- a/test/lit/passes/stub-unsupported-js-globals.wast +++ b/test/lit/passes/stub-unsupported-js-globals.wast @@ -15,10 +15,8 @@ ;; CHECK: (global $v128 v128 (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)) (global $v128 v128 (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)) - ;; CHECK: (export "bad1" (global $v128)) (export "bad1" (global $v128)) - ;; CHECK: (export "bad2" (global $i32)) (export "bad2" (global $i32)) ;; CHECK: (export "good" (func $func)) From ea1777ab4c5c888b1de073fc8b3ef03830ad556d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 9 Mar 2026 14:21:23 -0700 Subject: [PATCH 19/34] go --- scripts/fuzz_shell.js | 51 ++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index c07d6deb80a..e656f5ba62e 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -571,29 +571,34 @@ function build(binary, isSecond) { // First, do some operations on the Global wrapper itself. JSON.stringify(value); value.foobar; - // Look at the exported value itself, not the global wrapper. - let actualValue; - try { - actualValue = value.value; - } catch (e) { - if (e.message.startsWith('get WebAssembly.Global.value')) { - // Just log a null instead of a value we cannot access from JS, - // like an exnref. - actualValue = null; - } else { - throw e; - } - } - // Log the actual value, by building a lambda to be called along the - // functions. - value = () => { - if (typeof actualValue === 'object') { - // logRef can do a little more than logValue, so use it when possible. - logRef(actualValue); - } else { - logValue(actualValue); - } - }; + + // Log it at the right time later using a lambda. Note that we can't just + // capture |value| for the lambda, as the loop modifies it. + (() => { + var global = value; + value = () => { + // Time to log. Look at the exported value itself, not the global + // wrapper. + let actualValue; + try { + actualValue = global.value; + } catch (e) { + if (e.message.startsWith('get WebAssembly.Global.value')) { + // Just log a null instead of a value we cannot access from JS, + // like an exnref. + actualValue = null; + } else { + throw e; + } + } + if (typeof actualValue === 'object') { + // logRef can do a little more than logValue, so use it when possible. + logRef(actualValue); + } else { + logValue(actualValue); + } + }; + })(); } if (typeof value !== 'function') { From dc394e2561993ccc08bce7a02dbc023be96f815e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Mar 2026 10:40:03 -0700 Subject: [PATCH 20/34] typo --- src/passes/RemoveNonJSOps.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/passes/RemoveNonJSOps.cpp b/src/passes/RemoveNonJSOps.cpp index 8af0d1070ee..e5a87f5e3a9 100644 --- a/src/passes/RemoveNonJSOps.cpp +++ b/src/passes/RemoveNonJSOps.cpp @@ -385,7 +385,6 @@ struct StubUnsupportedJSOpsPass void visitModule(Module* module) { // We remove global exports, as wasm2js doesn't emit them in a fully // compatible form yet (they aren't instances of WebAssembly.Global). - // Globals. std::vector badExports; for (auto& exp : module->exports) { if (exp->kind == ExternalKind::Global) { From 0a6dcb5b7800aca7802e3bde3d642e0ea0680bb3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Mar 2026 10:40:52 -0700 Subject: [PATCH 21/34] undo --- scripts/fuzz_opt.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 451feaa43f4..a24c7b158b9 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -426,9 +426,6 @@ def pick_initial_contents(): # --fuzz-exec reports calls as [fuzz-exec] calling foo FUZZ_EXEC_CALL_PREFIX = '[fuzz-exec] calling' -# --fuzz-exec logs globals etc. as [fuzz-exec] logging foo -FUZZ_EXEC_LOG_PREFIX = '[fuzz-exec] logging' - # --fuzz-exec reports a stack limit using this notation STACK_LIMIT = '[trap stack limit]' @@ -1968,7 +1965,6 @@ def compare_to_merged_output(self, output, merged_output): # Comparing the original output from two files to the output after # merging them is not trivial. First, remove the extra logging that # --fuzz-exec-second adds. - output = output.replace('[fuzz-exec] logging second module\n', '') output = output.replace('[fuzz-exec] running second module\n', '') # Fix up both outputs. @@ -1999,10 +1995,6 @@ def compare_to_merged_output(self, output, merged_output): # for different foo/bar. Just copy the original. assert b.startswith(FUZZ_EXEC_CALL_PREFIX) merged_output_lines[i] = output_lines[i] - elif a.startswith(FUZZ_EXEC_LOG_PREFIX): - # As above, but for logging. - assert b.startswith(FUZZ_EXEC_LOG_PREFIX) - merged_output_lines[i] = output_lines[i] elif a.startswith(FUZZ_EXEC_NOTE_RESULT): # Fix up # [fuzz-exec] note result: foo/bar => 42 @@ -2288,12 +2280,12 @@ def handle(self, wasm): CheckDeterminism(), Wasm2JS(), TrapsNeverHappen(), - #CtorEval(), - #Merge(), - #Split(), + CtorEval(), + Merge(), + Split(), RoundtripText(), - #ClusterFuzz(), - #Two(), + ClusterFuzz(), + Two(), PreserveImportsExports(), BranchHintPreservation(), ] From 6b460de5a516a4b91200a24e4826a689fb15d37e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Mar 2026 10:43:00 -0700 Subject: [PATCH 22/34] simpl --- src/tools/execution-results.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 6cd78a338db..0fd3d6190c7 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -488,7 +488,8 @@ struct ExecutionResults { } } } else if (exp->kind == ExternalKind::Global) { - // Log the global's value. + // Log the global's value. (We use "calling" here to match the output + // for calls, which simplifies the fuzzer.) std::cout << "[fuzz-exec] calling " << exp->name << "\n"; Literals* value = instance.getExportedGlobalOrNull(exp->name); assert(value); From 4f56b372846328e34227177e75cf2682ec57b1fb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Mar 2026 10:52:11 -0700 Subject: [PATCH 23/34] go --- 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 a24c7b158b9..3c4ad458627 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1871,7 +1871,8 @@ def handle(self, wasm): # Make sure that we actually executed all exports from both # wasm files. - exports = get_exports(wasm, ['func']) + get_exports(second_wasm, ['func']) + exports = get_exports(wasm, ['func', 'global']) + exports += get_exports(second_wasm, ['func', 'global']) calls_in_output = output.count(FUZZ_EXEC_CALL_PREFIX) if calls_in_output == 0: print(f'warning: no calls in output. output:\n{output}') From d09d5d7bbf6c3820e7748c03474dd55ede2b9ea3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Mar 2026 14:19:04 -0700 Subject: [PATCH 24/34] go --- scripts/fuzz_opt.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 3c4ad458627..b2dc7b4eb78 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1370,6 +1370,15 @@ class CtorEval(TestCaseHandler): frequency = 0.1 def handle(self, wasm): + if get_exports(second_wasm, ['global']: + # The fuzzer reads exports in the order they are given, so if there + # are global exports it may read them before the ctors are run - but + # the ctors are meant to run before anything else, and can modify + # those global values. Rather than reorder how the fuzzer handles + # exports, ignore this case in this less-important fuzzer mode. + note_ignored_vm_run('ctor-eval with global exports') + return + # get the expected execution results. wasm_exec = run_bynterp(wasm, ['--fuzz-exec-before']) From d9fc4ba288147f0c3708e0de2f0252cc98627a5c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Mar 2026 14:19:41 -0700 Subject: [PATCH 25/34] 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 b2dc7b4eb78..638f6cafa7e 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1370,7 +1370,7 @@ class CtorEval(TestCaseHandler): frequency = 0.1 def handle(self, wasm): - if get_exports(second_wasm, ['global']: + if get_exports(second_wasm, ['global']): # The fuzzer reads exports in the order they are given, so if there # are global exports it may read them before the ctors are run - but # the ctors are meant to run before anything else, and can modify From c4b482f784527299fd8d1862d94a13e41508e28a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 10 Mar 2026 14:20:05 -0700 Subject: [PATCH 26/34] 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 638f6cafa7e..12c0461d413 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1370,7 +1370,7 @@ class CtorEval(TestCaseHandler): frequency = 0.1 def handle(self, wasm): - if get_exports(second_wasm, ['global']): + if get_exports(wasm, ['global']): # The fuzzer reads exports in the order they are given, so if there # are global exports it may read them before the ctors are run - but # the ctors are meant to run before anything else, and can modify From b5726968a0da64c5fed91f1c039e92c23209dcc1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 13 Mar 2026 16:00:04 -0700 Subject: [PATCH 27/34] work --- scripts/fuzz_opt.py | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 12c0461d413..5a634c8cecf 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1367,26 +1367,27 @@ def can_run_on_wasm(self, wasm): # Tests wasm-ctor-eval class CtorEval(TestCaseHandler): - frequency = 0.1 + frequency = 1 def handle(self, wasm): - if get_exports(wasm, ['global']): - # The fuzzer reads exports in the order they are given, so if there - # are global exports it may read them before the ctors are run - but - # the ctors are meant to run before anything else, and can modify - # those global values. Rather than reorder how the fuzzer handles - # exports, ignore this case in this less-important fuzzer mode. - note_ignored_vm_run('ctor-eval with global exports') - return - # get the expected execution results. wasm_exec = run_bynterp(wasm, ['--fuzz-exec-before']) # get the list of func exports, so we can tell ctor-eval what to eval. - ctors = ','.join(get_exports(wasm, ['func'])) + func_exports = get_exports(wasm, ['func']) + ctors = ','.join(func_exports) if not ctors: return + # The fuzzer evaluates exports in the order they are given, so if there + # are global exports it may read them before the ctors are run - but + # the ctors are meant to run before anything else, and can modify + # those global values. Keep only function exports to avoid this + # confusion. + filtered = wasm + '.filtered.wasm' + filter_exports(wasm, filtered, func_exports) + wasm = filtered + # Fix escaping of the names, as we will be passing them as commandline # parameters below (e.g. we want --ctors=foo\28bar and not # --ctors=foo\\28bar; that extra escaping \ would cause an error). @@ -2285,19 +2286,8 @@ def handle(self, wasm): # The global list of all test case handlers testcase_handlers = [ - FuzzExec(), - CompareVMs(), - CheckDeterminism(), - Wasm2JS(), - TrapsNeverHappen(), CtorEval(), - Merge(), - Split(), - RoundtripText(), - ClusterFuzz(), - Two(), - PreserveImportsExports(), - BranchHintPreservation(), + ] From 2d4b71872c8e8b07f5ead7a4929c57b02febedd0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 13 Mar 2026 16:01:00 -0700 Subject: [PATCH 28/34] go --- scripts/fuzz_opt.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 61f5640e025..9d0f852234a 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1367,7 +1367,7 @@ def can_run_on_wasm(self, wasm): # Tests wasm-ctor-eval class CtorEval(TestCaseHandler): - frequency = 1 + frequency = 0.1 def handle(self, wasm): # get the expected execution results. @@ -2286,8 +2286,19 @@ def handle(self, wasm): # The global list of all test case handlers testcase_handlers = [ + FuzzExec(), + CompareVMs(), + CheckDeterminism(), + Wasm2JS(), + TrapsNeverHappen(), CtorEval(), - + Merge(), + Split(), + RoundtripText(), + ClusterFuzz(), + Two(), + PreserveImportsExports(), + BranchHintPreservation(), ] From cc5b2ff5b1f772c7a0efe4dc448f294f518551a8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 13 Mar 2026 16:12:15 -0700 Subject: [PATCH 29/34] go --- scripts/fuzz_opt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 9d0f852234a..5b02d4ca9ae 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1370,9 +1370,6 @@ class CtorEval(TestCaseHandler): frequency = 0.1 def handle(self, wasm): - # get the expected execution results. - wasm_exec = run_bynterp(wasm, ['--fuzz-exec-before']) - # get the list of func exports, so we can tell ctor-eval what to eval. func_exports = get_exports(wasm, ['func']) ctors = ','.join(func_exports) @@ -1388,6 +1385,9 @@ def handle(self, wasm): filter_exports(wasm, filtered, func_exports) wasm = filtered + # get the expected execution results. + wasm_exec = run_bynterp(wasm, ['--fuzz-exec-before']) + # Fix escaping of the names, as we will be passing them as commandline # parameters below (e.g. we want --ctors=foo\28bar and not # --ctors=foo\\28bar; that extra escaping \ would cause an error). From fb41a411479b82a1d6460d364aaa66da1f695dac Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 16 Mar 2026 16:31:15 -0700 Subject: [PATCH 30/34] undo --- test/lit/exec/fuzzing-api.wast | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/test/lit/exec/fuzzing-api.wast b/test/lit/exec/fuzzing-api.wast index d6315d79153..ee394ec5bfb 100644 --- a/test/lit/exec/fuzzing-api.wast +++ b/test/lit/exec/fuzzing-api.wast @@ -5,8 +5,6 @@ ;; Test the fuzzing-support module imports. (module - (type $struct (struct)) - (import "fuzzing-support" "log-i32" (func $log-i32 (param i32))) (import "fuzzing-support" "log-f64" (func $log-f64 (param f64))) (import "fuzzing-support" "log-anyref" (func $log-anyref (param anyref))) @@ -33,17 +31,10 @@ (table $table 10 20 funcref) - (global $global (mut i32) (i32.const 42)) - - (global $global-immref anyref (struct.new $struct)) - ;; Note that the exported table appears first here, but in the binary and in ;; the IR it is actually last, as we always add function exports first. (export "table" (table $table)) - (export "global" (global $global)) - (export "global-immref" (global $global-immref)) - ;; CHECK: [fuzz-exec] calling logging ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [LoggingExternalInterface logging 3.14159] @@ -491,10 +482,6 @@ ;; CHECK: [fuzz-exec] calling return-externref-exception ;; CHECK-NEXT: [fuzz-exec] note result: return-externref-exception => object - ;; CHECK-NEXT: [fuzz-exec] logging global - ;; CHECK-NEXT: [LoggingExternalInterface logging 42] - ;; CHECK-NEXT: [fuzz-exec] logging global-immref - ;; CHECK-NEXT: [LoggingExternalInterface logging object] ;; CHECK-NEXT: warning: no passes specified, not doing any work (func $return-externref-exception (export "return-externref-exception") (result externref) ;; Call JS table.set in a way that throws (on out of bounds). The JS exception @@ -626,10 +613,6 @@ ;; CHECK: [fuzz-exec] calling return-externref-exception ;; CHECK-NEXT: [fuzz-exec] note result: return-externref-exception => object -;; CHECK-NEXT: [fuzz-exec] logging global -;; CHECK-NEXT: [LoggingExternalInterface logging 42] -;; CHECK-NEXT: [fuzz-exec] logging global-immref -;; CHECK-NEXT: [LoggingExternalInterface logging object] ;; CHECK-NEXT: [fuzz-exec] comparing catch-js-tag ;; CHECK-NEXT: [fuzz-exec] comparing do-sleep ;; CHECK-NEXT: [fuzz-exec] comparing export.calling From ddeef0484c432780c4712e15016b413e637ec2d8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 16 Mar 2026 16:33:48 -0700 Subject: [PATCH 31/34] test --- test/lit/exec/fuzzing-api-globals.wast | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test/lit/exec/fuzzing-api-globals.wast diff --git a/test/lit/exec/fuzzing-api-globals.wast b/test/lit/exec/fuzzing-api-globals.wast new file mode 100644 index 00000000000..b08224c540b --- /dev/null +++ b/test/lit/exec/fuzzing-api-globals.wast @@ -0,0 +1,21 @@ +;; Test logging of global exports. We don't do this using update_lit_checks as +;; global exports confuse the auto-updater. + +(module + (type $struct (struct)) + + (global $global (mut i32) (i32.const 42)) + + (global $global-immref anyref (struct.new $struct)) + + (export "global" (global $global)) + (export "global-immref" (global $global-immref)) +) + +;; RUN: wasm-opt %s -all --fuzz-exec -o /dev/null 2>&1 | filecheck %s + +;; CHECK: [fuzz-exec] calling global +;; CHECK-NEXT: [LoggingExternalInterface logging 42] +;; CHECK-NEXT: [fuzz-exec] calling global-immref +;; CHECK-NEXT: [LoggingExternalInterface logging object] + From f89366f9cdfb627c9b3a50a922440cdef34ea2d6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 17 Mar 2026 11:03:53 -0700 Subject: [PATCH 32/34] test --- test/lit/exec/fuzzing-api-globals.wast | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/lit/exec/fuzzing-api-globals.wast b/test/lit/exec/fuzzing-api-globals.wast index b08224c540b..6f5e1f5d446 100644 --- a/test/lit/exec/fuzzing-api-globals.wast +++ b/test/lit/exec/fuzzing-api-globals.wast @@ -5,11 +5,12 @@ (type $struct (struct)) (global $global (mut i32) (i32.const 42)) - (global $global-immref anyref (struct.new $struct)) + (global $global-v128 v128 (v128.const i64x2 12 34)) (export "global" (global $global)) (export "global-immref" (global $global-immref)) + (export "global-v128" (global $global-v128)) ) ;; RUN: wasm-opt %s -all --fuzz-exec -o /dev/null 2>&1 | filecheck %s @@ -18,4 +19,6 @@ ;; CHECK-NEXT: [LoggingExternalInterface logging 42] ;; CHECK-NEXT: [fuzz-exec] calling global-immref ;; CHECK-NEXT: [LoggingExternalInterface logging object] +;; CHECK-NEXT: [fuzz-exec] calling global-v128 +;; CHECK-NEXT: [LoggingExternalInterface logging i32x4 0x0000000c 0x00000000 0x00000022 0x00000000] From 84c6219aa940d72abca27267dbe65d9d3b40d845 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 17 Mar 2026 11:05:20 -0700 Subject: [PATCH 33/34] log.str --- scripts/fuzz_shell.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index e656f5ba62e..f43d2866101 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -584,9 +584,9 @@ function build(binary, isSecond) { actualValue = global.value; } catch (e) { if (e.message.startsWith('get WebAssembly.Global.value')) { - // Just log a null instead of a value we cannot access from JS, + // Just log a string instead of a value we cannot access from JS, // like an exnref. - actualValue = null; + actualValue = ''; } else { throw e; } From da419411a7605bff84d6b3e4f3b786ba80c87641 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 17 Mar 2026 11:12:06 -0700 Subject: [PATCH 34/34] clarify --- scripts/fuzz_shell.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/fuzz_shell.js b/scripts/fuzz_shell.js index f43d2866101..ada5768b54f 100644 --- a/scripts/fuzz_shell.js +++ b/scripts/fuzz_shell.js @@ -585,7 +585,9 @@ function build(binary, isSecond) { } catch (e) { if (e.message.startsWith('get WebAssembly.Global.value')) { // Just log a string instead of a value we cannot access from JS, - // like an exnref. + // like an exnref. Note we don't need matching code on the C++ + // side in execution-results.h because illegal exports are pruned + // anyhow if we are going to compare execution in JS to C++. actualValue = ''; } else { throw e;