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', ] diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index d178ff942a5..82451ace188 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 @@ -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. @@ -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. diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 55237a70af1..8f1342ac459 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,32 +90,14 @@ # 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', - 'local-cse-cont.wast', + # TODO: fuzzer support for remaining stack switching instructions: switch, + # cont.bind + 'cont.wast', 'precompute-stack-switching.wast', + 'coalesce-locals-stack-switching.wast', + 'stack_switching_switch_2.wast', + 'stack_switching_switch.wast', 'unsubtyping-stack-switching.wast', - 'vacuum-stack-switching.wast', - 'cont.wast', - 'cont_simple.wast', - 'gufa-cont.wast', - 'cont_many_unhandled.wast', - 'cont_export.wast', - 'cont_export_throw.wast', - 'type-merging-cont.wast', - 'remove-unused-module-elements-cont.wast', - 'abstract-type-refining-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/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 8f89c1292ed..f262fb0c797 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"); } @@ -672,6 +674,7 @@ void TranslateToFuzzReader::setupGlobals() { // Create new random globals. for (size_t index = upTo(fuzzParams->MAX_GLOBALS); index > 0; --index) { auto type = getConcreteType(); + // 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; @@ -680,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()) { @@ -708,6 +715,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. @@ -736,7 +746,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() { @@ -2432,10 +2443,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); if (usedTags.count(tag)) { continue; } @@ -2482,7 +2493,7 @@ Expression* TranslateToFuzzReader::makeTryTable(Type type) { return builder.makeTryTable(body, {}, {}, {}); } - if (wasm.tags.empty()) { + if (exceptionTags.empty()) { addTag(); } @@ -2497,7 +2508,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 { @@ -3449,7 +3460,15 @@ Expression* TranslateToFuzzReader::makeBasicRef(Type type) { return makeRefFuncConst(type); } case HeapType::cont: { - WASM_UNREACHABLE("not implemented"); + // 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. + 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 @@ -3673,8 +3692,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 = heapType.getContinuation().type; + return builder.makeContNew(heapType, makeTrappingRefUse(funcType)); + } case HeapTypeKind::Basic: break; } @@ -5222,10 +5243,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; 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