From 2cd5ca17f82ab7484883da8d552e9eed8ee0ea07 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 27 Jan 2021 14:18:33 -0800 Subject: [PATCH 01/29] Remove test assumption of minify_check roundtripping perfectly --- scripts/test/shared.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/scripts/test/shared.py b/scripts/test/shared.py index f782bb849eb..0996da55478 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -493,18 +493,8 @@ def minify_check(wast, verify_final_result=True): print(' ', ' '.join(cmd)) subprocess.check_call(cmd, stdout=open('a.wast', 'w'), stderr=subprocess.PIPE) assert os.path.exists('a.wast') - subprocess.check_call(WASM_OPT + ['a.wast', '--print-minified', '-all'], + subprocess.check_call(WASM_OPT + ['a.wast', '-all'], stdout=open('b.wast', 'w'), stderr=subprocess.PIPE) - assert os.path.exists('b.wast') - if verify_final_result: - expected = open('a.wast').read() - actual = open('b.wast').read() - if actual != expected: - fail(actual, expected) - if os.path.exists('a.wast'): - os.unlink('a.wast') - if os.path.exists('b.wast'): - os.unlink('b.wast') # run a check with BINARYEN_PASS_DEBUG set, to do full validation From a6342a6eb0508d377f4a9cc64db13f33b13b899f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 27 Jan 2021 15:03:21 -0800 Subject: [PATCH 02/29] better --- scripts/test/shared.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 0996da55478..4ca067262c1 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -492,9 +492,8 @@ def minify_check(wast, verify_final_result=True): cmd = WASM_OPT + [wast, '--print-minified', '-all'] print(' ', ' '.join(cmd)) subprocess.check_call(cmd, stdout=open('a.wast', 'w'), stderr=subprocess.PIPE) - assert os.path.exists('a.wast') subprocess.check_call(WASM_OPT + ['a.wast', '-all'], - stdout=open('b.wast', 'w'), stderr=subprocess.PIPE) + stderr=subprocess.PIPE, stderr=subprocess.PIPE) # run a check with BINARYEN_PASS_DEBUG set, to do full validation From d3e8b4d8212db2997af0664c62dd691f5f5ef826 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 27 Jan 2021 15:03:33 -0800 Subject: [PATCH 03/29] better --- scripts/test/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 4ca067262c1..670d41d3098 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -493,7 +493,7 @@ def minify_check(wast, verify_final_result=True): print(' ', ' '.join(cmd)) subprocess.check_call(cmd, stdout=open('a.wast', 'w'), stderr=subprocess.PIPE) subprocess.check_call(WASM_OPT + ['a.wast', '-all'], - stderr=subprocess.PIPE, stderr=subprocess.PIPE) + stdout=subprocess.PIPE, stderr=subprocess.PIPE) # run a check with BINARYEN_PASS_DEBUG set, to do full validation From 067f2be887dc146dcf78cc97feeaf0c9e9c644e9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 23 Sep 2025 11:31:18 -0700 Subject: [PATCH 04/29] start --- src/tools/fuzzing/fuzzing.cpp | 49 ++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 30ea252a372..d549ed25865 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1719,31 +1719,32 @@ void TranslateToFuzzReader::mutate(Function* func) { // reasonable chance of making some changes. percentChance = std::max(percentChance, Index(3)); - struct Modder : public PostWalker> { - TranslateToFuzzReader& parent; + struct Modder + : public ExpressionStackWalker> { + TranslateToFuzzReader& fuzzer; Index percentChance; // Whether to replace with unreachable. This can lead to less code getting // executed, so we don't want to do it all the time even in a big function. bool allowUnreachable; - Modder(TranslateToFuzzReader& parent, Index percentChance) - : parent(parent), percentChance(percentChance) { + Modder(TranslateToFuzzReader& fuzzer, Index percentChance) + : fuzzer(fuzzer), percentChance(percentChance) { // If the parent allows it then sometimes replace with an unreachable, and // sometimes not. Even if we allow it, only do it in certain functions // (half the time) and only do it rarely (see below). - allowUnreachable = parent.allowAddingUnreachableCode && parent.oneIn(2); + allowUnreachable = fuzzer.allowAddingUnreachableCode && fuzzer.oneIn(2); } void visitExpression(Expression* curr) { - if (parent.upTo(100) < percentChance && - parent.canBeArbitrarilyReplaced(curr)) { + if (fuzzer.upTo(100) < percentChance && + fuzzer.canBeArbitrarilyReplaced(curr)) { // We can replace in various modes, see below. Generate a random number // up to 100 to help us there. - int mode = parent.upTo(100); + int mode = fuzzer.upTo(100); if (allowUnreachable && mode < 5) { - replaceCurrent(parent.make(Type::unreachable)); + replaceCurrent(fuzzer.make(Type::unreachable)); return; } @@ -1752,14 +1753,32 @@ void TranslateToFuzzReader::mutate(Function* func) { // not, changing an offset, etc. if (auto* c = curr->dynCast()) { if (mode < 50) { - c->value = parent.tweak(c->value); + c->value = fuzzer.tweak(c->value); } else { // Just replace the entire thing. - replaceCurrent(parent.make(curr->type)); + replaceCurrent(fuzzer.make(curr->type)); } return; } + // We normally replace with the same type (or subtype). + auto replacementType = curr->type; + + // ref.cast is interesting in that we can replace the reference with + // anything of the same hierarchy. That is, we do not need to limit + // ourselves to subtypes of the current type, but can also allow + // supertypes. + if (auto* parent = getParent()) { + if (auto* parentCast = parent->dynCast()) { + // We are the child of a cast. Check we are the right one, and do + // not do this often. + if (parentCast->ref == curr && (mode & 8) && curr->type.isRef()) { + auto heapType = curr->type.getHeapType(); + replacementType = curr->type.with(heapType.getTop()); + } + } + } + // Generate a replacement for the expression, and by default replace all // of |curr| (including children) with that replacement, but in some // cases we can do more subtle things. @@ -1768,7 +1787,7 @@ void TranslateToFuzzReader::mutate(Function* func) { // labels, but we'll fix that up later. Note also that make() picks a // subtype, so this has a chance to replace us with anything that is // valid to put here. - auto* rep = parent.make(curr->type); + auto* rep = fuzzer.make(replacementType); if (mode < 33 && rep->type != Type::none) { // This has a non-none type. Replace the output, keeping the // expression and its children in a drop. This "interposes" between @@ -1816,7 +1835,7 @@ void TranslateToFuzzReader::mutate(Function* func) { } else { // Drop curr and append. rep = - parent.builder.makeSequence(parent.builder.makeDrop(curr), rep); + fuzzer.builder.makeSequence(fuzzer.builder.makeDrop(curr), rep); } } else if (mode >= 66 && !Properties::isControlFlowStructure(curr)) { ChildIterator children(curr); @@ -1841,13 +1860,13 @@ void TranslateToFuzzReader::mutate(Function* func) { // (NEW) // ) // - auto* block = parent.builder.makeBlock(); + auto* block = fuzzer.builder.makeBlock(); for (auto* child : children) { // Only drop the child if we can't replace it as one of NEW's // children. This does a linear scan of |rep| which is the reason // for the above limit on the number of children. if (!replaceChildWith(rep, child)) { - block->list.push_back(parent.builder.makeDrop(child)); + block->list.push_back(fuzzer.builder.makeDrop(child)); } } From bffcb99392f1584215fd61a5bac718cd5a3a9fd0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 23 Sep 2025 16:19:42 -0700 Subject: [PATCH 05/29] 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 d549ed25865..2247804b40c 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1768,7 +1768,7 @@ void TranslateToFuzzReader::mutate(Function* func) { // anything of the same hierarchy. That is, we do not need to limit // ourselves to subtypes of the current type, but can also allow // supertypes. - if (auto* parent = getParent()) { + if (auto* parent = getParent()) { XXX use subtypingdiscoverer if (auto* parentCast = parent->dynCast()) { // We are the child of a cast. Check we are the right one, and do // not do this often. From 4e47febf3539e2d1fae753d1551c8f218299b1b8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 24 Sep 2025 16:40:46 -0700 Subject: [PATCH 06/29] undo --- src/tools/fuzzing/fuzzing.cpp | 49 +++++++++++------------------------ 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index bf1ff3ddf4f..6954d8cf029 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1733,32 +1733,31 @@ void TranslateToFuzzReader::mutate(Function* func) { // reasonable chance of making some changes. percentChance = std::max(percentChance, Index(3)); - struct Modder - : public ExpressionStackWalker> { - TranslateToFuzzReader& fuzzer; + struct Modder : public PostWalker> { + TranslateToFuzzReader& parent; Index percentChance; // Whether to replace with unreachable. This can lead to less code getting // executed, so we don't want to do it all the time even in a big function. bool allowUnreachable; - Modder(TranslateToFuzzReader& fuzzer, Index percentChance) - : fuzzer(fuzzer), percentChance(percentChance) { + Modder(TranslateToFuzzReader& parent, Index percentChance) + : parent(parent), percentChance(percentChance) { // If the parent allows it then sometimes replace with an unreachable, and // sometimes not. Even if we allow it, only do it in certain functions // (half the time) and only do it rarely (see below). - allowUnreachable = fuzzer.allowAddingUnreachableCode && fuzzer.oneIn(2); + allowUnreachable = parent.allowAddingUnreachableCode && parent.oneIn(2); } void visitExpression(Expression* curr) { - if (fuzzer.upTo(100) < percentChance && - fuzzer.canBeArbitrarilyReplaced(curr)) { + if (parent.upTo(100) < percentChance && + parent.canBeArbitrarilyReplaced(curr)) { // We can replace in various modes, see below. Generate a random number // up to 100 to help us there. - int mode = fuzzer.upTo(100); + int mode = parent.upTo(100); if (allowUnreachable && mode < 5) { - replaceCurrent(fuzzer.make(Type::unreachable)); + replaceCurrent(parent.make(Type::unreachable)); return; } @@ -1767,32 +1766,14 @@ void TranslateToFuzzReader::mutate(Function* func) { // not, changing an offset, etc. if (auto* c = curr->dynCast()) { if (mode < 50) { - c->value = fuzzer.tweak(c->value); + c->value = parent.tweak(c->value); } else { // Just replace the entire thing. - replaceCurrent(fuzzer.make(curr->type)); + replaceCurrent(parent.make(curr->type)); } return; } - // We normally replace with the same type (or subtype). - auto replacementType = curr->type; - - // ref.cast is interesting in that we can replace the reference with - // anything of the same hierarchy. That is, we do not need to limit - // ourselves to subtypes of the current type, but can also allow - // supertypes. - if (auto* parent = getParent()) { XXX use subtypingdiscoverer - if (auto* parentCast = parent->dynCast()) { - // We are the child of a cast. Check we are the right one, and do - // not do this often. - if (parentCast->ref == curr && (mode & 8) && curr->type.isRef()) { - auto heapType = curr->type.getHeapType(); - replacementType = curr->type.with(heapType.getTop()); - } - } - } - // Generate a replacement for the expression, and by default replace all // of |curr| (including children) with that replacement, but in some // cases we can do more subtle things. @@ -1801,7 +1782,7 @@ void TranslateToFuzzReader::mutate(Function* func) { // labels, but we'll fix that up later. Note also that make() picks a // subtype, so this has a chance to replace us with anything that is // valid to put here. - auto* rep = fuzzer.make(replacementType); + auto* rep = parent.make(curr->type); if (mode < 33 && rep->type != Type::none) { // This has a non-none type. Replace the output, keeping the // expression and its children in a drop. This "interposes" between @@ -1849,7 +1830,7 @@ void TranslateToFuzzReader::mutate(Function* func) { } else { // Drop curr and append. rep = - fuzzer.builder.makeSequence(fuzzer.builder.makeDrop(curr), rep); + parent.builder.makeSequence(parent.builder.makeDrop(curr), rep); } } else if (mode >= 66 && !Properties::isControlFlowStructure(curr)) { ChildIterator children(curr); @@ -1874,13 +1855,13 @@ void TranslateToFuzzReader::mutate(Function* func) { // (NEW) // ) // - auto* block = fuzzer.builder.makeBlock(); + auto* block = parent.builder.makeBlock(); for (auto* child : children) { // Only drop the child if we can't replace it as one of NEW's // children. This does a linear scan of |rep| which is the reason // for the above limit on the number of children. if (!replaceChildWith(rep, child)) { - block->list.push_back(fuzzer.builder.makeDrop(child)); + block->list.push_back(parent.builder.makeDrop(child)); } } From 5357f5e70824d8a11b467bdff559a95c9780317a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 24 Sep 2025 16:51:28 -0700 Subject: [PATCH 07/29] go --- src/tools/fuzzing/fuzzing.cpp | 274 ++++++++++++++++++++-------------- 1 file changed, 162 insertions(+), 112 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 6954d8cf029..56fa07885f1 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -19,6 +19,7 @@ #include "ir/iteration.h" #include "ir/local-structural-dominance.h" #include "ir/module-utils.h" +#include "ir/subtype-exprs.h" #include "ir/subtypes.h" #include "ir/type-updating.h" #include "support/string.h" @@ -1749,133 +1750,182 @@ void TranslateToFuzzReader::mutate(Function* func) { allowUnreachable = parent.allowAddingUnreachableCode && parent.oneIn(2); } + void visitFunction(Function* curr) { + // TODO: replace the toplevel with very low chance + } + void visitExpression(Expression* curr) { - if (parent.upTo(100) < percentChance && - parent.canBeArbitrarilyReplaced(curr)) { - // We can replace in various modes, see below. Generate a random number - // up to 100 to help us there. - int mode = parent.upTo(100); - - if (allowUnreachable && mode < 5) { - replaceCurrent(parent.make(Type::unreachable)); - return; + // Replace some children. Find the types we can replace them with: usually + // a subtype of themselves, but sometimes we can do something less + // refined. + struct Collector + : ControlFlowWalker> { + + // Maps children to the types we can replace them with. + std::unordered_map childTypes; + + // We only care about constraints on Expression* things. + void noteSubtype(Type sub, Type super) {} + void noteSubtype(HeapType sub, HeapType super) {} + + void noteSubtype(Type sub, Expression* super) { + // The expression must be a supertype of a fixed type. Nothing to do. + } + void noteSubtype(Expression* sub, Type super) { + // The expression must be a subtype of a fixed type: note it. + childTypes[sub] = super; + } + void noteSubtype(Expression* sub, Expression* super) { + // The expression must be a subtype of another expression: note it. + childTypes[sub] = super->type; + } + void noteNonFlowSubtype(Expression* sub, Type super) { + childTypes[sub] = super->type; } + void noteCast(HeapType src, HeapType dst) {} + void noteCast(Expression* src, Type dst) {} + void noteCast(Expression* src, Expression* dst) {} + } collector; + collector.visit(curr); + + for (auto*& child : ChildIterator(curr)) { + if (!child->type.isRef() || + !parent.canBeArbitrarilyReplaced(curr) || + parent.upTo(100) >= percentChance) { + continue; + } + auto type = collector.childTypes[child]; + if (type == Type::none) { + // No constraint: We can use the top type. + type = child->type.with(child->type.getHeapType().getTop()).with(Nullable); + } + child = getReplacement(child); + } + } - // For constants, perform only a small tweaking in some cases. - // TODO: more minor tweaks to immediates, like making a load atomic or - // not, changing an offset, etc. - if (auto* c = curr->dynCast()) { - if (mode < 50) { - c->value = parent.tweak(c->value); - } else { - // Just replace the entire thing. - replaceCurrent(parent.make(curr->type)); - } - return; + Expression* getReplacement(Expression* curr) { + // We can replace in various modes, see below. Generate a random number + // up to 100 to help us there. + int mode = parent.upTo(100); + + if (allowUnreachable && mode < 5) { + return parent.make(Type::unreachable); + } + + // For constants, perform only a small tweaking in some cases. + // TODO: more minor tweaks to immediates, like making a load atomic or + // not, changing an offset, etc. + if (auto* c = curr->dynCast()) { + if (mode < 50) { + c->value = parent.tweak(c->value); + return c; + } else { + // Just replace the entire thing. + return parent.make(curr->type); } + } - // Generate a replacement for the expression, and by default replace all - // of |curr| (including children) with that replacement, but in some - // cases we can do more subtle things. + // Generate a replacement for the expression, and by default replace all + // of |curr| (including children) with that replacement, but in some + // cases we can do more subtle things. + // + // Note that such a replacement is not always valid due to nesting of + // labels, but we'll fix that up later. Note also that make() picks a + // subtype, so this has a chance to replace us with anything that is + // valid to put here. + auto* rep = parent.make(curr->type); + if (mode < 33 && rep->type != Type::none) { + // This has a non-none type. Replace the output, keeping the + // expression and its children in a drop. This "interposes" between + // this expression and its parent, something like this: // - // Note that such a replacement is not always valid due to nesting of - // labels, but we'll fix that up later. Note also that make() picks a - // subtype, so this has a chance to replace us with anything that is - // valid to put here. - auto* rep = parent.make(curr->type); - if (mode < 33 && rep->type != Type::none) { - // This has a non-none type. Replace the output, keeping the - // expression and its children in a drop. This "interposes" between - // this expression and its parent, something like this: - // - // (D - // (A - // (B) - // (C) - // ) - // ) - //// - // => ;; keep A, replace it in the parent + // (D + // (A + // (B) + // (C) + // ) + // ) + //// + // => ;; keep A, replace it in the parent + // + // (D + // (block + // (drop + // (A + // (B) + // (C) + // ) + // ) + // (NEW) + // ) + // ) + // + // We also sometimes try to insert A as a child of NEW, so we actually + // interpose directly: + // + // (D + // (NEW + // (A + // (B) + // (C) + // ) + // ) + // ) + // + // We do not do that all the time, as inserting a drop is actually an + // important situation to test: the drop makes the output of A unused, + // which may let optimizations remove it. + if ((mode & 1) && replaceChildWith(rep, curr)) { + // We managed to replace one of the children with curr, and have + // nothing more to do. + } else { + // Drop curr and append. + rep = + parent.builder.makeSequence(parent.builder.makeDrop(curr), rep); + } + } else if (mode >= 66 && !Properties::isControlFlowStructure(curr)) { + ChildIterator children(curr); + auto numChildren = children.getNumChildren(); + if (numChildren > 0 && numChildren < 5) { + // This is a normal (non-control-flow) expression with at least one + // child (and not an excessive amount of them; see the processing + // below). "Interpose" between the children and this expression by + // keeping them and replacing the parent |curr|. We do this by + // generating drops of the children, like this: // - // (D - // (block - // (drop - // (A - // (B) - // (C) - // ) - // ) - // (NEW) - // ) - // ) + // (A + // (B) + // (C) + // ) // - // We also sometimes try to insert A as a child of NEW, so we actually - // interpose directly: + // => ;; keep children, replace A // - // (D - // (NEW - // (A - // (B) - // (C) - // ) - // ) - // ) + // (block + // (drop (B)) + // (drop (C)) + // (NEW) + // ) // - // We do not do that all the time, as inserting a drop is actually an - // important situation to test: the drop makes the output of A unused, - // which may let optimizations remove it. - if ((mode & 1) && replaceChildWith(rep, curr)) { - // We managed to replace one of the children with curr, and have - // nothing more to do. - } else { - // Drop curr and append. - rep = - parent.builder.makeSequence(parent.builder.makeDrop(curr), rep); - } - } else if (mode >= 66 && !Properties::isControlFlowStructure(curr)) { - ChildIterator children(curr); - auto numChildren = children.getNumChildren(); - if (numChildren > 0 && numChildren < 5) { - // This is a normal (non-control-flow) expression with at least one - // child (and not an excessive amount of them; see the processing - // below). "Interpose" between the children and this expression by - // keeping them and replacing the parent |curr|. We do this by - // generating drops of the children, like this: - // - // (A - // (B) - // (C) - // ) - // - // => ;; keep children, replace A - // - // (block - // (drop (B)) - // (drop (C)) - // (NEW) - // ) - // - auto* block = parent.builder.makeBlock(); - for (auto* child : children) { - // Only drop the child if we can't replace it as one of NEW's - // children. This does a linear scan of |rep| which is the reason - // for the above limit on the number of children. - if (!replaceChildWith(rep, child)) { - block->list.push_back(parent.builder.makeDrop(child)); - } + auto* block = parent.builder.makeBlock(); + for (auto* child : children) { + // Only drop the child if we can't replace it as one of NEW's + // children. This does a linear scan of |rep| which is the reason + // for the above limit on the number of children. + if (!replaceChildWith(rep, child)) { + block->list.push_back(parent.builder.makeDrop(child)); } + } - if (!block->list.empty()) { - // We need the block, that is, we did not find a place for all the - // children. - block->list.push_back(rep); - block->finalize(); - rep = block; - } + if (!block->list.empty()) { + // We need the block, that is, we did not find a place for all the + // children. + block->list.push_back(rep); + block->finalize(); + rep = block; } } - replaceCurrent(rep); } + return rep; } }; From 6bb34686f3045dd4986a40ab210e806cd645d92d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 24 Sep 2025 16:52:36 -0700 Subject: [PATCH 08/29] 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 56fa07885f1..a7f6c465cc3 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1780,7 +1780,7 @@ void TranslateToFuzzReader::mutate(Function* func) { childTypes[sub] = super->type; } void noteNonFlowSubtype(Expression* sub, Type super) { - childTypes[sub] = super->type; + childTypes[sub] = super; } void noteCast(HeapType src, HeapType dst) {} void noteCast(Expression* src, Type dst) {} From 0eb4cea60c48aaefc4364376364e8bb7cb7b5689 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 24 Sep 2025 16:52:42 -0700 Subject: [PATCH 09/29] format --- src/tools/fuzzing/fuzzing.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index a7f6c465cc3..82be1c28b43 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1789,15 +1789,15 @@ void TranslateToFuzzReader::mutate(Function* func) { collector.visit(curr); for (auto*& child : ChildIterator(curr)) { - if (!child->type.isRef() || - !parent.canBeArbitrarilyReplaced(curr) || + if (!child->type.isRef() || !parent.canBeArbitrarilyReplaced(curr) || parent.upTo(100) >= percentChance) { continue; } auto type = collector.childTypes[child]; if (type == Type::none) { // No constraint: We can use the top type. - type = child->type.with(child->type.getHeapType().getTop()).with(Nullable); + type = + child->type.with(child->type.getHeapType().getTop()).with(Nullable); } child = getReplacement(child); } @@ -1880,8 +1880,7 @@ void TranslateToFuzzReader::mutate(Function* func) { // nothing more to do. } else { // Drop curr and append. - rep = - parent.builder.makeSequence(parent.builder.makeDrop(curr), rep); + rep = parent.builder.makeSequence(parent.builder.makeDrop(curr), rep); } } else if (mode >= 66 && !Properties::isControlFlowStructure(curr)) { ChildIterator children(curr); From 5a8400cc024929a73dd6d7f74aeb8a046dc2048e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 24 Sep 2025 16:55:36 -0700 Subject: [PATCH 10/29] go --- src/tools/fuzzing/fuzzing.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 82be1c28b43..04d674ccaf4 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1786,6 +1786,7 @@ void TranslateToFuzzReader::mutate(Function* func) { void noteCast(Expression* src, Type dst) {} void noteCast(Expression* src, Expression* dst) {} } collector; + collector.setFunction(parent.funcContext->func); collector.visit(curr); for (auto*& child : ChildIterator(curr)) { @@ -1799,7 +1800,9 @@ void TranslateToFuzzReader::mutate(Function* func) { type = child->type.with(child->type.getHeapType().getTop()).with(Nullable); } +auto old = child->type; child = getReplacement(child); +assert(Type::isSubType(child->type, old)); } } From 43e22f91a34b741cc56fc549d413e74c73943a84 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 26 Sep 2025 14:04:51 -0700 Subject: [PATCH 11/29] 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 04d674ccaf4..0ad49fc73a7 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1786,6 +1786,7 @@ void TranslateToFuzzReader::mutate(Function* func) { void noteCast(Expression* src, Type dst) {} void noteCast(Expression* src, Expression* dst) {} } collector; + collector.setModule(&parent.wasm); collector.setFunction(parent.funcContext->func); collector.visit(curr); From 2a75a40d39d385588f262db4a50c367fd47b317e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 09:34:19 -0700 Subject: [PATCH 12/29] rewrite --- src/tools/fuzzing/fuzzing.cpp | 102 ++++++++++++++++------------------ 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 0ad49fc73a7..b0b24e6cca9 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1734,80 +1734,76 @@ void TranslateToFuzzReader::mutate(Function* func) { // reasonable chance of making some changes. percentChance = std::max(percentChance, Index(3)); + // First, find things to replace and their types. SubtypingDiscoverer needs to + // do this in a single, full walk (as types of children depend on parents, and + // even block targets). + struct Finder : public PostWalker>> { + // Maps children we can replace to the types we can replace them with. + std::unordered_map childTypes; + + // We only care about constraints on Expression* things. + void noteSubtype(Type sub, Type super) {} + void noteSubtype(HeapType sub, HeapType super) {} + + void noteSubtype(Type sub, Expression* super) { + // The expression must be a supertype of a fixed type. Nothing to do. + } + void noteSubtype(Expression* sub, Type super) { + // This is an opportunity to replace sub with a subtype of a fixed type. + childTypes[sub] = super; + } + void noteSubtype(Expression* sub, Expression* super) { + // The expression must be a subtype of another expression: note it. + noteSubtype(sub, super->type); + } + void noteNonFlowSubtype(Expression* sub, Type super) { + noteSubtype(sub, super); + } + void noteCast(HeapType src, HeapType dst) {} + void noteCast(Expression* src, Type dst) {} + void noteCast(Expression* src, Expression* dst) {} + + // TODO: For things with no constraint, we can use the top type. Maybe + // easier to do below. + } finder; + finder.walkFunctionInModule(func, &wasm); + + // Next, modify things. struct Modder : public PostWalker> { TranslateToFuzzReader& parent; Index percentChance; + Finder& finder; // Whether to replace with unreachable. This can lead to less code getting // executed, so we don't want to do it all the time even in a big function. bool allowUnreachable; - Modder(TranslateToFuzzReader& parent, Index percentChance) - : parent(parent), percentChance(percentChance) { + Modder(TranslateToFuzzReader& parent, Index percentChance, Finder& finder) + : parent(parent), percentChance(percentChance), finder(finder) { // If the parent allows it then sometimes replace with an unreachable, and // sometimes not. Even if we allow it, only do it in certain functions // (half the time) and only do it rarely (see below). allowUnreachable = parent.allowAddingUnreachableCode && parent.oneIn(2); } - void visitFunction(Function* curr) { - // TODO: replace the toplevel with very low chance - } - void visitExpression(Expression* curr) { - // Replace some children. Find the types we can replace them with: usually - // a subtype of themselves, but sometimes we can do something less - // refined. - struct Collector - : ControlFlowWalker> { - - // Maps children to the types we can replace them with. - std::unordered_map childTypes; - - // We only care about constraints on Expression* things. - void noteSubtype(Type sub, Type super) {} - void noteSubtype(HeapType sub, HeapType super) {} + // See if we want to replace it. + if (!parent.canBeArbitrarilyReplaced(curr) || + parent.upTo(100) >= percentChance) { + return; + } - void noteSubtype(Type sub, Expression* super) { - // The expression must be a supertype of a fixed type. Nothing to do. - } - void noteSubtype(Expression* sub, Type super) { - // The expression must be a subtype of a fixed type: note it. - childTypes[sub] = super; - } - void noteSubtype(Expression* sub, Expression* super) { - // The expression must be a subtype of another expression: note it. - childTypes[sub] = super->type; - } - void noteNonFlowSubtype(Expression* sub, Type super) { - childTypes[sub] = super; - } - void noteCast(HeapType src, HeapType dst) {} - void noteCast(Expression* src, Type dst) {} - void noteCast(Expression* src, Expression* dst) {} - } collector; - collector.setModule(&parent.wasm); - collector.setFunction(parent.funcContext->func); - collector.visit(curr); - - for (auto*& child : ChildIterator(curr)) { - if (!child->type.isRef() || !parent.canBeArbitrarilyReplaced(curr) || - parent.upTo(100) >= percentChance) { - continue; - } - auto type = collector.childTypes[child]; + // Find the type to replace with. + auto type = curr->type; + if (type.isRef() { + type = collector.childTypes[child]; if (type == Type::none) { // No constraint: We can use the top type. type = child->type.with(child->type.getHeapType().getTop()).with(Nullable); } -auto old = child->type; - child = getReplacement(child); -assert(Type::isSubType(child->type, old)); } - } - Expression* getReplacement(Expression* curr) { // We can replace in various modes, see below. Generate a random number // up to 100 to help us there. int mode = parent.upTo(100); @@ -1825,7 +1821,7 @@ assert(Type::isSubType(child->type, old)); return c; } else { // Just replace the entire thing. - return parent.make(curr->type); + return parent.make(type); } } @@ -1837,7 +1833,7 @@ assert(Type::isSubType(child->type, old)); // labels, but we'll fix that up later. Note also that make() picks a // subtype, so this has a chance to replace us with anything that is // valid to put here. - auto* rep = parent.make(curr->type); + auto* rep = parent.make(type); if (mode < 33 && rep->type != Type::none) { // This has a non-none type. Replace the output, keeping the // expression and its children in a drop. This "interposes" between From cb3c72736f64b396d024326e7ee23bc935501a0a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 09:39:56 -0700 Subject: [PATCH 13/29] work --- src/tools/fuzzing/fuzzing.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index b0b24e6cca9..02851f407c5 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1795,12 +1795,12 @@ void TranslateToFuzzReader::mutate(Function* func) { // Find the type to replace with. auto type = curr->type; - if (type.isRef() { - type = collector.childTypes[child]; + if (type.isRef()) { + type = finder.childTypes[curr]; if (type == Type::none) { // No constraint: We can use the top type. type = - child->type.with(child->type.getHeapType().getTop()).with(Nullable); + type.with(type.getHeapType().getTop()).with(Nullable); } } From 92f67a44e7f7a9c6adf5ee5203096dc5147bbcc5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 09:41:00 -0700 Subject: [PATCH 14/29] work --- src/tools/fuzzing/fuzzing.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 02851f407c5..754f7153c08 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1809,7 +1809,8 @@ void TranslateToFuzzReader::mutate(Function* func) { int mode = parent.upTo(100); if (allowUnreachable && mode < 5) { - return parent.make(Type::unreachable); + replaceCurrent(parent.make(Type::unreachable)); + return; } // For constants, perform only a small tweaking in some cases. @@ -1818,11 +1819,11 @@ void TranslateToFuzzReader::mutate(Function* func) { if (auto* c = curr->dynCast()) { if (mode < 50) { c->value = parent.tweak(c->value); - return c; } else { // Just replace the entire thing. - return parent.make(type); + replaceCurrent(parent.make(type)); } + return; } // Generate a replacement for the expression, and by default replace all @@ -1924,7 +1925,7 @@ void TranslateToFuzzReader::mutate(Function* func) { } } } - return rep; + replaceCurrent(rep); } }; From d56cc383d04ad4c62d1ae722dbc3d03188815194 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 09:41:14 -0700 Subject: [PATCH 15/29] 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 754f7153c08..5ab43235193 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1929,7 +1929,7 @@ void TranslateToFuzzReader::mutate(Function* func) { } }; - Modder modder(*this, percentChance); + Modder modder(*this, percentChance, finder); modder.walkFunctionInModule(func, &wasm); } From 033246e78dea1683d8f4d99cbafffa8dc28f8824 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 09:54:36 -0700 Subject: [PATCH 16/29] work --- src/tools/fuzzing/fuzzing.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 5ab43235193..73414a28808 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1795,13 +1795,24 @@ void TranslateToFuzzReader::mutate(Function* func) { // Find the type to replace with. auto type = curr->type; +//std::cout << "on " << type << '\n'; if (type.isRef()) { type = finder.childTypes[curr]; if (type == Type::none) { // No constraint: We can use the top type. +//std::cout << " go?\n"; type = - type.with(type.getHeapType().getTop()).with(Nullable); + curr->type.with(curr->type.getHeapType().getTop()).with(Nullable); +std::cout << *curr << " with " << type << " in " << *getFunction() << '\n'; +abort(); } + // We can only be given a less-refined type (certainly we can replace + // curr with its own type). + assert(Type::isSubType(curr->type, type)); +if (type != curr->type) { +std::cout << *curr << " 2with " << type <<" in " << *getFunction() << '\n'; +abort(); +} } // We can replace in various modes, see below. Generate a random number From 671b91bdd060240802a35c73b2f675deaa8baf33 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 10:17:34 -0700 Subject: [PATCH 17/29] work --- src/tools/fuzzing/fuzzing.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 73414a28808..31336bb808a 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1799,12 +1799,9 @@ void TranslateToFuzzReader::mutate(Function* func) { if (type.isRef()) { type = finder.childTypes[curr]; if (type == Type::none) { - // No constraint: We can use the top type. -//std::cout << " go?\n"; - type = - curr->type.with(curr->type.getHeapType().getTop()).with(Nullable); -std::cout << *curr << " with " << type << " in " << *getFunction() << '\n'; -abort(); + // No constraint was reported. This is something SubtypingDiscoverer + // does not fully handle, so assume the worst. + type = curr->type; } // We can only be given a less-refined type (certainly we can replace // curr with its own type). From 3a2329b8cc3f73a75a5ad9f12ecd2620545427d8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 13:17:57 -0700 Subject: [PATCH 18/29] work --- src/tools/fuzzing/fuzzing.cpp | 37 +++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 31336bb808a..1a4a1b48e54 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1737,20 +1737,28 @@ void TranslateToFuzzReader::mutate(Function* func) { // First, find things to replace and their types. SubtypingDiscoverer needs to // do this in a single, full walk (as types of children depend on parents, and // even block targets). - struct Finder : public PostWalker>> { - // Maps children we can replace to the types we can replace them with. + struct Finder : public ControlFlowWalker> { + // Maps children we can replace to the types we can replace them with. We + // only store nontrivial ones (i.e., where the type is not just the child's + // type). std::unordered_map childTypes; // We only care about constraints on Expression* things. - void noteSubtype(Type sub, Type super) {} - void noteSubtype(HeapType sub, HeapType super) {} + void noteSubtype(Type sub, Type super) { + } + void noteSubtype(HeapType sub, HeapType super) { + } void noteSubtype(Type sub, Expression* super) { // The expression must be a supertype of a fixed type. Nothing to do. } void noteSubtype(Expression* sub, Type super) { - // This is an opportunity to replace sub with a subtype of a fixed type. - childTypes[sub] = super; + if (super.isRef() && sub->type != super) { + // This is a nontrivial opportunity to replace sub with a given type. + childTypes[sub] = super; +std::cout << "note " << *sub << " 2with " << super <<" in " << *getFunction() << '\n'; +abort(); + } } void noteSubtype(Expression* sub, Expression* super) { // The expression must be a subtype of another expression: note it. @@ -1759,6 +1767,7 @@ void TranslateToFuzzReader::mutate(Function* func) { void noteNonFlowSubtype(Expression* sub, Type super) { noteSubtype(sub, super); } + // TODO: casts, use top void noteCast(HeapType src, HeapType dst) {} void noteCast(Expression* src, Type dst) {} void noteCast(Expression* src, Expression* dst) {} @@ -1797,19 +1806,17 @@ void TranslateToFuzzReader::mutate(Function* func) { auto type = curr->type; //std::cout << "on " << type << '\n'; if (type.isRef()) { - type = finder.childTypes[curr]; - if (type == Type::none) { - // No constraint was reported. This is something SubtypingDiscoverer - // does not fully handle, so assume the worst. - type = curr->type; - } - // We can only be given a less-refined type (certainly we can replace - // curr with its own type). - assert(Type::isSubType(curr->type, type)); + auto iter = finder.childTypes.find(curr); + if (iter != finder.childTypes.end()) { + type = iter->second; + // We can only be given a less-refined type (certainly we can replace + // curr with its own type). + assert(Type::isSubType(curr->type, type)); if (type != curr->type) { std::cout << *curr << " 2with " << type <<" in " << *getFunction() << '\n'; abort(); } + } } // We can replace in various modes, see below. Generate a random number From 6000107b98a8f43c95e18694e940b984c895d5da Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 13:36:28 -0700 Subject: [PATCH 19/29] work --- src/passes/Print.cpp | 1 + src/tools/fuzzing/fuzzing.cpp | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 75c5452f238..987ac766325 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -523,6 +523,7 @@ struct PrintExpressionContents } void visitLocalGet(LocalGet* curr) { printMedium(o, "local.get "); + std::cout << "locget " << curr << ' '; printLocal(curr->index, currFunction, o); } void visitLocalSet(LocalSet* curr) { diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 1a4a1b48e54..2986831b742 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1753,15 +1753,20 @@ void TranslateToFuzzReader::mutate(Function* func) { // The expression must be a supertype of a fixed type. Nothing to do. } void noteSubtype(Expression* sub, Type super) { + // |sub| may not exist if it is a break target, and that break target has + // not yet been fixed up, which happens after mutate(). + if (!sub) { + return; + } if (super.isRef() && sub->type != super) { // This is a nontrivial opportunity to replace sub with a given type. childTypes[sub] = super; -std::cout << "note " << *sub << " 2with " << super <<" in " << *getFunction() << '\n'; -abort(); } } void noteSubtype(Expression* sub, Expression* super) { - // The expression must be a subtype of another expression: note it. + if (!super) { + return; + } noteSubtype(sub, super->type); } void noteNonFlowSubtype(Expression* sub, Type super) { @@ -1771,9 +1776,6 @@ abort(); void noteCast(HeapType src, HeapType dst) {} void noteCast(Expression* src, Type dst) {} void noteCast(Expression* src, Expression* dst) {} - - // TODO: For things with no constraint, we can use the top type. Maybe - // easier to do below. } finder; finder.walkFunctionInModule(func, &wasm); From dfccd972fb0df48c5eb0ec180df44fe3b924fa28 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 13:36:42 -0700 Subject: [PATCH 20/29] work --- src/tools/fuzzing/fuzzing.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 2986831b742..648d6cf3ad0 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1737,17 +1737,16 @@ void TranslateToFuzzReader::mutate(Function* func) { // First, find things to replace and their types. SubtypingDiscoverer needs to // do this in a single, full walk (as types of children depend on parents, and // even block targets). - struct Finder : public ControlFlowWalker> { + struct Finder + : public ControlFlowWalker> { // Maps children we can replace to the types we can replace them with. We // only store nontrivial ones (i.e., where the type is not just the child's // type). std::unordered_map childTypes; // We only care about constraints on Expression* things. - void noteSubtype(Type sub, Type super) { - } - void noteSubtype(HeapType sub, HeapType super) { - } + void noteSubtype(Type sub, Type super) {} + void noteSubtype(HeapType sub, HeapType super) {} void noteSubtype(Type sub, Expression* super) { // The expression must be a supertype of a fixed type. Nothing to do. @@ -1806,7 +1805,7 @@ void TranslateToFuzzReader::mutate(Function* func) { // Find the type to replace with. auto type = curr->type; -//std::cout << "on " << type << '\n'; + // std::cout << "on " << type << '\n'; if (type.isRef()) { auto iter = finder.childTypes.find(curr); if (iter != finder.childTypes.end()) { @@ -1814,10 +1813,11 @@ void TranslateToFuzzReader::mutate(Function* func) { // We can only be given a less-refined type (certainly we can replace // curr with its own type). assert(Type::isSubType(curr->type, type)); -if (type != curr->type) { -std::cout << *curr << " 2with " << type <<" in " << *getFunction() << '\n'; -abort(); -} + if (type != curr->type) { + std::cout << *curr << " 2with " << type << " in " << *getFunction() + << '\n'; + abort(); + } } } From 8d774f44b56f9516f483e10cd3d5b91004ed3e83 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 13:38:28 -0700 Subject: [PATCH 21/29] work --- src/tools/fuzzing/fuzzing.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 648d6cf3ad0..a9ad062015d 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1813,11 +1813,6 @@ void TranslateToFuzzReader::mutate(Function* func) { // We can only be given a less-refined type (certainly we can replace // curr with its own type). assert(Type::isSubType(curr->type, type)); - if (type != curr->type) { - std::cout << *curr << " 2with " << type << " in " << *getFunction() - << '\n'; - abort(); - } } } From b66a5681c73cfa7b8d902e7cc1e0b1ffbdebe9fa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 13:42:00 -0700 Subject: [PATCH 22/29] work --- src/tools/fuzzing/fuzzing.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index a9ad062015d..728c6bcd1a0 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1771,10 +1771,21 @@ void TranslateToFuzzReader::mutate(Function* func) { void noteNonFlowSubtype(Expression* sub, Type super) { noteSubtype(sub, super); } - // TODO: casts, use top + + // Casts always accept the top type. void noteCast(HeapType src, HeapType dst) {} - void noteCast(Expression* src, Type dst) {} - void noteCast(Expression* src, Expression* dst) {} + void noteCast(Expression* src, Type dst) { + if (!src || !dst.isRef()) { + return; + } + childTypes[src] = dst.with(dst.getHeapType().getTop()).with(Nullable); + } + void noteCast(Expression* src, Expression* dst) { + if (!dst) { + return; + } + noteCast(src, dst->type); + } } finder; finder.walkFunctionInModule(func, &wasm); From 31b1834ba1a0f6567344a489a82f36d2bd8dc9bf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 14:09:17 -0700 Subject: [PATCH 23/29] work --- src/tools/fuzzing/fuzzing.cpp | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 728c6bcd1a0..b309063edd7 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1772,20 +1772,11 @@ void TranslateToFuzzReader::mutate(Function* func) { noteSubtype(sub, super); } - // Casts always accept the top type. + // TODO: Many casts can accept the top type. We may need to use visit*(), to + // handle each expression class separately. void noteCast(HeapType src, HeapType dst) {} - void noteCast(Expression* src, Type dst) { - if (!src || !dst.isRef()) { - return; - } - childTypes[src] = dst.with(dst.getHeapType().getTop()).with(Nullable); - } - void noteCast(Expression* src, Expression* dst) { - if (!dst) { - return; - } - noteCast(src, dst->type); - } + void noteCast(Expression* src, Type dst) {} + void noteCast(Expression* src, Expression* dst) {} } finder; finder.walkFunctionInModule(func, &wasm); From 3eb54ab7ac2239ccbf6f5eb2ae02df045069fa55 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 14:13:57 -0700 Subject: [PATCH 24/29] work --- src/tools/fuzzing/fuzzing.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index b309063edd7..ccda3c554f5 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1561,6 +1561,7 @@ void TranslateToFuzzReader::modFunction(Function* func) { // still want to run them some of the time, at least, so that we // check variations on initial testcases even at the risk of OOB. recombine(func); + fixAfterChanges(func); mutate(func); fixAfterChanges(func); } @@ -1752,20 +1753,12 @@ void TranslateToFuzzReader::mutate(Function* func) { // The expression must be a supertype of a fixed type. Nothing to do. } void noteSubtype(Expression* sub, Type super) { - // |sub| may not exist if it is a break target, and that break target has - // not yet been fixed up, which happens after mutate(). - if (!sub) { - return; - } if (super.isRef() && sub->type != super) { // This is a nontrivial opportunity to replace sub with a given type. childTypes[sub] = super; } } void noteSubtype(Expression* sub, Expression* super) { - if (!super) { - return; - } noteSubtype(sub, super->type); } void noteNonFlowSubtype(Expression* sub, Type super) { From ba7b084670c395e6525303b180e69e0b18d49d50 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 14:25:04 -0700 Subject: [PATCH 25/29] work --- src/tools/fuzzing/fuzzing.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index ccda3c554f5..e4a1e5c4f4b 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1561,7 +1561,6 @@ void TranslateToFuzzReader::modFunction(Function* func) { // still want to run them some of the time, at least, so that we // check variations on initial testcases even at the risk of OOB. recombine(func); - fixAfterChanges(func); mutate(func); fixAfterChanges(func); } @@ -1735,6 +1734,9 @@ void TranslateToFuzzReader::mutate(Function* func) { // reasonable chance of making some changes. percentChance = std::max(percentChance, Index(3)); + // Half the time, use the SubtypingDiscoverer below. + bool useSubtypingDiscoverer = r & 1; + // First, find things to replace and their types. SubtypingDiscoverer needs to // do this in a single, full walk (as types of children depend on parents, and // even block targets). @@ -1771,7 +1773,17 @@ void TranslateToFuzzReader::mutate(Function* func) { void noteCast(Expression* src, Type dst) {} void noteCast(Expression* src, Expression* dst) {} } finder; - finder.walkFunctionInModule(func, &wasm); + + if (useSubtypingDiscoverer) { + // We read the IR here, and it must be totally valid - e.g. breaks have a + // proper break target - or else we'd hit internal errors. Fix it up first. + // (Otherwise, fixing it up is done once after mutation, and in that case + // we can mutate the IR in simple ways but not read it using + // useSubtypingDiscoverer). We avoid always doing this second fixup as it + // may bias the code in some ways. + fixAfterChanges(func); + finder.walkFunctionInModule(func, &wasm); + } // Next, modify things. struct Modder : public PostWalker> { From ee7fe178d391447a440d11743a51286d0e8d6fc1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 14:26:56 -0700 Subject: [PATCH 26/29] work --- src/passes/Print.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 987ac766325..75c5452f238 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -523,7 +523,6 @@ struct PrintExpressionContents } void visitLocalGet(LocalGet* curr) { printMedium(o, "local.get "); - std::cout << "locget " << curr << ' '; printLocal(curr->index, currFunction, o); } void visitLocalSet(LocalSet* curr) { From 358bf8555f774d8c8fc4cc1a4eedc9d2f87b2bc0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 29 Sep 2025 14:40:17 -0700 Subject: [PATCH 27/29] work --- src/tools/fuzzing/fuzzing.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index e4a1e5c4f4b..ca1f176032d 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1734,9 +1734,6 @@ void TranslateToFuzzReader::mutate(Function* func) { // reasonable chance of making some changes. percentChance = std::max(percentChance, Index(3)); - // Half the time, use the SubtypingDiscoverer below. - bool useSubtypingDiscoverer = r & 1; - // First, find things to replace and their types. SubtypingDiscoverer needs to // do this in a single, full walk (as types of children depend on parents, and // even block targets). @@ -1774,7 +1771,7 @@ void TranslateToFuzzReader::mutate(Function* func) { void noteCast(Expression* src, Expression* dst) {} } finder; - if (useSubtypingDiscoverer) { + if (oneIn(2)) { // We read the IR here, and it must be totally valid - e.g. breaks have a // proper break target - or else we'd hit internal errors. Fix it up first. // (Otherwise, fixing it up is done once after mutation, and in that case @@ -1820,6 +1817,8 @@ void TranslateToFuzzReader::mutate(Function* func) { // We can only be given a less-refined type (certainly we can replace // curr with its own type). assert(Type::isSubType(curr->type, type)); + // We only store an interesting non-trivial type. + assert(type != curr->type); } } From 96df56593e00676192df7cb859424dbcc5a16a1b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 30 Sep 2025 16:56:46 -0700 Subject: [PATCH 28/29] polish --- src/tools/fuzzing/fuzzing.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index ca1f176032d..07ae5d702ae 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1772,7 +1772,7 @@ void TranslateToFuzzReader::mutate(Function* func) { } finder; if (oneIn(2)) { - // We read the IR here, and it must be totally valid - e.g. breaks have a + // |finder| reads the IR, and it must be totally valid - e.g. breaks have a // proper break target - or else we'd hit internal errors. Fix it up first. // (Otherwise, fixing it up is done once after mutation, and in that case // we can mutate the IR in simple ways but not read it using @@ -1809,7 +1809,6 @@ void TranslateToFuzzReader::mutate(Function* func) { // Find the type to replace with. auto type = curr->type; - // std::cout << "on " << type << '\n'; if (type.isRef()) { auto iter = finder.childTypes.find(curr); if (iter != finder.childTypes.end()) { From 725488ee231c2e938d60aab6b4e275c77305add7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 30 Sep 2025 16:57:11 -0700 Subject: [PATCH 29/29] update --- test/passes/fuzz_metrics_noprint.bin.txt | 52 ++++++------ .../fuzz_metrics_passes_noprint.bin.txt | 54 ++++++------- ...e-to-fuzz_all-features_metrics_noprint.txt | 81 ++++++++++--------- 3 files changed, 95 insertions(+), 92 deletions(-) diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index 50632e024d7..baa6a426640 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -1,34 +1,34 @@ Metrics total - [exports] : 65 - [funcs] : 93 + [exports] : 32 + [funcs] : 42 [globals] : 7 [imports] : 4 [memories] : 1 [memory-data] : 23 - [table-data] : 25 + [table-data] : 9 [tables] : 1 [tags] : 0 - [total] : 6794 - [vars] : 256 - Binary : 454 - Block : 1202 - Break : 188 - Call : 205 - CallIndirect : 61 - Const : 1132 - Drop : 89 - GlobalGet : 635 - GlobalSet : 487 - If : 378 - Load : 88 - LocalGet : 406 - LocalSet : 341 - Loop : 148 - Nop : 107 - RefFunc : 25 - Return : 58 - Select : 52 - Store : 41 - Unary : 451 - Unreachable : 246 + [total] : 7716 + [vars] : 107 + Binary : 512 + Block : 1312 + Break : 299 + Call : 245 + CallIndirect : 49 + Const : 1197 + Drop : 95 + GlobalGet : 659 + GlobalSet : 465 + If : 416 + Load : 140 + LocalGet : 625 + LocalSet : 450 + Loop : 188 + Nop : 87 + RefFunc : 9 + Return : 75 + Select : 67 + Store : 45 + Unary : 547 + Unreachable : 234 diff --git a/test/passes/fuzz_metrics_passes_noprint.bin.txt b/test/passes/fuzz_metrics_passes_noprint.bin.txt index 9994355a595..ffcba736f5a 100644 --- a/test/passes/fuzz_metrics_passes_noprint.bin.txt +++ b/test/passes/fuzz_metrics_passes_noprint.bin.txt @@ -1,35 +1,35 @@ Metrics total - [exports] : 38 - [funcs] : 59 + [exports] : 17 + [funcs] : 25 [globals] : 4 [imports] : 6 [memories] : 1 [memory-data] : 20 - [table-data] : 28 + [table-data] : 9 [tables] : 1 [tags] : 0 - [total] : 9393 - [vars] : 189 - Binary : 651 - Block : 1535 - Break : 316 - Call : 296 - CallIndirect : 91 - Const : 1670 - Drop : 66 - GlobalGet : 650 - GlobalSet : 582 - If : 506 - Load : 149 - LocalGet : 823 - LocalSet : 497 - Loop : 232 - Nop : 114 - RefFunc : 28 - Return : 83 - Select : 75 - Store : 71 - Switch : 7 - Unary : 659 - Unreachable : 292 + [total] : 3774 + [vars] : 91 + Binary : 238 + Block : 655 + Break : 105 + Call : 135 + CallIndirect : 23 + Const : 703 + Drop : 103 + GlobalGet : 258 + GlobalSet : 230 + If : 211 + Load : 44 + LocalGet : 285 + LocalSet : 163 + Loop : 90 + Nop : 64 + RefFunc : 9 + Return : 41 + Select : 24 + Store : 19 + Switch : 2 + Unary : 256 + Unreachable : 116 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 1ffd3eb3d11..f92366af55d 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,51 +1,54 @@ Metrics total - [exports] : 18 - [funcs] : 24 + [exports] : 8 + [funcs] : 12 [globals] : 26 [imports] : 12 [memories] : 1 [memory-data] : 16 - [table-data] : 15 + [table-data] : 1 [tables] : 2 [tags] : 2 - [total] : 882 - [vars] : 54 - ArrayNewFixed : 7 - AtomicCmpxchg : 1 - Binary : 38 - Block : 172 + [total] : 724 + [vars] : 58 + ArrayNew : 1 + ArrayNewFixed : 4 + Binary : 31 + Block : 141 + BrOn : 1 Break : 9 - Call : 37 - Const : 180 - Drop : 72 - GlobalGet : 72 - GlobalSet : 60 - If : 33 - Load : 5 + Call : 28 + Const : 166 + Drop : 69 + GlobalGet : 47 + GlobalSet : 34 + I31Get : 2 + If : 20 + Load : 6 LocalGet : 8 - LocalSet : 9 - Loop : 6 - Nop : 7 - Pop : 2 - RefAs : 1 - RefEq : 4 - RefFunc : 15 - RefI31 : 8 - RefIsNull : 1 - RefNull : 10 - Return : 5 - SIMDExtract : 4 - Select : 1 - StringConst : 6 + LocalSet : 17 + Loop : 5 + Nop : 2 + Pop : 5 + RefCast : 1 + RefEq : 5 + RefFunc : 2 + RefI31 : 14 + RefNull : 9 + Return : 6 + SIMDExtract : 1 + Select : 3 + Store : 1 + StringConst : 9 + StringEncode : 1 StringEq : 1 StringMeasure : 1 - StringWTF16Get : 1 - StructNew : 13 - Throw : 1 - Try : 2 - TryTable : 2 - TupleExtract : 1 - TupleMake : 13 - Unary : 43 - Unreachable : 31 + StructNew : 11 + TableSet : 1 + Throw : 2 + Try : 5 + TryTable : 5 + TupleExtract : 2 + TupleMake : 7 + Unary : 24 + Unreachable : 17