Skip to content

[Stack Switching] Fuzz Stack Switching #7834

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 97 commits into from
Aug 22, 2025
Merged
Show file tree
Hide file tree
Changes from 85 commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
d20a437
Add final ss tests
kripken Aug 12, 2025
8f1bed1
go
kripken Aug 12, 2025
f988d9f
fix
kripken Aug 12, 2025
e719298
Merge remote-tracking branch 'myself/cont.test' into fuzz.cont
kripken Aug 12, 2025
a94d31a
fix
kripken Aug 12, 2025
3f29688
fix
kripken Aug 12, 2025
058d05e
fuzz
kripken Aug 12, 2025
9944360
fuzz
kripken Aug 12, 2025
3a47f99
fix
kripken Aug 12, 2025
23f5846
Merge remote-tracking branch 'origin/main' into fuzz.cont
kripken Aug 12, 2025
c76f0a7
fix
kripken Aug 12, 2025
7f3630b
work
kripken Aug 12, 2025
3e57bcd
work
kripken Aug 12, 2025
252fae2
fix
kripken Aug 12, 2025
8eefbab
work
kripken Aug 12, 2025
f8789f2
Merge remote-tracking branch 'origin/main' into fuzz.cont
kripken Aug 13, 2025
943aedc
start
kripken Aug 13, 2025
444d8e0
undo
kripken Aug 13, 2025
689114f
Merge remote-tracking branch 'origin/main' into cont.gufa
kripken Aug 13, 2025
922fe8c
fix
kripken Aug 13, 2025
33171c1
Revert "undo"
kripken Aug 13, 2025
fc66e42
format
kripken Aug 13, 2025
8e24d91
work
kripken Aug 13, 2025
797e137
fix
kripken Aug 13, 2025
e367a42
Merge branch 'cont.gufa' into cont.gufa.2
kripken Aug 13, 2025
add4a52
fix
kripken Aug 13, 2025
bb77eba
test
kripken Aug 13, 2025
ed9d5e3
Merge remote-tracking branch 'myself/cont.gufa.2' into fuzz.cont
kripken Aug 13, 2025
149ec27
fix
kripken Aug 13, 2025
c6fd413
test
kripken Aug 13, 2025
e414980
Merge branch 'cont.clear' into fuzz.cont
kripken Aug 13, 2025
612cf96
work
kripken Aug 13, 2025
1d4532a
fix
kripken Aug 13, 2025
169f679
fix
kripken Aug 13, 2025
605620e
fix
kripken Aug 13, 2025
e403a23
fix
kripken Aug 13, 2025
42da090
fix
kripken Aug 13, 2025
2a50223
merge
kripken Aug 13, 2025
8bc214d
work
kripken Aug 13, 2025
124c33f
sinpl
kripken Aug 13, 2025
bb42ddb
Merge remote-tracking branch 'origin/main' into fuzz.cont
kripken Aug 13, 2025
2659042
Merge remote-tracking branch 'myself/cont.export' into fuzz.cont
kripken Aug 13, 2025
643142f
fix
kripken Aug 14, 2025
8d121da
work
kripken Aug 14, 2025
a4959f4
fix
kripken Aug 14, 2025
2513194
fix
kripken Aug 14, 2025
9d4cc20
fix
kripken Aug 14, 2025
997579e
fix
kripken Aug 14, 2025
9cc2c7b
Merge remote-tracking branch 'myself/cont.no-gc' into fuzz.cont
kripken Aug 14, 2025
1759b03
bettr
kripken Aug 14, 2025
45585fe
Merge remote-tracking branch 'myself/cont.no-gc' into fuzz.cont
kripken Aug 14, 2025
eb67fac
fix?
kripken Aug 14, 2025
135c252
work
kripken Aug 14, 2025
f02650b
wo
kripken Aug 14, 2025
96b2130
work
kripken Aug 14, 2025
b523289
test
kripken Aug 14, 2025
d5f8cc5
Merge remote-tracking branch 'myself/cont.export.2' into fuzz.cont
kripken Aug 14, 2025
ceb4338
fix
kripken Aug 15, 2025
9daca9d
Merge remote-tracking branch 'myself/no.gc.no.cont' into fuzz.cont
kripken Aug 15, 2025
f1dd340
undo
kripken Aug 15, 2025
c963d6a
Merge remote-tracking branch 'origin/main' into fuzz.cont
kripken Aug 15, 2025
c5b2e3f
work
kripken Aug 15, 2025
ff8d7b1
Merge remote-tracking branch 'myself/tm.cont' into fuzz.cont
kripken Aug 15, 2025
38df246
test
kripken Aug 15, 2025
efdb9a7
skip
kripken Aug 15, 2025
8360dfc
Update test/lit/passes/type-merging-cont.wast
kripken Aug 15, 2025
7c81b78
Merge remote-tracking branch 'myself/tm.cont' into fuzz.cont
kripken Aug 15, 2025
1b987a8
Merge remote-tracking branch 'origin/main' into fuzz.cont
kripken Aug 15, 2025
e92d00e
fix
kripken Aug 15, 2025
4c2c02a
work
kripken Aug 15, 2025
f7f032b
form
kripken Aug 15, 2025
bc29e68
form
kripken Aug 15, 2025
b0b9733
form
kripken Aug 15, 2025
afe5df2
fix
kripken Aug 15, 2025
eeb8894
Merge remote-tracking branch 'myself/fuzz.less' into fuzz.cont
kripken Aug 15, 2025
24bea1a
Merge remote-tracking branch 'origin/main' into fuzz.cont
kripken Aug 15, 2025
5808c64
update
kripken Aug 15, 2025
64075e0
Merge remote-tracking branch 'myself/tm.cont' into fuzz.cont
kripken Aug 15, 2025
41cf9b6
fix
kripken Aug 15, 2025
b594c26
Merge remote-tracking branch 'myself/cont.null' into fuzz.cont
kripken Aug 15, 2025
51257eb
Merge remote-tracking branch 'origin/main' into fuzz.cont
kripken Aug 15, 2025
00fc791
undo
kripken Aug 15, 2025
575ba68
fix
kripken Aug 15, 2025
45aa8d4
undo
kripken Aug 15, 2025
6e0b0b2
update
kripken Aug 15, 2025
0512f00
Merge remote-tracking branch 'origin/main' into fuzz.cont
kripken Aug 15, 2025
090814f
feedback
kripken Aug 15, 2025
8293638
fix
kripken Aug 18, 2025
0d3b16a
Revert "fix"
kripken Aug 18, 2025
64ab851
fix logic bug with ||,&& when deciding to emit a null. only emit a nu…
kripken Aug 18, 2025
9b8f59a
Improve logic for avoiding cont.new in globals
kripken Aug 18, 2025
1155b7f
skip fuzzing a test with switch
kripken Aug 19, 2025
7abbee0
fix
kripken Aug 19, 2025
2243de7
Merge remote-tracking branch 'origin/main' into fuzz.cont
kripken Aug 19, 2025
1204577
Merge remote-tracking branch 'origin/main' into fuzz.cont
kripken Aug 20, 2025
a07976f
Merge remote-tracking branch 'origin/main' into fuzz.cont
kripken Aug 21, 2025
5591ad6
more skipping
kripken Aug 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/bundle_clusterfuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
'--disable-shared-everything',
'--disable-fp16',
'--disable-strings',
'--disable-stack-switching',
]

with tarfile.open(output_file, "w:gz") as tar:
Expand Down
1 change: 1 addition & 0 deletions scripts/clusterfuzz/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
'--disable-shared-everything',
'--disable-fp16',
'--disable-strings',
'--disable-stack-switching',
]


Expand Down
13 changes: 8 additions & 5 deletions scripts/fuzz_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1624,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.
Expand Down Expand Up @@ -1832,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.
Expand Down
30 changes: 2 additions & 28 deletions scripts/test/fuzzing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -96,29 +90,9 @@
# 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',
# TODO: fuzzer support for remaining stack switching instructions: switch,
# cont.bind
'cont.wast',
'cont_simple.wast',
'gufa-cont.wast',
'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',
Expand Down
3 changes: 3 additions & 0 deletions src/tools/fuzzing.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@ class TranslateToFuzzReader {
// All arrays that are mutable.
std::vector<HeapType> mutableArrays;

// All tags that are valid as exception tags (which cannot have results).
std::vector<Tag*> exceptionTags;

Index numAddedFunctions = 0;

// The name of an empty tag.
Expand Down
52 changes: 41 additions & 11 deletions src/tools/fuzzing/fuzzing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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");
}
Expand Down Expand Up @@ -614,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
Expand Down Expand Up @@ -672,6 +685,10 @@ 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.
auto mutability = oneIn(3) ? Builder::Mutable : Builder::Immutable;
Expand Down Expand Up @@ -708,6 +725,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.
Expand Down Expand Up @@ -736,7 +756,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() {
Expand Down Expand Up @@ -2432,10 +2453,10 @@ Expression* TranslateToFuzzReader::makeTry(Type type) {
auto numTags = upTo(fuzzParams->MAX_TRY_CATCHES);
std::unordered_set<Tag*> 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);
if (usedTags.count(tag)) {
continue;
}
Expand Down Expand Up @@ -2482,7 +2503,7 @@ Expression* TranslateToFuzzReader::makeTryTable(Type type) {
return builder.makeTryTable(body, {}, {}, {});
}

if (wasm.tags.empty()) {
if (exceptionTags.empty()) {
addTag();
}

Expand All @@ -2497,7 +2518,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 {
Expand Down Expand Up @@ -3449,7 +3470,14 @@ 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 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
Expand Down Expand Up @@ -3673,8 +3701,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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto funcType = Type(heapType.getContinuation().type, NonNullable);
auto funcType = Type(heapType.getContinuation().type, Nullable);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nulls would just trap. But I guess we should cover that too. I can change this to use nulls rarely like we do elsewhere.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return builder.makeContNew(heapType, makeRefFuncConst(funcType));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not allow this to be an arbitrary expression with the expected type?

Copy link
Member Author

@kripken kripken Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yeah, I don't think it could recurse in a bad way. I'll try that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
case HeapTypeKind::Basic:
break;
}
Expand Down Expand Up @@ -5222,10 +5252,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<Expression*> operands;
Expand Down
86 changes: 43 additions & 43 deletions test/passes/translate-to-fuzz_all-features_metrics_noprint.txt
Original file line number Diff line number Diff line change
@@ -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
Loading