From d20a4370ccef658fd9deee6b6f3bbed7bf187625 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 11:12:49 -0700 Subject: [PATCH 01/69] Add final ss tests --- test/lit/ctor-eval/cont.wast | 55 ++++++++++++++ test/spec/cont.wast | 140 +++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 test/lit/ctor-eval/cont.wast diff --git a/test/lit/ctor-eval/cont.wast b/test/lit/ctor-eval/cont.wast new file mode 100644 index 00000000000..c8bb0dccc84 --- /dev/null +++ b/test/lit/ctor-eval/cont.wast @@ -0,0 +1,55 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-ctor-eval --ctors=test --kept-exports=test --quiet -all -S -o - | filecheck %s + +;; Test that we can precompute continuations. + +(module + (type $f (func)) + (type $k (cont $f)) + + (tag $more) + + (global $g (mut i32) (i32.const 0)) + + (func $run (param $k (ref $k)) + ;; Run a coroutine, continuing to resume it until it is complete. + ;; start + (loop $loop + (block $on (result (ref $k)) + (resume $k (on $more $on) + (local.get $k) + ) + ;; stop + (return) + ) + ;; continue + (local.set $k) + (br $loop) + ) + (unreachable) + ) + + (func $cont + ;; increment the global three times, around suspends. + (global.set $g (i32.add (global.get $g) (i32.const 1))) + (suspend $more) + (global.set $g (i32.add (global.get $g) (i32.const 1))) + (suspend $more) + (global.set $g (i32.add (global.get $g) (i32.const 1))) + ) + + (func $test (export "test") (result i32) + ;; All of this can be computed, leaving a return of 3. + (call $run + (cont.new $k (ref.func $cont)) + ) + (global.get $g) + ) +) +;; CHECK: (type $0 (func (result i32))) + +;; CHECK: (export "test" (func $test_3)) + +;; CHECK: (func $test_3 (type $0) (result i32) +;; CHECK-NEXT: (i32.const 3) +;; CHECK-NEXT: ) diff --git a/test/spec/cont.wast b/test/spec/cont.wast index 299f3cb9e41..2c344cfef91 100644 --- a/test/spec/cont.wast +++ b/test/spec/cont.wast @@ -726,3 +726,143 @@ ) (assert_return (invoke "main") (i32.const 10)) +;; Syntax: check unfolded forms +(module + (type $ft (func)) + (type $ct (cont $ft)) + (rec + (type $ft2 (func (param (ref null $ct2)))) + (type $ct2 (cont $ft2))) + + (tag $yield (param i32)) + (tag $swap) + + ;; Check cont.new + (func (result (ref $ct)) + ref.null $ft + block (param (ref null $ft)) (result (ref $ct)) + cont.new $ct + end + ) + ;; Check cont.bind + (func (param (ref $ct)) (result (ref $ct)) + local.get 0 + block (param (ref $ct)) (result (ref $ct)) + cont.bind $ct $ct + end + ) + ;; Check suspend + (func + block + suspend $swap + end + ) + ;; Check resume + (func (param $k (ref $ct)) (result i32) + (local.get $k) + block $on_yield (param (ref $ct)) (result i32 (ref $ct)) + resume $ct (on $yield $on_yield) + i32.const 42 + return + end + local.set $k + ) + ;; Check resume_throw + (func (param $k (ref $ct)) (result i32) + block $on_yield (result i32 (ref $ct)) + i32.const 42 + local.get $k + resume_throw $ct $yield + i32.const 42 + return + end + local.set $k + ) + ;; Check switch + (func (param $k (ref $ct2)) + local.get $k + block (param (ref $ct2)) (result (ref null $ct2)) + switch $ct2 $swap + end + drop + ) +) + +;; Syntax: check instructions in tail position in unfolded form +(module + (type $ft (func)) + (type $ct (cont $ft)) + (rec + (type $ft2 (func (param (ref null $ct2)))) + (type $ct2 (cont $ft2))) + + (tag $yield (param i32)) + (tag $swap) + + ;; Check cont.new + (func (result (ref $ct)) + ref.null $ft + cont.new $ct + ) + ;; Check cont.bind + (func (param (ref $ct)) (result (ref $ct)) + local.get 0 + cont.bind $ct $ct + ) + + ;; Check resume + (func (;2;) (param $k (ref $ct)) + local.get $k + resume $ct + ) + ;; Check resume_throw + (func (param $k (ref $ct)) + i32.const 42 + local.get $k + resume_throw $ct $yield + ) + ;; Check switch + (func (param $k (ref $ct2)) (result (ref null $ct2)) + local.get $k + switch $ct2 $swap + ) + ;; Check suspend + (func + suspend $swap + ) +) + +(module + (type $ft0 (func)) + (type $ct0 (cont $ft0)) + + (type $ft1 (func (param (ref $ct0)))) + (type $ct1 (cont $ft1)) + + (tag $t) + + (func $f + (cont.new $ct1 (ref.func $g)) + (switch $ct1 $t) + ) + (elem declare func $f) + + (func $g (param (ref $ct0))) + (elem declare func $g) + + (func $entry + (cont.new $ct0 (ref.func $f)) + (resume $ct0 (on $t switch)) + ) +) + +(assert_invalid + (module + (rec + (type $ft (func (param (ref $ct)))) + (type $ct (cont $ft))) + (tag $t (param i32)) + + (func (param $k (ref $ct)) + (switch $ct $t))) + "type mismatch in switch tag") From 8f1bed104a9b3275319678053eb22b56b12f223e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 12:52:13 -0700 Subject: [PATCH 02/69] go --- scripts/test/fuzzing.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index e9c790d3c03..8c0294617f2 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -70,12 +70,6 @@ # the fuzzer does not support imported memories 'multi-memory-lowering-import.wast', 'multi-memory-lowering-import-error.wast', - # the fuzzer does not support typed continuations - 'typed_continuations.wast', - 'typed_continuations_resume.wast', - 'typed_continuations_contnew.wast', - 'typed_continuations_contbind.wast', - 'typed_continuations_suspend.wast', # the fuzzer does not support struct RMW ops 'gc-atomics.wast', 'gc-atomics-null-refs.wast', @@ -96,24 +90,6 @@ # it removes unknown imports 'string-lifting.wast', 'string-lifting-custom-module.wast', - # TODO: fuzzer support for stack switching - 'tag_linked.wast', - 'stack_switching.wast', - 'stack_switching_contnew.wast', - 'stack_switching_contbind.wast', - 'stack_switching_suspend.wast', - 'stack_switching_resume.wast', - 'stack_switching_resume_throw.wast', - 'stack_switching_switch.wast', - 'stack_switching_switch_2.wast', - 'O3_stack-switching.wast', - 'coalesce-locals-stack-switching.wast', - 'dce-stack-switching.wast', - 'precompute-stack-switching.wast', - 'unsubtyping-stack-switching.wast', - 'vacuum-stack-switching.wast', - 'cont.wast', - 'cont_simple.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', From f988d9fc9fae734d30d35d2a2fb193a6250a1b65 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 12:59:13 -0700 Subject: [PATCH 03/69] fix --- src/tools/fuzzing/fuzzing.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 9722faec4b5..b209256a980 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -491,6 +491,7 @@ void TranslateToFuzzReader::setupHeapTypes() { auto eq = HeapTypes::eq.getBasic(share); auto any = HeapTypes::any.getBasic(share); auto func = HeapTypes::func.getBasic(share); + auto cont = HeapTypes::cont.getBasic(share); switch (type.getKind()) { case HeapTypeKind::Func: interestingHeapSubTypes[func].push_back(type); @@ -517,7 +518,8 @@ void TranslateToFuzzReader::setupHeapTypes() { } break; case HeapTypeKind::Cont: - WASM_UNREACHABLE("TODO: cont"); + interestingHeapSubTypes[cont].push_back(type); + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } From a94d31afaa16a2056783abcd35c7cdbb20b5b0ed Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 13:08:36 -0700 Subject: [PATCH 04/69] fix --- src/tools/fuzzing/fuzzing.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index b209256a980..461c4f6f6ae 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3671,8 +3671,10 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) { builder.makeConst(int32_t(upTo(fuzzParams->MAX_ARRAY_SIZE))); return builder.makeArrayNew(type.getHeapType(), count, init); } - case HeapTypeKind::Cont: - WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Cont: { + auto funcType = Type(heapType.getContinuation().type, NonNullable); + return builder.makeContNew(heapType, makeRefFuncConst(funcType)); + } case HeapTypeKind::Basic: break; } From 3f2968834789b638a018910f538bd0a363ca5cc3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 13:10:55 -0700 Subject: [PATCH 05/69] fix --- scripts/test/shared.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test/shared.py b/scripts/test/shared.py index a5702ddb4b0..49ffcfc98f9 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -258,6 +258,7 @@ def has_shell_timeout(): '--experimental-wasm-stringref', '--experimental-wasm-fp16', '--experimental-wasm-custom-descriptors', + '--experimental-wasm-wasmfx', ] # external tools From 058d05ecbf868193a707bd571e6b8687168066a1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 14:10:26 -0700 Subject: [PATCH 06/69] fuzz --- 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 db3572dacd6..352ac20c189 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -152,11 +152,13 @@ def randomize_feature_opts(): # The shared-everything feature is new and we want to fuzz it, but it # also currently disables fuzzing V8, so disable it most of the time. # Same with strings. Relaxed SIMD's nondeterminism disables much but not - # all of our V8 fuzzing, so avoid it too. + # all of our V8 fuzzing, so avoid it too. Stack Switching, as well, is + # not yet ready in V8. if random.random() < 0.9: FEATURE_OPTS.append('--disable-shared-everything') FEATURE_OPTS.append('--disable-strings') FEATURE_OPTS.append('--disable-relaxed-simd') + FEATURE_OPTS.append('--disable-stack-switching') print('randomized feature opts:', '\n ' + '\n '.join(FEATURE_OPTS)) @@ -824,8 +826,9 @@ def run(self, wasm, extra_d8_flags=[]): def can_run(self, wasm): # V8 does not support shared memories when running with # shared-everything enabled, so do not fuzz shared-everything - # for now. It also does not yet support strings. - return all_disallowed(['shared-everything', 'strings']) + # for now. It also does not yet support strings, nor stack + # switching + return all_disallowed(['shared-everything', 'strings', 'stack-switching']) def can_compare_to_self(self): # With nans, VM differences can confuse us, so only very simple VMs From 99443600117e4470aee1ae9a5bc8e361b0c42387 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 14:11:06 -0700 Subject: [PATCH 07/69] fuzz --- 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 352ac20c189..fe746c2f933 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -1627,7 +1627,7 @@ def can_run_on_wasm(self, wasm): return False # see D8.can_run - return all_disallowed(['shared-everything', 'strings']) + return all_disallowed(['shared-everything', 'strings', 'stack-switching']) # Check that the text format round-trips without error. @@ -1835,7 +1835,7 @@ def can_run_on_wasm(self, wasm): return False if NANS: return False - return all_disallowed(['shared-everything', 'strings']) + return all_disallowed(['shared-everything', 'strings', 'stack-switching']) # Test --fuzz-preserve-imports-exports, which never modifies imports or exports. From 3a47f996681eca7e93cf2bf308e918aa942ce15a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 15:07:16 -0700 Subject: [PATCH 08/69] fix --- scripts/bundle_clusterfuzz.py | 1 + scripts/clusterfuzz/run.py | 1 + 2 files changed, 2 insertions(+) diff --git a/scripts/bundle_clusterfuzz.py b/scripts/bundle_clusterfuzz.py index ec39fc96a68..2c4f2932292 100755 --- a/scripts/bundle_clusterfuzz.py +++ b/scripts/bundle_clusterfuzz.py @@ -108,6 +108,7 @@ '--disable-shared-everything', '--disable-fp16', '--disable-strings', + '--disable-stack-switching', ] with tarfile.open(output_file, "w:gz") as tar: diff --git a/scripts/clusterfuzz/run.py b/scripts/clusterfuzz/run.py index e4dbf58cfe7..70b6bf89776 100755 --- a/scripts/clusterfuzz/run.py +++ b/scripts/clusterfuzz/run.py @@ -93,6 +93,7 @@ '--disable-shared-everything', '--disable-fp16', '--disable-strings', + '--disable-stack-switching', ] From c76f0a7949165f58544b9e9582faaa729f87295b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 15:44:24 -0700 Subject: [PATCH 09/69] fix --- src/tools/fuzzing/fuzzing.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 461c4f6f6ae..15dcd843b1b 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -674,6 +674,12 @@ void TranslateToFuzzReader::setupGlobals() { // Create new random globals. for (size_t index = upTo(fuzzParams->MAX_GLOBALS); index > 0; --index) { auto type = getConcreteType(); + if (type.isContinuation()) { + // There is no way to make a continuation in a global. + ++index; + continue; + } + // Prefer immutable ones as they can be used in global.gets in other // globals, for more interesting patterns. auto mutability = oneIn(3) ? Builder::Mutable : Builder::Immutable; From 7f3630b862ba5586db045929f9d04cb4a1922a4f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 16:28:41 -0700 Subject: [PATCH 10/69] work --- src/tools/fuzzing.h | 3 +++ src/tools/fuzzing/fuzzing.cpp | 21 +++++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 20b1e39fddd..1d42c11aeaf 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -211,6 +211,9 @@ class TranslateToFuzzReader { // All arrays that are mutable. std::vector mutableArrays; + // All tags that are valid as exception tags (which cannot have results). + std::vector exceptionTags; + Index numAddedFunctions = 0; // The name of an empty tag. diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 15dcd843b1b..3cb9d623ef4 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -675,7 +675,8 @@ void TranslateToFuzzReader::setupGlobals() { for (size_t index = upTo(fuzzParams->MAX_GLOBALS); index > 0; --index) { auto type = getConcreteType(); if (type.isContinuation()) { - // There is no way to make a continuation in a global. + // There is no way to make a continuation in a global. TODO: We could + // allow null ones, at least, that are always set to null. ++index; continue; } @@ -716,6 +717,9 @@ void TranslateToFuzzReader::setupTags() { if (tag->imported() && !preserveImportsAndExports) { tag->module = tag->base = Name(); } + if (tag->results() != Type::none) { + exceptionTags.push_back(tag.get()); + } } // Add some random tags. @@ -744,7 +748,8 @@ void TranslateToFuzzReader::setupTags() { void TranslateToFuzzReader::addTag() { auto tag = builder.makeTag(Names::getValidTagName(wasm, "tag$"), Signature(getControlFlowType(), Type::none)); - wasm.addTag(std::move(tag)); + auto* tagg = wasm.addTag(std::move(tag)); + exceptionTags.push_back(tagg); } void TranslateToFuzzReader::finalizeMemory() { @@ -2436,10 +2441,10 @@ Expression* TranslateToFuzzReader::makeTry(Type type) { auto numTags = upTo(fuzzParams->MAX_TRY_CATCHES); std::unordered_set usedTags; for (Index i = 0; i < numTags; i++) { - if (wasm.tags.empty()) { + if (exceptionTags.empty()) { addTag(); } - auto* tag = pick(wasm.tags).get(); + auto* tag = pick(exceptionTags).get(); if (usedTags.count(tag)) { continue; } @@ -2486,7 +2491,7 @@ Expression* TranslateToFuzzReader::makeTryTable(Type type) { return builder.makeTryTable(body, {}, {}, {}); } - if (wasm.tags.empty()) { + if (exceptionTags.empty()) { addTag(); } @@ -2501,7 +2506,7 @@ Expression* TranslateToFuzzReader::makeTryTable(Type type) { Type tagType; if (i < numCatches) { // Look for a specific tag. - auto& tag = pick(wasm.tags); + auto* tag = pick(exceptionTags); tagName = tag->name; tagType = tag->params(); } else { @@ -5228,10 +5233,10 @@ Expression* TranslateToFuzzReader::makeThrow(Type type) { } } else { // Get a random tag, adding a random one if necessary. - if (wasm.tags.empty()) { + if (exceptionTags.empty()) { addTag(); } - tag = pick(wasm.tags).get(); + tag = pick(exceptionTags); } auto tagType = tag->params(); std::vector operands; From 3e57bcde800698d63dd2680bebcb4540ff008495 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 16:29:25 -0700 Subject: [PATCH 11/69] work --- src/tools/fuzzing/fuzzing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 3cb9d623ef4..d359ccf38c8 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -2444,7 +2444,7 @@ Expression* TranslateToFuzzReader::makeTry(Type type) { if (exceptionTags.empty()) { addTag(); } - auto* tag = pick(exceptionTags).get(); + auto* tag = pick(exceptionTags); if (usedTags.count(tag)) { continue; } From 252fae2d01650cffb2938cc985e9ae9071be0ff0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 16:34:29 -0700 Subject: [PATCH 12/69] fix --- src/tools/fuzzing/fuzzing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index d359ccf38c8..0bbb2cc782a 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -717,7 +717,7 @@ void TranslateToFuzzReader::setupTags() { if (tag->imported() && !preserveImportsAndExports) { tag->module = tag->base = Name(); } - if (tag->results() != Type::none) { + if (tag->results() == Type::none) { exceptionTags.push_back(tag.get()); } } From 8eefbab370788ec673d28a51d5ec0b8ea2775602 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 Aug 2025 16:42:10 -0700 Subject: [PATCH 13/69] work --- scripts/test/shared.py | 1 - src/tools/fuzzing/fuzzing.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 49ffcfc98f9..a5702ddb4b0 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -258,7 +258,6 @@ def has_shell_timeout(): '--experimental-wasm-stringref', '--experimental-wasm-fp16', '--experimental-wasm-custom-descriptors', - '--experimental-wasm-wasmfx', ] # external tools diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 0bbb2cc782a..c646657e166 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -677,7 +677,6 @@ void TranslateToFuzzReader::setupGlobals() { if (type.isContinuation()) { // There is no way to make a continuation in a global. TODO: We could // allow null ones, at least, that are always set to null. - ++index; continue; } From 943aedce082ce77545ff7cbce8d2f217d57f382c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 10:17:12 -0700 Subject: [PATCH 14/69] start --- src/ir/possible-contents.cpp | 56 +++++++++++++++++++++++++----------- src/ir/possible-contents.h | 31 ++++++-------------- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 7c1be8d2ed2..e49d0243d25 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -891,11 +891,10 @@ struct InfoCollector handleIndirectCall(curr, curr->target->type); } - // Creates a location for a null of a particular type and adds a root for it. - // Such roots are where the default value of an i32 local comes from, or the - // value in a ref.null. - Location getNullLocation(Type type) { - auto location = NullLocation{type}; + // Creates a location for a root of a particular type, creating a RootLocation + // and marking it as a root. + Location getRootLocation(Type type) { + auto location = RootLocation{type}; addRoot(location, PossibleContents::literal(Literal::makeZero(type))); return location; } @@ -928,7 +927,7 @@ struct InfoCollector auto& fields = type.getStruct().fields; for (Index i = 0; i < fields.size(); i++) { info.links.push_back( - {getNullLocation(fields[i].type), DataLocation{type, i}}); + {getRootLocation(fields[i].type), DataLocation{type, i}}); } } else { // Link the operands to the struct's fields. @@ -948,7 +947,7 @@ struct InfoCollector {ExpressionLocation{curr->init, 0}, DataLocation{type, 0}}); } else { info.links.push_back( - {getNullLocation(type.getArray().element.type), DataLocation{type, 0}}); + {getRootLocation(type.getArray().element.type), DataLocation{type, 0}}); } addRoot(curr, PossibleContents::exactType(curr->type)); } @@ -1220,9 +1219,7 @@ struct InfoCollector } if (curr->catchRefs[tagIndex]) { - auto location = CaughtExnRefLocation{}; - addRoot(location, - PossibleContents::fromType(Type(HeapType::exn, NonNullable))); + auto location = getRootLocation(Type(HeapType::exn, NonNullable)); info.links.push_back( {location, getBreakTargetLocation(target, exnrefIndex)}); } @@ -1283,13 +1280,40 @@ struct InfoCollector // TODO: optimize when possible addRoot(curr); } - void visitResume(Resume* curr) { + + template + void handleResume(T* curr) { // TODO: optimize when possible addRoot(curr); + + // Connect handled tags with their branch targets, and materialize non-null + // exnref values. + auto numTags = curr->handlerTags.size(); + for (Index tagIndex = 0; tagIndex < numTags; tagIndex++) { + auto tag = curr->handlerTags[tagIndex]; + auto target = curr->handlerBlocks[tagIndex]; + auto params = getModule()->getTag(tag)->params(); + + // Add the values from the tag. + for (Index i = 0; i < params.size(); i++) { + if (isRelevant(params[i])) { + info.links.push_back( + {TagLocation{tag, i}, getBreakTargetLocation(target, i)}); + } + } + + // Add the continuation. + auto location = getRootLocation(Type(HeapType::cont, NonNullable)); + info.links.push_back( + {location, getBreakTargetLocation(target, params.size())}); + } + } + + void visitResume(Resume* curr) { + handleResume(curr); } void visitResumeThrow(ResumeThrow* curr) { - // TODO: optimize when possible - addRoot(curr); + handleResume(curr); } void visitStackSwitch(StackSwitch* curr) { // TODO: optimize when possible @@ -1337,7 +1361,7 @@ struct InfoCollector source = ParamLocation{getFunction(), index}; } else { // This is the default value from the function entry, a null. - source = getNullLocation(type[i]); + source = getRootLocation(type[i]); } info.links.push_back({source, ExpressionLocation{get, i}}); } @@ -3062,8 +3086,8 @@ void Flower::dump(Location location) { std::cout << " sigparamloc " << '\n'; } else if (auto* loc = std::get_if(&location)) { std::cout << " sigresultloc " << loc->type << " : " << loc->index << '\n'; - } else if (auto* loc = std::get_if(&location)) { - std::cout << " Nullloc " << loc->type << '\n'; + } else if (auto* loc = std::get_if(&location)) { + std::cout << " rootloc " << loc->type << '\n'; } else { std::cout << " (other)\n"; } diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 8054d9ca325..732da3d1015 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -500,20 +500,13 @@ struct TagLocation { } }; -// The location of an exnref materialized by a catch_ref or catch_all_ref clause -// of a try_table. No data is stored here. exnrefs contain a tag and a payload -// at run-time, as well as potential metadata such as stack traces, but we don't -// track that. So this is the same as NullLocation in a way: we just need *a* -// source of contents for places that receive an exnref. -struct CaughtExnRefLocation { - bool operator==(const CaughtExnRefLocation& other) const { return true; } -}; - -// A null value. This is used as the location of the default value of a var in a -// function, a null written to a struct field in struct.new_with_default, etc. -struct NullLocation { +// A root value. This is used as the location of the default value of a var in a +// function, a null written to a struct field in struct.new_with_default, an +// exnref from a catch etc. - in all these cases, all we know is the type and +// nothing more, so these are simple typed roots in the graph. +struct RootLocation { Type type; - bool operator==(const NullLocation& other) const { + bool operator==(const RootLocation& other) const { return type == other.type; } }; @@ -556,7 +549,7 @@ using Location = std::variant; } // namespace wasm @@ -637,14 +630,8 @@ template<> struct hash { } }; -template<> struct hash { - size_t operator()(const wasm::CaughtExnRefLocation& loc) const { - return std::hash()("caught-exnref-location"); - } -}; - -template<> struct hash { - size_t operator()(const wasm::NullLocation& loc) const { +template<> struct hash { + size_t operator()(const wasm::RootLocation& loc) const { return std::hash{}(loc.type); } }; From 444d8e09a6b713ce0d56591b8ca32993ac7490cb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 10:17:26 -0700 Subject: [PATCH 15/69] undo --- src/ir/possible-contents.cpp | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index e49d0243d25..780954e7bde 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1280,40 +1280,13 @@ struct InfoCollector // TODO: optimize when possible addRoot(curr); } - - template - void handleResume(T* curr) { + void visitResume(Resume* curr) { // TODO: optimize when possible addRoot(curr); - - // Connect handled tags with their branch targets, and materialize non-null - // exnref values. - auto numTags = curr->handlerTags.size(); - for (Index tagIndex = 0; tagIndex < numTags; tagIndex++) { - auto tag = curr->handlerTags[tagIndex]; - auto target = curr->handlerBlocks[tagIndex]; - auto params = getModule()->getTag(tag)->params(); - - // Add the values from the tag. - for (Index i = 0; i < params.size(); i++) { - if (isRelevant(params[i])) { - info.links.push_back( - {TagLocation{tag, i}, getBreakTargetLocation(target, i)}); - } - } - - // Add the continuation. - auto location = getRootLocation(Type(HeapType::cont, NonNullable)); - info.links.push_back( - {location, getBreakTargetLocation(target, params.size())}); - } - } - - void visitResume(Resume* curr) { - handleResume(curr); } void visitResumeThrow(ResumeThrow* curr) { - handleResume(curr); + // TODO: optimize when possible + addRoot(curr); } void visitStackSwitch(StackSwitch* curr) { // TODO: optimize when possible From 922fe8cef072d6592676191f6753796ad05b9183 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 10:18:39 -0700 Subject: [PATCH 16/69] fix --- src/ir/possible-contents.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 732da3d1015..0406f230d88 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -548,7 +548,6 @@ using Location = std::variant; From 33171c1a2c22df594038800cc7f003de4e0a7589 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 10:25:02 -0700 Subject: [PATCH 17/69] Revert "undo" This reverts commit 444d8e09a6b713ce0d56591b8ca32993ac7490cb. --- src/ir/possible-contents.cpp | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 780954e7bde..e49d0243d25 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1280,13 +1280,40 @@ struct InfoCollector // TODO: optimize when possible addRoot(curr); } - void visitResume(Resume* curr) { + + template + void handleResume(T* curr) { // TODO: optimize when possible addRoot(curr); + + // Connect handled tags with their branch targets, and materialize non-null + // exnref values. + auto numTags = curr->handlerTags.size(); + for (Index tagIndex = 0; tagIndex < numTags; tagIndex++) { + auto tag = curr->handlerTags[tagIndex]; + auto target = curr->handlerBlocks[tagIndex]; + auto params = getModule()->getTag(tag)->params(); + + // Add the values from the tag. + for (Index i = 0; i < params.size(); i++) { + if (isRelevant(params[i])) { + info.links.push_back( + {TagLocation{tag, i}, getBreakTargetLocation(target, i)}); + } + } + + // Add the continuation. + auto location = getRootLocation(Type(HeapType::cont, NonNullable)); + info.links.push_back( + {location, getBreakTargetLocation(target, params.size())}); + } + } + + void visitResume(Resume* curr) { + handleResume(curr); } void visitResumeThrow(ResumeThrow* curr) { - // TODO: optimize when possible - addRoot(curr); + handleResume(curr); } void visitStackSwitch(StackSwitch* curr) { // TODO: optimize when possible From fc66e42e53ff92fd6a980f2711c364b5ac002ed8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 10:25:10 -0700 Subject: [PATCH 18/69] format --- src/ir/possible-contents.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index e49d0243d25..3c3ce8b4641 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1281,8 +1281,7 @@ struct InfoCollector addRoot(curr); } - template - void handleResume(T* curr) { + template void handleResume(T* curr) { // TODO: optimize when possible addRoot(curr); @@ -1309,12 +1308,8 @@ struct InfoCollector } } - void visitResume(Resume* curr) { - handleResume(curr); - } - void visitResumeThrow(ResumeThrow* curr) { - handleResume(curr); - } + void visitResume(Resume* curr) { handleResume(curr); } + void visitResumeThrow(ResumeThrow* curr) { handleResume(curr); } void visitStackSwitch(StackSwitch* curr) { // TODO: optimize when possible addRoot(curr); From 8e24d91cca4039811edcba7b0375eac4b955d10c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 10:35:44 -0700 Subject: [PATCH 19/69] work --- scripts/test/fuzzing.py | 1 + src/passes/GUFA.cpp | 2 ++ test/lit/passes/gufa-cont.wast | 28 ++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 test/lit/passes/gufa-cont.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index e9c790d3c03..fce138a6e81 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -114,6 +114,7 @@ 'vacuum-stack-switching.wast', 'cont.wast', 'cont_simple.wast', + 'gufa-cont.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp index 21f9aa8521d..92593f8b85e 100644 --- a/src/passes/GUFA.cpp +++ b/src/passes/GUFA.cpp @@ -138,6 +138,7 @@ struct GUFAOptimizer if (contents.getType() == Type::unreachable) { // This cannot contain any possible value at all. It must be unreachable // code. +std::cout << "no value yo " << *curr << "\n"; replaceCurrent(getDroppedChildrenAndAppend( curr, wasm, options, builder.makeUnreachable())); optimized = true; @@ -177,6 +178,7 @@ struct GUFAOptimizer // The type is not compatible and this is a simple constant expression // like a ref.func. That means this code must be unreachable. (See below // for the case of a non-constant.) +std::cout << "not compatible man\n"; replaceCurrent(getDroppedChildrenAndAppend( curr, wasm, options, builder.makeUnreachable())); optimized = true; diff --git a/test/lit/passes/gufa-cont.wast b/test/lit/passes/gufa-cont.wast new file mode 100644 index 00000000000..b93f0da60e6 --- /dev/null +++ b/test/lit/passes/gufa-cont.wast @@ -0,0 +1,28 @@ +(module + (type $func (func)) + (type $cont (cont $func)) + + (tag $tag (type $func)) + + (export "run_invoker" (func $main)) + + (func $main + ;; A continuation is created, it suspends, and we handle that. There is + ;; nothing to optimize or change here. + (drop + (block $block (result (ref $cont)) + (resume $cont (on $tag $block) + (cont.new $cont + (ref.func $cont) + ) + ) + (return) + ) + ) + ) + + (func $cont + (suspend $tag) + ) +) + From 797e137660d071b0a2ef79e9d076290823174fd6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 10:41:33 -0700 Subject: [PATCH 20/69] fix --- src/ir/possible-contents.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 780954e7bde..d8dc7ebe9a9 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -893,12 +893,23 @@ struct InfoCollector // Creates a location for a root of a particular type, creating a RootLocation // and marking it as a root. - Location getRootLocation(Type type) { + Location getRootLocation(Type type, PossibleContents rootValue) { auto location = RootLocation{type}; - addRoot(location, PossibleContents::literal(Literal::makeZero(type))); + addRoot(location, rootValue); return location; } + // Creates a root location, settings its value by the type. + Location getRootLocation(Type type) { + return getRootLocation(type, PossibleContents::fromType(type)); + } + + // Makes a root location containing a null. + Location getNullLocation(Type type) { + return getRootLocation(type, + PossibleContents::literal(Literal::makeZero(type))); + } + // Iterates over a list of children and adds links from them. The target of // those link is created using a function that is passed in, which receives // the index of the child. @@ -927,7 +938,7 @@ struct InfoCollector auto& fields = type.getStruct().fields; for (Index i = 0; i < fields.size(); i++) { info.links.push_back( - {getRootLocation(fields[i].type), DataLocation{type, i}}); + {getNullLocation(fields[i].type), DataLocation{type, i}}); } } else { // Link the operands to the struct's fields. @@ -947,7 +958,7 @@ struct InfoCollector {ExpressionLocation{curr->init, 0}, DataLocation{type, 0}}); } else { info.links.push_back( - {getRootLocation(type.getArray().element.type), DataLocation{type, 0}}); + {getNullLocation(type.getArray().element.type), DataLocation{type, 0}}); } addRoot(curr, PossibleContents::exactType(curr->type)); } @@ -1334,7 +1345,7 @@ struct InfoCollector source = ParamLocation{getFunction(), index}; } else { // This is the default value from the function entry, a null. - source = getRootLocation(type[i]); + source = getNullLocation(type[i]); } info.links.push_back({source, ExpressionLocation{get, i}}); } From add4a526d01b00292edc9745bf6db4c8fb9463cd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 10:43:25 -0700 Subject: [PATCH 21/69] fix --- src/passes/GUFA.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp index 92593f8b85e..21f9aa8521d 100644 --- a/src/passes/GUFA.cpp +++ b/src/passes/GUFA.cpp @@ -138,7 +138,6 @@ struct GUFAOptimizer if (contents.getType() == Type::unreachable) { // This cannot contain any possible value at all. It must be unreachable // code. -std::cout << "no value yo " << *curr << "\n"; replaceCurrent(getDroppedChildrenAndAppend( curr, wasm, options, builder.makeUnreachable())); optimized = true; @@ -178,7 +177,6 @@ std::cout << "no value yo " << *curr << "\n"; // The type is not compatible and this is a simple constant expression // like a ref.func. That means this code must be unreachable. (See below // for the case of a non-constant.) -std::cout << "not compatible man\n"; replaceCurrent(getDroppedChildrenAndAppend( curr, wasm, options, builder.makeUnreachable())); optimized = true; From bb77eba809d3ab8770274545fccdfb0653fb4e48 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 10:43:51 -0700 Subject: [PATCH 22/69] test --- test/lit/passes/gufa-cont.wast | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/lit/passes/gufa-cont.wast b/test/lit/passes/gufa-cont.wast index b93f0da60e6..1b63da8b05a 100644 --- a/test/lit/passes/gufa-cont.wast +++ b/test/lit/passes/gufa-cont.wast @@ -1,11 +1,33 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --gufa -S -o - | filecheck %s + (module + ;; CHECK: (type $func (func)) (type $func (func)) + ;; CHECK: (type $cont (cont $func)) (type $cont (cont $func)) + ;; CHECK: (elem declare func $cont) + + ;; CHECK: (tag $tag (type $func)) (tag $tag (type $func)) + ;; CHECK: (export "run_invoker" (func $main)) (export "run_invoker" (func $main)) + ;; CHECK: (func $main (type $func) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result (ref $cont)) + ;; CHECK-NEXT: (resume $cont (on $tag $block) + ;; CHECK-NEXT: (cont.new $cont + ;; CHECK-NEXT: (ref.func $cont) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) (func $main ;; A continuation is created, it suspends, and we handle that. There is ;; nothing to optimize or change here. @@ -21,6 +43,9 @@ ) ) + ;; CHECK: (func $cont (type $func) + ;; CHECK-NEXT: (suspend $tag) + ;; CHECK-NEXT: ) (func $cont (suspend $tag) ) From 149ec27e7303ed85a6a3b29a1169ce681e02a58c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 13:00:08 -0700 Subject: [PATCH 23/69] fix --- src/tools/execution-results.h | 1 + test/lit/exec/cont_many_unhandled.wast | 40 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 test/lit/exec/cont_many_unhandled.wast diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index df3721baa3c..f26314827ab 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -476,6 +476,7 @@ struct ExecutionResults { auto flow = instance.callFunction(func->name, arguments); if (flow.suspendTag) { // TODO: support stack switching here std::cout << "[exception thrown: unhandled suspend]" << std::endl; + instance.clearContinuationStore(); return Exception{}; } return flow.values; diff --git a/test/lit/exec/cont_many_unhandled.wast b/test/lit/exec/cont_many_unhandled.wast new file mode 100644 index 00000000000..6bb71c6eb16 --- /dev/null +++ b/test/lit/exec/cont_many_unhandled.wast @@ -0,0 +1,40 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s + +;; Three exports, one which throws from a resume, and two that do unhandled +;; suspends. This is a regression test for a bug where the global state of +;; continuations got into a confused state at the last export, and asserted. + +(module + (type $none (func)) + (type $cont (cont $none)) + + (tag $tag (type $none)) + + (func $empty + ) + + ;; CHECK: [fuzz-exec] calling a + ;; CHECK-NEXT: [exception thrown: tag ()] + (func $a (export "a") + (resume_throw $cont $tag + (cont.new $cont + (ref.func $empty) + ) + ) + ) + + ;; CHECK: [fuzz-exec] calling b + ;; CHECK-NEXT: [exception thrown: unhandled suspend] + (func $b (export "b") + (suspend $tag) + ) + + ;; CHECK: [fuzz-exec] calling c + ;; CHECK-NEXT: [exception thrown: unhandled suspend] + (func $c (export "c") + (suspend $tag) + ) +) + From c6fd4130ac6e6f9a0c4a202729b8bf31fd241be4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 13:00:33 -0700 Subject: [PATCH 24/69] test --- scripts/test/fuzzing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index e9c790d3c03..eb02c6a8e92 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -114,6 +114,7 @@ 'vacuum-stack-switching.wast', 'cont.wast', 'cont_simple.wast', + 'cont_many_unhandled.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', From 612cf968a7effd31900a2aaa746dab5e8771aedb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 13:40:58 -0700 Subject: [PATCH 25/69] work --- src/tools/fuzzing/fuzzing.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index c646657e166..3f16ccf0736 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3457,7 +3457,12 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { return makeRefFuncConst(type); } case HeapType::cont: { - WASM_UNREACHABLE("not implemented"); + if (type.isNullable() || oneIn(4)) { + return builder.makeRefNull(HeapTypes::cont.getBasic(share)); + } + // Emit the simplest possible continuation. + auto funcType = Type(Type::none, Type::none).as(share); + return builder.makeContNew(heapType, makeRefFuncConst(funcType)); } case HeapType::any: { // Choose a subtype we can materialize a constant for. We cannot From 1d4532ac49693862a3699db008e9c26e76f05a04 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 13:44:15 -0700 Subject: [PATCH 26/69] fix --- src/tools/fuzzing/fuzzing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 3f16ccf0736..af498a74cfe 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3461,7 +3461,7 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { return builder.makeRefNull(HeapTypes::cont.getBasic(share)); } // Emit the simplest possible continuation. - auto funcType = Type(Type::none, Type::none).as(share); + auto funcType = Type(Signature(Type::none, Type::none), NonNullable); return builder.makeContNew(heapType, makeRefFuncConst(funcType)); } case HeapType::any: { From 169f6797c8c39b40437a4d1d1a764d9e92171d8d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 13:45:47 -0700 Subject: [PATCH 27/69] fix --- src/tools/fuzzing/fuzzing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index af498a74cfe..efb163917c6 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3461,7 +3461,7 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { return builder.makeRefNull(HeapTypes::cont.getBasic(share)); } // Emit the simplest possible continuation. - auto funcType = Type(Signature(Type::none, Type::none), NonNullable); + auto funcType = Type(Signature(Type::none, Type::none), NonNullable, Exact); return builder.makeContNew(heapType, makeRefFuncConst(funcType)); } case HeapType::any: { From 605620ed1cf5f2818e1e06efe6b3a50acc61d3e8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 13:47:23 -0700 Subject: [PATCH 28/69] fix --- src/tools/fuzzing/fuzzing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index efb163917c6..af498a74cfe 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3461,7 +3461,7 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { return builder.makeRefNull(HeapTypes::cont.getBasic(share)); } // Emit the simplest possible continuation. - auto funcType = Type(Signature(Type::none, Type::none), NonNullable, Exact); + auto funcType = Type(Signature(Type::none, Type::none), NonNullable); return builder.makeContNew(heapType, makeRefFuncConst(funcType)); } case HeapType::any: { From e403a23771082f66c620b6ea587336f0aeda3a34 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 13:52:10 -0700 Subject: [PATCH 29/69] fix --- src/tools/fuzzing/fuzzing.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index af498a74cfe..db78d6dbddb 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3462,6 +3462,7 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { } // Emit the simplest possible continuation. auto funcType = Type(Signature(Type::none, Type::none), NonNullable); + heapType = Continuation(funcType); return builder.makeContNew(heapType, makeRefFuncConst(funcType)); } case HeapType::any: { From 42da090d57701cc9e644b3b87dd3e7e8d390391c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 13:53:44 -0700 Subject: [PATCH 30/69] fix --- src/tools/fuzzing/fuzzing.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index db78d6dbddb..c79d682bdaa 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3461,9 +3461,10 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { return builder.makeRefNull(HeapTypes::cont.getBasic(share)); } // Emit the simplest possible continuation. - auto funcType = Type(Signature(Type::none, Type::none), NonNullable); - heapType = Continuation(funcType); - return builder.makeContNew(heapType, makeRefFuncConst(funcType)); + auto funcSig = Signature(Type::none, Type::none); + auto funcType = Type(funcSig, NonNullable); + auto contType = Continuation(funcSig); + return builder.makeContNew(contType, makeRefFuncConst(funcType)); } case HeapType::any: { // Choose a subtype we can materialize a constant for. We cannot From 8bc214d094ba16c2d609218bba09c781ed451cdb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 16:50:32 -0700 Subject: [PATCH 31/69] work --- scripts/test/fuzzing.py | 1 + src/tools/execution-results.h | 27 ++++++++++------- test/lit/exec/cont_export.wast | 53 ++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 test/lit/exec/cont_export.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 0d8423eea02..f11649cd2e9 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -116,6 +116,7 @@ 'cont_simple.wast', 'gufa-cont.wast', 'cont_many_unhandled.wast', + 'cont_export.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index f26314827ab..5d8941ad6d1 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -139,21 +139,28 @@ struct LoggingExternalInterface : public ShellExternalInterface { tableStore(exportedTable, index, arguments[1]); return {}; } else if (import->base == "call-export") { - callExportAsJS(arguments[0].geti32()); // The second argument determines if we should catch and rethrow // exceptions. There is no observable difference in those two modes in // the binaryen interpreter, so we don't need to do anything. - + auto flow = callExportAsJS(arguments[0].geti32()); // Return nothing. If we wanted to return a value we'd need to have - // multiple such functions, one for each signature. + // multiple such functions, one for each signature. However, we do need + // to trap on suspensions (suspending through JS is not valid). + if (flow.suspendTag) { + throwJSException(); + } return {}; } else if (import->base == "call-export-catch") { + Flow flow; try { - callExportAsJS(arguments[0].geti32()); - return {Literal(int32_t(0))}; + flow = callExportAsJS(arguments[0].geti32()); } catch (const WasmException& e) { return {Literal(int32_t(1))}; } + if (flow.suspendTag) { + throwJSException(); + } + return {Literal(int32_t(0))}; } else if (import->base == "call-ref") { // Similar to call-export*, but with a ref. callRefAsJS(arguments[0]); @@ -203,7 +210,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { throwException(WasmException{Literal(payload)}); } - Literals callExportAsJS(Index index) { + Flow callExportAsJS(Index index) { if (index >= wasm.exports.size()) { // No export. throwJSException(); @@ -216,7 +223,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { return callFunctionAsJS(*exp->getInternalName()); } - Literals callRefAsJS(Literal ref) { + Flow callRefAsJS(Literal ref) { if (!ref.isFunction()) { // Not a callable ref. throwJSException(); @@ -226,7 +233,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { // Call a function in a "JS-ey" manner, adding arguments as needed, and // throwing if necessary, the same way JS does. - Literals callFunctionAsJS(Name name) { + Flow callFunctionAsJS(Name name) { auto* func = wasm.getFunction(name); // Send default values as arguments, or error if we need anything else. @@ -254,9 +261,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { } // Call the function. - auto flow = instance->callFunction(func->name, arguments); - assert(!flow.suspendTag); - return flow.values; + return instance->callFunction(func->name, arguments); } void setModuleRunner(ModuleRunner* instance_) { instance = instance_; } diff --git a/test/lit/exec/cont_export.wast b/test/lit/exec/cont_export.wast new file mode 100644 index 00000000000..b7e76f88cd1 --- /dev/null +++ b/test/lit/exec/cont_export.wast @@ -0,0 +1,53 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s + +(module + (type $none (func)) + (type $cont (cont $none)) + + (import "fuzzing-support" "log" (func $log (param i32))) + + (import "fuzzing-support" "call-export" (func $call-export (param i32 i32))) + + (tag $tag (type $none)) + + ;; CHECK: [fuzz-exec] calling suspend + ;; CHECK-NEXT: [LoggingExternalInterface logging 10] + ;; CHECK-NEXT: [exception thrown: unhandled suspend] + (func $suspend (export "suspend") + ;; Helper for below. + (call $log (i32.const 10)) + (suspend $tag) + (call $log (i32.const 20)) + ) + + ;; CHECK: [fuzz-exec] calling call-call-export + ;; CHECK-NEXT: [LoggingExternalInterface logging 10] + ;; CHECK-NEXT: [exception thrown: __private externref] + (func $call-call-export (export "call-call-export") + ;; Call suspend as an export. We cannot suspend through JS, so we throw. + (call $call-export + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: [fuzz-exec] calling handled + ;; CHECK-NEXT: [LoggingExternalInterface logging 10] + ;; CHECK-NEXT: [exception thrown: __private externref] + (func $handled (export "handled") + ;; As above, but inside a continuation, so it would be handled - if we could + ;; suspend though JS. But we can't, so we throw. + (drop + (block $inner (result (ref $cont)) + (resume $cont (on $tag $inner) + (cont.new $cont + (ref.func $call-call-export) + ) + ) + (unreachable) + ) + ) + ) +) From 124c33f0e7e20d70a6098e65a999fcd1b6654ffd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 13 Aug 2025 16:52:38 -0700 Subject: [PATCH 32/69] sinpl --- src/tools/execution-results.h | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 5d8941ad6d1..d67cb4d6801 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -139,28 +139,21 @@ struct LoggingExternalInterface : public ShellExternalInterface { tableStore(exportedTable, index, arguments[1]); return {}; } else if (import->base == "call-export") { + callExportAsJS(arguments[0].geti32()); // The second argument determines if we should catch and rethrow // exceptions. There is no observable difference in those two modes in // the binaryen interpreter, so we don't need to do anything. - auto flow = callExportAsJS(arguments[0].geti32()); + // Return nothing. If we wanted to return a value we'd need to have - // multiple such functions, one for each signature. However, we do need - // to trap on suspensions (suspending through JS is not valid). - if (flow.suspendTag) { - throwJSException(); - } + // multiple such functions, one for each signature. return {}; } else if (import->base == "call-export-catch") { - Flow flow; try { - flow = callExportAsJS(arguments[0].geti32()); + callExportAsJS(arguments[0].geti32()); + return {Literal(int32_t(0))}; } catch (const WasmException& e) { return {Literal(int32_t(1))}; } - if (flow.suspendTag) { - throwJSException(); - } - return {Literal(int32_t(0))}; } else if (import->base == "call-ref") { // Similar to call-export*, but with a ref. callRefAsJS(arguments[0]); @@ -210,7 +203,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { throwException(WasmException{Literal(payload)}); } - Flow callExportAsJS(Index index) { + Literals callExportAsJS(Index index) { if (index >= wasm.exports.size()) { // No export. throwJSException(); @@ -223,7 +216,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { return callFunctionAsJS(*exp->getInternalName()); } - Flow callRefAsJS(Literal ref) { + Literals callRefAsJS(Literal ref) { if (!ref.isFunction()) { // Not a callable ref. throwJSException(); @@ -233,7 +226,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { // Call a function in a "JS-ey" manner, adding arguments as needed, and // throwing if necessary, the same way JS does. - Flow callFunctionAsJS(Name name) { + Literals callFunctionAsJS(Name name) { auto* func = wasm.getFunction(name); // Send default values as arguments, or error if we need anything else. @@ -261,7 +254,12 @@ struct LoggingExternalInterface : public ShellExternalInterface { } // Call the function. - return instance->callFunction(func->name, arguments); + auto flow = instance->callFunction(func->name, arguments); + // Suspending through JS is not valid. + if (flow.suspendTag) { + throwJSException(); + } + return flow.values; } void setModuleRunner(ModuleRunner* instance_) { instance = instance_; } From 643142f1a476c088d06dd8571586be192971b163 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 11:00:54 -0700 Subject: [PATCH 33/69] fix --- src/tools/fuzzing/fuzzing.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index c79d682bdaa..05354c9731e 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -674,9 +674,16 @@ void TranslateToFuzzReader::setupGlobals() { // Create new random globals. for (size_t index = upTo(fuzzParams->MAX_GLOBALS); index > 0; --index) { auto type = getConcreteType(); - if (type.isContinuation()) { - // There is no way to make a continuation in a global. TODO: We could - // allow null ones, at least, that are always set to null. + auto skip = false; + for (auto t : type) { + if (t.isContinuation()) { + // There is no way to make a continuation in a global. TODO: We could + // allow null ones, at least, that are always set to null. + skip = true; + break; + } + } + if (skip) { continue; } From 8d121da68826c9ee33cfa46d4fe62525f5a8c154 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 11:55:41 -0700 Subject: [PATCH 34/69] work --- src/tools/fuzzing/fuzzing.cpp | 40 +++++++++++++++++++++-------------- src/tools/fuzzing/random.cpp | 10 +++++++++ 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 05354c9731e..b8a0a45f05a 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -616,6 +616,17 @@ void TranslateToFuzzReader::setupTables() { } } +static bool canCreateContentWithoutFunctionScope(Type type) { + for (auto t : type) { + if (t.isContinuation()) { + // There is no way to make a continuation in a global. TODO: We could + // allow null ones, at least, that are always set to null. + return false; + } + } + return true; +} + void TranslateToFuzzReader::setupGlobals() { // If there were initial wasm contents, there may be imported globals. That // would be a problem in the fuzzer harness as we'd error if we do not @@ -674,16 +685,7 @@ void TranslateToFuzzReader::setupGlobals() { // Create new random globals. for (size_t index = upTo(fuzzParams->MAX_GLOBALS); index > 0; --index) { auto type = getConcreteType(); - auto skip = false; - for (auto t : type) { - if (t.isContinuation()) { - // There is no way to make a continuation in a global. TODO: We could - // allow null ones, at least, that are always set to null. - skip = true; - break; - } - } - if (skip) { + if (!canCreateContentWithoutFunctionScope(type)) { continue; } @@ -735,7 +737,7 @@ void TranslateToFuzzReader::setupTags() { } // Add the fuzzing support tags manually sometimes. - if (!preserveImportsAndExports && oneIn(2)) { + if (!preserveImportsAndExports && oneIn(2) && !random.finished()) { auto wasmTag = builder.makeTag(Names::getValidTagName(wasm, "wasmtag"), Signature(Type::i32, Type::none)); wasmTag->module = "fuzzing-support"; @@ -878,7 +880,7 @@ void TranslateToFuzzReader::shuffleExports() { // find more things). But we also keep a good chance for the natural order // here, as it may help some initial content. Note we cannot do this if we are // preserving the exports, as their order is something we must maintain. - if (wasm.exports.empty() || preserveImportsAndExports || oneIn(2)) { + if (wasm.exports.empty() || preserveImportsAndExports || oneIn(2) || random.finished()) { return; } @@ -912,6 +914,9 @@ void TranslateToFuzzReader::addHangLimitSupport() { } void TranslateToFuzzReader::addImportLoggingSupport() { + if (random.finished()) { + return; + } for (auto type : loggableTypes) { auto func = std::make_unique(); Name baseName = std::string("log-") + type.toString(); @@ -931,7 +936,7 @@ void TranslateToFuzzReader::addImportLoggingSupport() { } void TranslateToFuzzReader::addImportCallingSupport() { - if (preserveImportsAndExports) { + if (preserveImportsAndExports || random.finished()) { return; } @@ -1024,6 +1029,9 @@ void TranslateToFuzzReader::addImportCallingSupport() { } void TranslateToFuzzReader::addImportThrowingSupport() { + if (random.finished()) { + return; + } // Throw some kind of exception from JS. If we send 0 then a pure JS // exception is thrown, and any other value is the value in a wasm tag. throwImportName = Names::getValidFunctionName(wasm, "throw"); @@ -1045,7 +1053,7 @@ void TranslateToFuzzReader::addImportTableSupport() { // for them. For simplicity, use the funcref table we use internally, though // we could pick one at random, support non-funcref ones, and even export // multiple ones TODO - if (!funcrefTableName) { + if (!funcrefTableName || random.finished()) { return; } @@ -1087,7 +1095,7 @@ void TranslateToFuzzReader::addImportTableSupport() { void TranslateToFuzzReader::addImportSleepSupport() { // Fuzz this somewhat rarely, as it may be slow, and only when we can add // imports. - if (preserveImportsAndExports || !oneIn(4)) { + if (preserveImportsAndExports || !oneIn(4) || random.finished()) { return; } @@ -1105,7 +1113,7 @@ void TranslateToFuzzReader::addImportSleepSupport() { void TranslateToFuzzReader::addHashMemorySupport() { // Don't always add this. - if (oneIn(2)) { + if (oneIn(2) || random.finished()) { return; } diff --git a/src/tools/fuzzing/random.cpp b/src/tools/fuzzing/random.cpp index 8beda478b84..05ce283ed7a 100644 --- a/src/tools/fuzzing/random.cpp +++ b/src/tools/fuzzing/random.cpp @@ -26,6 +26,13 @@ Random::Random(std::vector&& bytes_, FeatureSet features) if (bytes.empty()) { bytes.push_back(0); } + if (auto* maxBytes = getenv("BINARYEN_MAX_BYTES")) { + unsigned max = atoi(maxBytes); + if (max < bytes.size()) { + std::cerr << "resizing from " << bytes.size() << " to " << max << '\n'; + bytes.resize(max); + } + } } int8_t Random::get() { @@ -58,6 +65,9 @@ float Random::getFloat() { return Literal(get32()).reinterpretf32(); } double Random::getDouble() { return Literal(get64()).reinterpretf64(); } uint32_t Random::upTo(uint32_t x) { + if (finished()) { + return 0; + } if (x == 0) { return 0; } From a4959f4758a157a6b2d61aacaabcbd71ba39f280 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 12:53:08 -0700 Subject: [PATCH 35/69] fix --- src/wasm/wasm-binary.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 5d32f52d726..209a44ddfdc 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1863,7 +1863,9 @@ void WasmBinaryWriter::writeHeapType(HeapType type, Exactness exactness) { if (!wasm->features.hasCustomDescriptors()) { exactness = Inexact; } - if (!wasm->features.hasGC()) { + // GC can refer to full heap types for many things; Stack Switching can do so + // for functions (continuation types refer to function types). + if (!wasm->features.hasGC()) && !wasm->features.hasStackSwitching()) { type = type.getTop(); } assert(!type.isBasic() || exactness == Inexact); From 2513194f9495ea431cce30ea0b910f476f317177 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 12:53:58 -0700 Subject: [PATCH 36/69] fix --- src/wasm/wasm-binary.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 209a44ddfdc..c5d14336123 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1865,7 +1865,7 @@ void WasmBinaryWriter::writeHeapType(HeapType type, Exactness exactness) { } // GC can refer to full heap types for many things; Stack Switching can do so // for functions (continuation types refer to function types). - if (!wasm->features.hasGC()) && !wasm->features.hasStackSwitching()) { + if (!wasm->features.hasGC() && !wasm->features.hasStackSwitching()) { type = type.getTop(); } assert(!type.isBasic() || exactness == Inexact); From 9d4cc20dda5fc242e6286ff14b41b2c5735bec3b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 12:55:09 -0700 Subject: [PATCH 37/69] fix --- src/wasm/wasm-binary.cpp | 4 +++- test/lit/basic/cont-no-gc.wast | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 test/lit/basic/cont-no-gc.wast diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 5d32f52d726..c5d14336123 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1863,7 +1863,9 @@ void WasmBinaryWriter::writeHeapType(HeapType type, Exactness exactness) { if (!wasm->features.hasCustomDescriptors()) { exactness = Inexact; } - if (!wasm->features.hasGC()) { + // GC can refer to full heap types for many things; Stack Switching can do so + // for functions (continuation types refer to function types). + if (!wasm->features.hasGC() && !wasm->features.hasStackSwitching()) { type = type.getTop(); } assert(!type.isBasic() || exactness == Inexact); diff --git a/test/lit/basic/cont-no-gc.wast b/test/lit/basic/cont-no-gc.wast new file mode 100644 index 00000000000..c70d80c378b --- /dev/null +++ b/test/lit/basic/cont-no-gc.wast @@ -0,0 +1,20 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Test that we can write a binary without erroring when using stack switching +;; without GC. This verifies we can emit continuation types properly even when +;; GC is disabled + +;; RUN: wasm-opt %s --enable-stack-switching --roundtrip -S -o - | filecheck %s + +(module + (type $proc (func)) + (type $cont (cont $proc)) + + (func $main + (drop + (cont.new $cont + (ref.func $main) + ) + ) + ) +) From 997579ec6c2454d74c6b1c6ece655a2032a163c4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 12:59:02 -0700 Subject: [PATCH 38/69] fix --- test/lit/basic/cont-no-gc.wast | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/lit/basic/cont-no-gc.wast b/test/lit/basic/cont-no-gc.wast index c70d80c378b..f7fe3f27e1d 100644 --- a/test/lit/basic/cont-no-gc.wast +++ b/test/lit/basic/cont-no-gc.wast @@ -4,12 +4,21 @@ ;; without GC. This verifies we can emit continuation types properly even when ;; GC is disabled -;; RUN: wasm-opt %s --enable-stack-switching --roundtrip -S -o - | filecheck %s +;; RUN: wasm-opt %s --enable-stack-switching --enable-reference-types --roundtrip -S -o - | filecheck %s (module + ;; CHECK: (type $proc (func)) (type $proc (func)) + ;; CHECK: (type $cont (cont $proc)) (type $cont (cont $proc)) + ;; CHECK: (func $main + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (cont.new $cont + ;; CHECK-NEXT: (ref.func $main) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) (func $main (drop (cont.new $cont From 1759b0330e398c03e7dd04e405201c2f6e5d1b71 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 13:21:20 -0700 Subject: [PATCH 39/69] bettr --- src/wasm/wasm-binary.cpp | 6 ++---- src/wasm/wasm-stack.cpp | 4 +++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index c5d14336123..0b03c5824e9 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -310,7 +310,7 @@ void WasmBinaryWriter::writeTypes() { break; case HeapTypeKind::Cont: o << uint8_t(BinaryConsts::EncodedType::Cont); - writeHeapType(type.getContinuation().type, Inexact); + writeIndexedHeapType(type.getContinuation().type); break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); @@ -1863,9 +1863,7 @@ void WasmBinaryWriter::writeHeapType(HeapType type, Exactness exactness) { if (!wasm->features.hasCustomDescriptors()) { exactness = Inexact; } - // GC can refer to full heap types for many things; Stack Switching can do so - // for functions (continuation types refer to function types). - if (!wasm->features.hasGC() && !wasm->features.hasStackSwitching()) { + if (!wasm->features.hasGC()) { type = type.getTop(); } assert(!type.isBasic() || exactness == Inexact); diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 529bb1db19c..bfc0d8191ae 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -135,7 +135,9 @@ void BinaryInstWriter::visitCallIndirect(CallIndirect* curr) { Index tableIdx = parent.getTableIndex(curr->table); int8_t op = curr->isReturn ? BinaryConsts::RetCallIndirect : BinaryConsts::CallIndirect; - o << op << U32LEB(parent.getTypeIndex(curr->heapType)) << U32LEB(tableIdx); + o << op; + parent.writeIndexedHeapType(curr->heapType); + o << U32LEB(tableIdx); } void BinaryInstWriter::visitLocalGet(LocalGet* curr) { From eb67facb204efd5fc66e1be65eb47dfa69b0d05e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 13:36:25 -0700 Subject: [PATCH 40/69] fix? --- src/tools/fuzzing/fuzzing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index b8a0a45f05a..0fbbfbf484e 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -5238,7 +5238,7 @@ Expression* TranslateToFuzzReader::makeI31Get(Type type) { Expression* TranslateToFuzzReader::makeThrow(Type type) { assert(type == Type::unreachable); Tag* tag; - if (trivialNesting) { + if (trivialNesting || random.finished()) { // We are nested under a makeTrivial call, so only emit something trivial. // Get (or create) a trivial tag, so we have no operands (and will not call // make(), below). Otherwise, we might recurse very deeply if we threw a From 135c25260790f0a751b89ae160b9c0069752c561 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 14:12:46 -0700 Subject: [PATCH 41/69] work --- src/tools/execution-results.h | 6 +++- test/lit/exec/cont_export_throw.wast | 48 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 test/lit/exec/cont_export_throw.wast diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index d67cb4d6801..312e0b03a66 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -472,26 +472,30 @@ struct ExecutionResults { if (!param.isDefaultable()) { std::cout << "[trap fuzzer can only send defaultable parameters to " "exports]\n"; + instance.clearContinuationStore(); return Trap{}; } arguments.push_back(Literal::makeZero(param)); } auto flow = instance.callFunction(func->name, arguments); - if (flow.suspendTag) { // TODO: support stack switching here + if (flow.suspendTag) { std::cout << "[exception thrown: unhandled suspend]" << std::endl; instance.clearContinuationStore(); return Exception{}; } return flow.values; } catch (const TrapException&) { + instance.clearContinuationStore(); return Trap{}; } catch (const WasmException& e) { + instance.clearContinuationStore(); std::cout << "[exception thrown: " << e << "]" << std::endl; return Exception{}; } catch (const HostLimitException&) { // This should be ignored and not compared with, as optimizations can // change whether a host limit is reached. ignore = true; + instance.clearContinuationStore(); return {}; } } diff --git a/test/lit/exec/cont_export_throw.wast b/test/lit/exec/cont_export_throw.wast new file mode 100644 index 00000000000..343b82d598c --- /dev/null +++ b/test/lit/exec/cont_export_throw.wast @@ -0,0 +1,48 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s + +;; Three exports, one which suspends, the second resumes but ends up throwing, +;; and another suspend. This is a regression test for a bug where the global +;; state of continuations got into a confused state at the last export, and +;; asserted. + +(module + (type $none (func)) + (type $cont (cont $none)) + + (import "fuzzing-support" "call-export" (func $call-export (param i32 i32))) + + (tag $tag (type $none)) + + (export "suspend" (func $suspend)) + (export "handled" (func $handle)) + (export "suspend_invoker" (func $suspend)) + + (func $suspend + (suspend $tag) + ) + + (func $handle + (drop + (block $block (result (ref $cont)) + (resume $cont (on $tag $block) + (cont.new $cont + (ref.func $cont) + ) + ) + (unreachable) + ) + ) + ) + + (func $cont + ;; This calls $suspend through an export, which traps as we cannot suspend + ;; through JS. + (call $call-export + (i32.const 0) + (i32.const 0) + ) + ) +) + From f02650b2e2b91b80f4b25d08bce7bde227950cfc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 14:14:00 -0700 Subject: [PATCH 42/69] wo --- src/tools/execution-results.h | 6 +++- test/lit/exec/cont_export_throw.wast | 48 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 test/lit/exec/cont_export_throw.wast diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index d67cb4d6801..312e0b03a66 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -472,26 +472,30 @@ struct ExecutionResults { if (!param.isDefaultable()) { std::cout << "[trap fuzzer can only send defaultable parameters to " "exports]\n"; + instance.clearContinuationStore(); return Trap{}; } arguments.push_back(Literal::makeZero(param)); } auto flow = instance.callFunction(func->name, arguments); - if (flow.suspendTag) { // TODO: support stack switching here + if (flow.suspendTag) { std::cout << "[exception thrown: unhandled suspend]" << std::endl; instance.clearContinuationStore(); return Exception{}; } return flow.values; } catch (const TrapException&) { + instance.clearContinuationStore(); return Trap{}; } catch (const WasmException& e) { + instance.clearContinuationStore(); std::cout << "[exception thrown: " << e << "]" << std::endl; return Exception{}; } catch (const HostLimitException&) { // This should be ignored and not compared with, as optimizations can // change whether a host limit is reached. ignore = true; + instance.clearContinuationStore(); return {}; } } diff --git a/test/lit/exec/cont_export_throw.wast b/test/lit/exec/cont_export_throw.wast new file mode 100644 index 00000000000..343b82d598c --- /dev/null +++ b/test/lit/exec/cont_export_throw.wast @@ -0,0 +1,48 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s + +;; Three exports, one which suspends, the second resumes but ends up throwing, +;; and another suspend. This is a regression test for a bug where the global +;; state of continuations got into a confused state at the last export, and +;; asserted. + +(module + (type $none (func)) + (type $cont (cont $none)) + + (import "fuzzing-support" "call-export" (func $call-export (param i32 i32))) + + (tag $tag (type $none)) + + (export "suspend" (func $suspend)) + (export "handled" (func $handle)) + (export "suspend_invoker" (func $suspend)) + + (func $suspend + (suspend $tag) + ) + + (func $handle + (drop + (block $block (result (ref $cont)) + (resume $cont (on $tag $block) + (cont.new $cont + (ref.func $cont) + ) + ) + (unreachable) + ) + ) + ) + + (func $cont + ;; This calls $suspend through an export, which traps as we cannot suspend + ;; through JS. + (call $call-export + (i32.const 0) + (i32.const 0) + ) + ) +) + From 96b21304f14a3b7c61aa7e1a440ff34e25558a33 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 14:14:45 -0700 Subject: [PATCH 43/69] work --- scripts/test/fuzzing.py | 1 + test/lit/exec/cont_export_throw.wast | 2 ++ 2 files changed, 3 insertions(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index f11649cd2e9..6907662d7ca 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -117,6 +117,7 @@ 'gufa-cont.wast', 'cont_many_unhandled.wast', 'cont_export.wast', + 'cont_export_throw.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', diff --git a/test/lit/exec/cont_export_throw.wast b/test/lit/exec/cont_export_throw.wast index 343b82d598c..73611e6cfbb 100644 --- a/test/lit/exec/cont_export_throw.wast +++ b/test/lit/exec/cont_export_throw.wast @@ -19,6 +19,8 @@ (export "handled" (func $handle)) (export "suspend_invoker" (func $suspend)) + ;; CHECK: [fuzz-exec] calling suspend + ;; CHECK-NEXT: [exception thrown: unhandled suspend] (func $suspend (suspend $tag) ) From b52328925fb919862609c3e25b6f2e9d3d5ebc1d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 14:17:02 -0700 Subject: [PATCH 44/69] test --- test/lit/exec/cont_export_throw.wast | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/lit/exec/cont_export_throw.wast b/test/lit/exec/cont_export_throw.wast index 73611e6cfbb..39fa0af20b4 100644 --- a/test/lit/exec/cont_export_throw.wast +++ b/test/lit/exec/cont_export_throw.wast @@ -15,17 +15,15 @@ (tag $tag (type $none)) - (export "suspend" (func $suspend)) - (export "handled" (func $handle)) - (export "suspend_invoker" (func $suspend)) - ;; CHECK: [fuzz-exec] calling suspend ;; CHECK-NEXT: [exception thrown: unhandled suspend] - (func $suspend + (func $suspend (export "suspend") (suspend $tag) ) - (func $handle + ;; CHECK: [fuzz-exec] calling handled + ;; CHECK-NEXT: [exception thrown: __private externref] + (func $handled (export "handled") (drop (block $block (result (ref $cont)) (resume $cont (on $tag $block) @@ -38,6 +36,12 @@ ) ) + ;; CHECK: [fuzz-exec] calling suspend2 + ;; CHECK-NEXT: [exception thrown: unhandled suspend] + (func $suspend2 (export "suspend2") + (suspend $tag) + ) + (func $cont ;; This calls $suspend through an export, which traps as we cannot suspend ;; through JS. From ceb43385605e5245ccc07db18c85f57541515e64 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 17:04:15 -0700 Subject: [PATCH 45/69] fix --- 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 db3572dacd6..d178ff942a5 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2403,7 +2403,7 @@ def get_random_opts(): # disabled, its dependent features need to be disabled as well. IMPLIED_FEATURE_OPTS = { '--disable-reference-types': ['--disable-gc', '--disable-exception-handling', '--disable-strings'], - '--disable-gc': ['--disable-strings'], + '--disable-gc': ['--disable-strings', '--disable-stack-switching'], } print(''' From f1dd3406a91baa90208504272068dbd3d5c69f3e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 Aug 2025 17:08:01 -0700 Subject: [PATCH 46/69] undo --- src/wasm/wasm-binary.cpp | 2 +- test/lit/basic/cont-no-gc.wast | 29 ----------------------------- 2 files changed, 1 insertion(+), 30 deletions(-) delete mode 100644 test/lit/basic/cont-no-gc.wast diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 0b03c5824e9..5d32f52d726 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -310,7 +310,7 @@ void WasmBinaryWriter::writeTypes() { break; case HeapTypeKind::Cont: o << uint8_t(BinaryConsts::EncodedType::Cont); - writeIndexedHeapType(type.getContinuation().type); + writeHeapType(type.getContinuation().type, Inexact); break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); diff --git a/test/lit/basic/cont-no-gc.wast b/test/lit/basic/cont-no-gc.wast deleted file mode 100644 index f7fe3f27e1d..00000000000 --- a/test/lit/basic/cont-no-gc.wast +++ /dev/null @@ -1,29 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. - -;; Test that we can write a binary without erroring when using stack switching -;; without GC. This verifies we can emit continuation types properly even when -;; GC is disabled - -;; RUN: wasm-opt %s --enable-stack-switching --enable-reference-types --roundtrip -S -o - | filecheck %s - -(module - ;; CHECK: (type $proc (func)) - (type $proc (func)) - ;; CHECK: (type $cont (cont $proc)) - (type $cont (cont $proc)) - - ;; CHECK: (func $main - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (cont.new $cont - ;; CHECK-NEXT: (ref.func $main) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $main - (drop - (cont.new $cont - (ref.func $main) - ) - ) - ) -) From c5b2e3f544b7abac2b46a46a56188b90d9abdafa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 08:01:16 -0700 Subject: [PATCH 47/69] work --- src/passes/TypeMerging.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index 186aa69080b..6bd06e040d6 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -224,6 +224,7 @@ bool shapeEq(HeapType a, HeapType b); bool shapeEq(const Struct& a, const Struct& b); bool shapeEq(Array a, Array b); bool shapeEq(Signature a, Signature b); +bool shapeEq(Continuation a, Continuation b); bool shapeEq(Field a, Field b); bool shapeEq(Type a, Type b); bool shapeEq(const Tuple& a, const Tuple& b); @@ -232,6 +233,7 @@ size_t shapeHash(HeapType a); size_t shapeHash(const Struct& a); size_t shapeHash(Array a); size_t shapeHash(Signature a); +size_t shapeHash(Continuation a); size_t shapeHash(Field a); size_t shapeHash(Type a); size_t shapeHash(const Tuple& a); @@ -690,7 +692,10 @@ bool shapeEq(HeapType a, HeapType b) { } break; case HeapTypeKind::Cont: - WASM_UNREACHABLE("TODO: cont"); + if (!shapeEq(a.getContinuation(), b.getContinuation())) { + return false; + } + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -719,7 +724,8 @@ size_t shapeHash(HeapType a) { hash_combine(digest, shapeHash(type.getArray())); continue; case HeapTypeKind::Cont: - WASM_UNREACHABLE("TODO: cont"); + hash_combine(digest, shapeHash(type.getContinuation())); + continue; case HeapTypeKind::Basic: continue; } @@ -762,6 +768,10 @@ size_t shapeHash(Signature a) { return digest; } +bool shapeEq(Continuation a, Continuation b) { return shapeEq(a.type, b.type); } + +size_t shapeHash(Continuation a) { return shapeHash(a.type); } + bool shapeEq(Field a, Field b) { return a.packedType == b.packedType && a.mutable_ == b.mutable_ && shapeEq(a.type, b.type); From 38df24614d3a46520a53d16a432ea82ed6d87977 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 08:28:18 -0700 Subject: [PATCH 48/69] test --- test/lit/passes/type-merging-cont.wast | 50 ++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 test/lit/passes/type-merging-cont.wast diff --git a/test/lit/passes/type-merging-cont.wast b/test/lit/passes/type-merging-cont.wast new file mode 100644 index 00000000000..76fde96384a --- /dev/null +++ b/test/lit/passes/type-merging-cont.wast @@ -0,0 +1,50 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \ +;; RUN: --type-merging --remove-unused-types -S -o - | filecheck %s + +;; $A1 and $A2 can be merged, and $B1/$B2, as they have identical continuation +;; fields. But $A*/B* cannot be merged, as the continuations differ. + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $f (func)) + (type $f (func)) + ;; CHECK: (type $f_0 (func)) + + ;; CHECK: (type $k (cont $f_0)) + (type $k (cont $f)) + + (type $f-i32 (func (result i32))) + ;; CHECK: (type $f-i32_0 (func (result i32))) + + ;; CHECK: (type $k-i32 (cont $f-i32_0)) + (type $k-i32 (cont $f-i32)) + + (rec + ;; CHECK: (type $A1 (struct (field (ref $k)))) + (type $A1 (struct (ref $k))) + ;; CHECK: (type $A2 (struct (field (ref $k-i32)))) + (type $A2 (struct (ref $k-i32))) + (type $B1 (struct (ref $k))) + (type $B2 (struct (ref $k-i32))) + ) + + ;; CHECK: (func $test (type $f) + ;; CHECK-NEXT: (local $k (ref $k)) + ;; CHECK-NEXT: (local $k-i32 (ref $k-i32)) + ;; CHECK-NEXT: (local $a1 (ref $A1)) + ;; CHECK-NEXT: (local $a2 (ref $A2)) + ;; CHECK-NEXT: (local $b1 (ref $A1)) + ;; CHECK-NEXT: (local $b2 (ref $A2)) + ;; CHECK-NEXT: ) + (func $test + (local $k (ref $k)) + (local $k-i32 (ref $k-i32)) + (local $a1 (ref $A1)) + (local $a2 (ref $A2)) + (local $b1 (ref $B1)) + (local $b2 (ref $B2)) + ) +) + From efdb9a7ee7b5e2abd16013388961924fe6f4846a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 08:46:53 -0700 Subject: [PATCH 49/69] skip --- scripts/test/fuzzing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 6907662d7ca..1afbb898833 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -118,6 +118,7 @@ 'cont_many_unhandled.wast', 'cont_export.wast', 'cont_export_throw.wast', + 'type-merging-cont.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', From 8360dfcfc47185c9dd4769ce1fcfed90445d89a7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 09:48:42 -0700 Subject: [PATCH 50/69] Update test/lit/passes/type-merging-cont.wast Co-authored-by: Thomas Lively --- test/lit/passes/type-merging-cont.wast | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/type-merging-cont.wast b/test/lit/passes/type-merging-cont.wast index 76fde96384a..90e791c895b 100644 --- a/test/lit/passes/type-merging-cont.wast +++ b/test/lit/passes/type-merging-cont.wast @@ -25,8 +25,8 @@ ;; CHECK: (type $A1 (struct (field (ref $k)))) (type $A1 (struct (ref $k))) ;; CHECK: (type $A2 (struct (field (ref $k-i32)))) - (type $A2 (struct (ref $k-i32))) - (type $B1 (struct (ref $k))) + (type $A2 (struct (ref $k))) + (type $B1 (struct (ref $k-i32))) (type $B2 (struct (ref $k-i32))) ) From e92d00eaffe063b88ff36327a8986ac0ff7f5150 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 09:58:25 -0700 Subject: [PATCH 51/69] fix --- src/tools/fuzzing/fuzzing.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 0fbbfbf484e..5ba3b8249d2 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -880,7 +880,8 @@ void TranslateToFuzzReader::shuffleExports() { // find more things). But we also keep a good chance for the natural order // here, as it may help some initial content. Note we cannot do this if we are // preserving the exports, as their order is something we must maintain. - if (wasm.exports.empty() || preserveImportsAndExports || oneIn(2) || random.finished()) { + if (wasm.exports.empty() || preserveImportsAndExports || oneIn(2) || + random.finished()) { return; } From 4c2c02a37e0af0996240faf4816527723ac7a59f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 10:01:53 -0700 Subject: [PATCH 52/69] work --- src/tools/fuzzing/fuzzing.cpp | 20 +++++++++++++------- src/tools/fuzzing/random.cpp | 10 ++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 9722faec4b5..41ffdb2bdbb 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -717,7 +717,7 @@ void TranslateToFuzzReader::setupTags() { } // Add the fuzzing support tags manually sometimes. - if (!preserveImportsAndExports && oneIn(2)) { + if (!preserveImportsAndExports && oneIn(2) && !random.finished()) { auto wasmTag = builder.makeTag(Names::getValidTagName(wasm, "wasmtag"), Signature(Type::i32, Type::none)); wasmTag->module = "fuzzing-support"; @@ -859,7 +859,7 @@ void TranslateToFuzzReader::shuffleExports() { // find more things). But we also keep a good chance for the natural order // here, as it may help some initial content. Note we cannot do this if we are // preserving the exports, as their order is something we must maintain. - if (wasm.exports.empty() || preserveImportsAndExports || oneIn(2)) { + if (wasm.exports.empty() || preserveImportsAndExports || oneIn(2) || random.finished()) { return; } @@ -893,6 +893,9 @@ void TranslateToFuzzReader::addHangLimitSupport() { } void TranslateToFuzzReader::addImportLoggingSupport() { + if (random.finished()) { + return; + } for (auto type : loggableTypes) { auto func = std::make_unique(); Name baseName = std::string("log-") + type.toString(); @@ -912,7 +915,7 @@ void TranslateToFuzzReader::addImportLoggingSupport() { } void TranslateToFuzzReader::addImportCallingSupport() { - if (preserveImportsAndExports) { + if (preserveImportsAndExports || random.finished()) { return; } @@ -1005,6 +1008,9 @@ void TranslateToFuzzReader::addImportCallingSupport() { } void TranslateToFuzzReader::addImportThrowingSupport() { + if (random.finished()) { + return; + } // Throw some kind of exception from JS. If we send 0 then a pure JS // exception is thrown, and any other value is the value in a wasm tag. throwImportName = Names::getValidFunctionName(wasm, "throw"); @@ -1026,7 +1032,7 @@ void TranslateToFuzzReader::addImportTableSupport() { // for them. For simplicity, use the funcref table we use internally, though // we could pick one at random, support non-funcref ones, and even export // multiple ones TODO - if (!funcrefTableName) { + if (!funcrefTableName || random.finished()) { return; } @@ -1068,7 +1074,7 @@ void TranslateToFuzzReader::addImportTableSupport() { void TranslateToFuzzReader::addImportSleepSupport() { // Fuzz this somewhat rarely, as it may be slow, and only when we can add // imports. - if (preserveImportsAndExports || !oneIn(4)) { + if (preserveImportsAndExports || !oneIn(4) || random.finished()) { return; } @@ -1086,7 +1092,7 @@ void TranslateToFuzzReader::addImportSleepSupport() { void TranslateToFuzzReader::addHashMemorySupport() { // Don't always add this. - if (oneIn(2)) { + if (oneIn(2) || random.finished()) { return; } @@ -5202,7 +5208,7 @@ Expression* TranslateToFuzzReader::makeI31Get(Type type) { Expression* TranslateToFuzzReader::makeThrow(Type type) { assert(type == Type::unreachable); Tag* tag; - if (trivialNesting) { + if (trivialNesting || random.finished()) { // We are nested under a makeTrivial call, so only emit something trivial. // Get (or create) a trivial tag, so we have no operands (and will not call // make(), below). Otherwise, we might recurse very deeply if we threw a diff --git a/src/tools/fuzzing/random.cpp b/src/tools/fuzzing/random.cpp index 8beda478b84..05ce283ed7a 100644 --- a/src/tools/fuzzing/random.cpp +++ b/src/tools/fuzzing/random.cpp @@ -26,6 +26,13 @@ Random::Random(std::vector&& bytes_, FeatureSet features) if (bytes.empty()) { bytes.push_back(0); } + if (auto* maxBytes = getenv("BINARYEN_MAX_BYTES")) { + unsigned max = atoi(maxBytes); + if (max < bytes.size()) { + std::cerr << "resizing from " << bytes.size() << " to " << max << '\n'; + bytes.resize(max); + } + } } int8_t Random::get() { @@ -58,6 +65,9 @@ float Random::getFloat() { return Literal(get32()).reinterpretf32(); } double Random::getDouble() { return Literal(get64()).reinterpretf64(); } uint32_t Random::upTo(uint32_t x) { + if (finished()) { + return 0; + } if (x == 0) { return 0; } From f7f032b743e4960bf843dbc925878351271cde69 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 10:01:58 -0700 Subject: [PATCH 53/69] form --- src/tools/fuzzing/fuzzing.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 41ffdb2bdbb..93029196632 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -859,7 +859,8 @@ void TranslateToFuzzReader::shuffleExports() { // find more things). But we also keep a good chance for the natural order // here, as it may help some initial content. Note we cannot do this if we are // preserving the exports, as their order is something we must maintain. - if (wasm.exports.empty() || preserveImportsAndExports || oneIn(2) || random.finished()) { + if (wasm.exports.empty() || preserveImportsAndExports || oneIn(2) || + random.finished()) { return; } From bc29e685a509efde8d431bcea3ee95479cd8e5dd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 10:02:33 -0700 Subject: [PATCH 54/69] form --- src/tools/fuzzing/random.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/fuzzing/random.cpp b/src/tools/fuzzing/random.cpp index 05ce283ed7a..2487baae687 100644 --- a/src/tools/fuzzing/random.cpp +++ b/src/tools/fuzzing/random.cpp @@ -26,10 +26,10 @@ Random::Random(std::vector&& bytes_, FeatureSet features) if (bytes.empty()) { bytes.push_back(0); } - if (auto* maxBytes = getenv("BINARYEN_MAX_BYTES")) { + if (auto* maxBytes = getenv("BINARYEN_FUZZER_MAX_BYTES")) { unsigned max = atoi(maxBytes); if (max < bytes.size()) { - std::cerr << "resizing from " << bytes.size() << " to " << max << '\n'; + std::cerr << "fuzzer: resizing from " << bytes.size() << " to " << max << '\n'; bytes.resize(max); } } From b0b9733049a90d91178490e06b3a8405f806a7e3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 10:02:35 -0700 Subject: [PATCH 55/69] form --- src/tools/fuzzing/random.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/fuzzing/random.cpp b/src/tools/fuzzing/random.cpp index 2487baae687..cfcdbdd970e 100644 --- a/src/tools/fuzzing/random.cpp +++ b/src/tools/fuzzing/random.cpp @@ -29,7 +29,8 @@ Random::Random(std::vector&& bytes_, FeatureSet features) if (auto* maxBytes = getenv("BINARYEN_FUZZER_MAX_BYTES")) { unsigned max = atoi(maxBytes); if (max < bytes.size()) { - std::cerr << "fuzzer: resizing from " << bytes.size() << " to " << max << '\n'; + std::cerr << "fuzzer: resizing from " << bytes.size() << " to " << max + << '\n'; bytes.resize(max); } } From afe5df2936471a2b292bc7a3adaffc3a090b9990 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 10:06:28 -0700 Subject: [PATCH 56/69] fix --- src/tools/fuzzing/fuzzing.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 93029196632..8f89c1292ed 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -894,9 +894,6 @@ void TranslateToFuzzReader::addHangLimitSupport() { } void TranslateToFuzzReader::addImportLoggingSupport() { - if (random.finished()) { - return; - } for (auto type : loggableTypes) { auto func = std::make_unique(); Name baseName = std::string("log-") + type.toString(); @@ -916,7 +913,7 @@ void TranslateToFuzzReader::addImportLoggingSupport() { } void TranslateToFuzzReader::addImportCallingSupport() { - if (preserveImportsAndExports || random.finished()) { + if (preserveImportsAndExports) { return; } @@ -941,7 +938,7 @@ void TranslateToFuzzReader::addImportCallingSupport() { // Only add these some of the time, as they inhibit some fuzzing (things like // wasm-ctor-eval and wasm-merge are sensitive to the wasm being able to call // its own exports, and to care about the indexes of the exports). - if (oneIn(2)) { + if (oneIn(2) || random.finished()) { return; } From 5808c648e6897a2fdeacf4d48ef49bdfb2292ddf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 10:13:11 -0700 Subject: [PATCH 57/69] update --- test/lit/passes/type-merging-cont.wast | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/lit/passes/type-merging-cont.wast b/test/lit/passes/type-merging-cont.wast index 90e791c895b..e8f059345b1 100644 --- a/test/lit/passes/type-merging-cont.wast +++ b/test/lit/passes/type-merging-cont.wast @@ -24,8 +24,8 @@ (rec ;; CHECK: (type $A1 (struct (field (ref $k)))) (type $A1 (struct (ref $k))) - ;; CHECK: (type $A2 (struct (field (ref $k-i32)))) (type $A2 (struct (ref $k))) + ;; CHECK: (type $B1 (struct (field (ref $k-i32)))) (type $B1 (struct (ref $k-i32))) (type $B2 (struct (ref $k-i32))) ) @@ -34,9 +34,9 @@ ;; CHECK-NEXT: (local $k (ref $k)) ;; CHECK-NEXT: (local $k-i32 (ref $k-i32)) ;; CHECK-NEXT: (local $a1 (ref $A1)) - ;; CHECK-NEXT: (local $a2 (ref $A2)) - ;; CHECK-NEXT: (local $b1 (ref $A1)) - ;; CHECK-NEXT: (local $b2 (ref $A2)) + ;; CHECK-NEXT: (local $a2 (ref $A1)) + ;; CHECK-NEXT: (local $b1 (ref $B1)) + ;; CHECK-NEXT: (local $b2 (ref $B1)) ;; CHECK-NEXT: ) (func $test (local $k (ref $k)) From 41cf9b6b740f5685f09cfdeda7b43aecb4e19197 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 10:21:11 -0700 Subject: [PATCH 58/69] fix --- src/wasm-interpreter.h | 6 +++++- test/spec/cont.wast | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 2357bdf6827..866075c6d4a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4927,7 +4927,11 @@ class ModuleRunnerBase : public ExpressionRunner { } // Get and execute the continuation. - auto contData = flow.getSingleValue().getContData(); + auto cont = flow.getSingleValue(); + if (cont.isNull()) { + trap("null ref"); + } + auto contData = cont.getContData(); auto func = contData->func; // If we are resuming a nested suspend then we should just rewind the call diff --git a/test/spec/cont.wast b/test/spec/cont.wast index 2c344cfef91..9e67a2dcd6a 100644 --- a/test/spec/cont.wast +++ b/test/spec/cont.wast @@ -116,6 +116,12 @@ (func (export "non-linear-4") (call $nl4 (cont.new $k1 (ref.func $r1))) ) + + (func (export "null") + (resume $k1 + (ref.null $k1) + ) + ) ) (assert_suspension (invoke "unhandled-1") "unhandled") @@ -132,6 +138,8 @@ (assert_trap (invoke "non-linear-3") "continuation already consumed") (assert_trap (invoke "non-linear-4") "continuation already consumed") +(assert_trap (invoke "null") "null continuation reference") + (assert_invalid (module (type $ft (func)) From 00fc7916b91c1bfbcdc32e12cefc9fcc383d9378 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 11:50:24 -0700 Subject: [PATCH 59/69] undo --- src/tools/fuzzing/fuzzing.cpp | 3 --- src/wasm/wasm-stack.cpp | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 4cba8c7907d..6e463ef6105 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -915,9 +915,6 @@ void TranslateToFuzzReader::addHangLimitSupport() { } void TranslateToFuzzReader::addImportLoggingSupport() { - if (random.finished()) { - return; - } for (auto type : loggableTypes) { auto func = std::make_unique(); Name baseName = std::string("log-") + type.toString(); diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index bfc0d8191ae..529bb1db19c 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -135,9 +135,7 @@ void BinaryInstWriter::visitCallIndirect(CallIndirect* curr) { Index tableIdx = parent.getTableIndex(curr->table); int8_t op = curr->isReturn ? BinaryConsts::RetCallIndirect : BinaryConsts::CallIndirect; - o << op; - parent.writeIndexedHeapType(curr->heapType); - o << U32LEB(tableIdx); + o << op << U32LEB(parent.getTypeIndex(curr->heapType)) << U32LEB(tableIdx); } void BinaryInstWriter::visitLocalGet(LocalGet* curr) { From 575ba68826deddf5f3bbf684988fdad35cfd476d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 12:41:11 -0700 Subject: [PATCH 60/69] fix --- scripts/test/fuzzing.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 8c0294617f2..d791f594409 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -90,6 +90,9 @@ # it removes unknown imports 'string-lifting.wast', 'string-lifting-custom-module.wast', + # TODO: fuzzer support for remaining stack switching instructions: switch, + # cont.bind + 'cont.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', From 45aa8d48f31e823e1530deb203118c21b473cd69 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 13:13:22 -0700 Subject: [PATCH 61/69] undo --- src/tools/fuzzing/fuzzing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 6e463ef6105..f7f696d6ea2 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -934,7 +934,7 @@ void TranslateToFuzzReader::addImportLoggingSupport() { } void TranslateToFuzzReader::addImportCallingSupport() { - if (preserveImportsAndExports || random.finished()) { + if (preserveImportsAndExports) { return; } From 6e0b0b21039952aeb9dd4ba57495937f46b80989 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 13:25:09 -0700 Subject: [PATCH 62/69] update --- ...e-to-fuzz_all-features_metrics_noprint.txt | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 5bdbbb27ea9..047b8e83a66 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,57 +1,57 @@ Metrics total - [exports] : 14 - [funcs] : 20 + [exports] : 10 + [funcs] : 14 [globals] : 26 [imports] : 12 [memories] : 1 [memory-data] : 16 - [table-data] : 6 + [table-data] : 3 [tables] : 2 - [tags] : 1 - [total] : 734 - [vars] : 45 - ArrayNewFixed : 5 + [tags] : 2 + [total] : 642 + [vars] : 48 + ArrayNewFixed : 6 AtomicCmpxchg : 1 - AtomicFence : 1 - AtomicNotify : 2 - AtomicRMW : 1 - Binary : 43 - Block : 115 - BrOn : 1 - Break : 11 - Call : 27 - CallRef : 2 - Const : 144 - Drop : 15 - GlobalGet : 66 - GlobalSet : 50 + AtomicFence : 2 + Binary : 40 + Block : 106 + Break : 8 + Call : 26 + CallRef : 1 + Const : 109 + DataDrop : 2 + Drop : 14 + GlobalGet : 57 + GlobalSet : 44 If : 32 - Load : 5 + Load : 6 LocalGet : 20 - LocalSet : 11 - Loop : 5 + LocalSet : 16 + Loop : 8 MemoryCopy : 1 - MemoryFill : 1 - MemoryInit : 1 - Nop : 13 - RefEq : 3 - RefFunc : 11 - RefI31 : 12 - RefNull : 11 + Nop : 11 + Pop : 1 + RefAs : 2 + RefCast : 2 + RefEq : 4 + RefFunc : 5 + RefI31 : 8 + RefNull : 8 RefTest : 1 Return : 6 - SIMDExtract : 2 - Select : 3 - Store : 3 - StringConst : 13 - StringEq : 2 - StringWTF16Get : 1 - StructNew : 14 + SIMDExtract : 1 + Select : 1 + Store : 1 + StringConst : 8 + StringEncode : 1 + StringMeasure : 1 + StructNew : 9 + Switch : 1 Throw : 1 - Try : 5 - TryTable : 6 - TupleExtract : 1 - TupleMake : 8 - Unary : 33 - Unreachable : 25 + Try : 1 + TryTable : 4 + TupleExtract : 2 + TupleMake : 6 + Unary : 36 + Unreachable : 22 From 090814f8025bb6e5345526eaea9809b368e72990 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 Aug 2025 16:32:58 -0700 Subject: [PATCH 63/69] feedback --- src/tools/fuzzing/fuzzing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index f7f696d6ea2..f6497588ff2 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3702,8 +3702,8 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) { return builder.makeArrayNew(type.getHeapType(), count, init); } case HeapTypeKind::Cont: { - auto funcType = Type(heapType.getContinuation().type, NonNullable); - return builder.makeContNew(heapType, makeRefFuncConst(funcType)); + auto funcType = heapType.getContinuation().type; + return builder.makeContNew(heapType, makeTrappingRefUse(funcType)); } case HeapTypeKind::Basic: break; From 8293638a027a52814c056d001b8a299fe58ad489 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 18 Aug 2025 09:43:13 -0700 Subject: [PATCH 64/69] fix --- src/wasm-interpreter.h | 6 +++++- test/spec/cont.wast | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 866075c6d4a..56a9df9d1de 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4824,7 +4824,11 @@ class ModuleRunnerBase : public ExpressionRunner { return funcFlow; } // Create a new continuation for the target function. - Name funcName = funcFlow.getSingleValue().getFunc(); + auto funcValue = funcFlow.getSingleValue(); + if (funcValue.isNull()) { + trap("null ref"); + } + auto funcName = funcValue.getFunc(); auto* func = self()->getModule()->getFunction(funcName); return Literal(std::make_shared( self()->makeFuncData(func->name, func->type), curr->type.getHeapType())); diff --git a/test/spec/cont.wast b/test/spec/cont.wast index 9e67a2dcd6a..8bfaf83c4d9 100644 --- a/test/spec/cont.wast +++ b/test/spec/cont.wast @@ -117,11 +117,17 @@ (call $nl4 (cont.new $k1 (ref.func $r1))) ) - (func (export "null") + (func (export "null-resume") (resume $k1 (ref.null $k1) ) ) + + (func (export "null-new") (result (ref null $k1)) + (cont.new $k1 + (ref.null $f1) + ) + ) ) (assert_suspension (invoke "unhandled-1") "unhandled") @@ -138,7 +144,8 @@ (assert_trap (invoke "non-linear-3") "continuation already consumed") (assert_trap (invoke "non-linear-4") "continuation already consumed") -(assert_trap (invoke "null") "null continuation reference") +(assert_trap (invoke "null-resume") "null continuation reference") +(assert_trap (invoke "null-new") "null function reference") (assert_invalid (module From 0d3b16a6cc06a3f4e187566d149b981f02e5c93a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 18 Aug 2025 09:43:26 -0700 Subject: [PATCH 65/69] Revert "fix" This reverts commit 8293638a027a52814c056d001b8a299fe58ad489. --- src/wasm-interpreter.h | 6 +----- test/spec/cont.wast | 11 ++--------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 56a9df9d1de..866075c6d4a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4824,11 +4824,7 @@ class ModuleRunnerBase : public ExpressionRunner { return funcFlow; } // Create a new continuation for the target function. - auto funcValue = funcFlow.getSingleValue(); - if (funcValue.isNull()) { - trap("null ref"); - } - auto funcName = funcValue.getFunc(); + Name funcName = funcFlow.getSingleValue().getFunc(); auto* func = self()->getModule()->getFunction(funcName); return Literal(std::make_shared( self()->makeFuncData(func->name, func->type), curr->type.getHeapType())); diff --git a/test/spec/cont.wast b/test/spec/cont.wast index 8bfaf83c4d9..9e67a2dcd6a 100644 --- a/test/spec/cont.wast +++ b/test/spec/cont.wast @@ -117,17 +117,11 @@ (call $nl4 (cont.new $k1 (ref.func $r1))) ) - (func (export "null-resume") + (func (export "null") (resume $k1 (ref.null $k1) ) ) - - (func (export "null-new") (result (ref null $k1)) - (cont.new $k1 - (ref.null $f1) - ) - ) ) (assert_suspension (invoke "unhandled-1") "unhandled") @@ -144,8 +138,7 @@ (assert_trap (invoke "non-linear-3") "continuation already consumed") (assert_trap (invoke "non-linear-4") "continuation already consumed") -(assert_trap (invoke "null-resume") "null continuation reference") -(assert_trap (invoke "null-new") "null function reference") +(assert_trap (invoke "null") "null continuation reference") (assert_invalid (module From 64ab85164add01fb13b61c199b9b42a14fb57b1b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 18 Aug 2025 12:37:36 -0700 Subject: [PATCH 66/69] fix logic bug with ||,&& when deciding to emit a null. only emit a null when nullable.. --- src/tools/fuzzing/fuzzing.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index f6497588ff2..8d2db8fd4f8 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3470,7 +3470,8 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { return makeRefFuncConst(type); } case HeapType::cont: { - if (type.isNullable() || oneIn(4)) { + // Most of the time, avoid null continuations, as they will trap. + if (type.isNullable() && oneIn(4)) { return builder.makeRefNull(HeapTypes::cont.getBasic(share)); } // Emit the simplest possible continuation. From 9b8f59a62194155a0b5a710b6195ccf0c2a67df2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 18 Aug 2025 13:29:45 -0700 Subject: [PATCH 67/69] Improve logic for avoiding cont.new in globals --- src/tools/fuzzing/fuzzing.cpp | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 8d2db8fd4f8..f262fb0c797 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -616,17 +616,6 @@ void TranslateToFuzzReader::setupTables() { } } -static bool canCreateContentWithoutFunctionScope(Type type) { - for (auto t : type) { - if (t.isContinuation()) { - // There is no way to make a continuation in a global. TODO: We could - // allow null ones, at least, that are always set to null. - return false; - } - } - return true; -} - void TranslateToFuzzReader::setupGlobals() { // If there were initial wasm contents, there may be imported globals. That // would be a problem in the fuzzer harness as we'd error if we do not @@ -685,9 +674,6 @@ void TranslateToFuzzReader::setupGlobals() { // Create new random globals. for (size_t index = upTo(fuzzParams->MAX_GLOBALS); index > 0; --index) { auto type = getConcreteType(); - if (!canCreateContentWithoutFunctionScope(type)) { - continue; - } // Prefer immutable ones as they can be used in global.gets in other // globals, for more interesting patterns. @@ -697,12 +683,16 @@ void TranslateToFuzzReader::setupGlobals() { // initializer. auto* init = makeTrivial(type); - if (!FindAll(init).list.empty()) { + if (!FindAll(init).list.empty() || + !FindAll(init).list.empty()) { // When creating this initial value we ended up emitting a RefAs, which // means we had to stop in the middle of an overly-nested struct or array, // which we can break out of using ref.as_non_null of a nullable ref. That // traps in normal code, which is bad enough, but it does not even // validate in a global. Switch to something safe instead. + // + // Likewise, if we see cont.new, we must switch as well. That can happen + // if a nested struct we create has a continuation field, for example. type = getMVPType(); init = makeConst(type); } else if (type.isTuple() && !init->is()) { From 1155b7f2bbc70410100073663aac4af1be8a29e8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 19 Aug 2025 07:35:15 -0700 Subject: [PATCH 68/69] skip fuzzing a test with switch --- scripts/test/fuzzing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index d791f594409..66af65c6427 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -93,6 +93,7 @@ # TODO: fuzzer support for remaining stack switching instructions: switch, # cont.bind 'cont.wast', + 'precompute-stack-switching.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', From 5591ad63c2eefb3aaa05979b3cb2c0a33b3b11f5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 21 Aug 2025 07:52:44 -0700 Subject: [PATCH 69/69] more skipping --- scripts/test/fuzzing.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index e2e0cbe40ab..8f1342ac459 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -95,6 +95,9 @@ 'cont.wast', 'precompute-stack-switching.wast', 'coalesce-locals-stack-switching.wast', + 'stack_switching_switch_2.wast', + 'stack_switching_switch.wast', + 'unsubtyping-stack-switching.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast',