From 13a3f344d1b8cbd97a356da7d7274d74d709a8fa Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 14 Jan 2025 14:27:40 -0800 Subject: [PATCH 1/4] Generate Struct RMW operations in the fuzzer And allow fuzzing of initial contents that contain these operations. --- scripts/test/fuzzing.py | 15 ------- src/tools/fuzzing.h | 2 + src/tools/fuzzing/fuzzing.cpp | 75 +++++++++++++++++++++++++++++++++-- 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 2c18789fdb5..29e4cf0e4ac 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -70,21 +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 struct RMW ops - 'gc-atomics.wast', - 'gc-atomics-null-refs.wast', - 'shared-structs.wast', - 'heap2local-rmw.wast', - 'optimize-instructions-struct-rmw.wast', - 'gto-removals-rmw.wast', - 'type-refining-rmw.wast', - 'type-ssa-exact-rmw.wast', - 'cfp-rmw.wast', - 'unsubtyping-cmpxchg.wast', - 'struct-atomic-threads.wast', - 'type-refining-gufa-rmw.wast', - 'struct-cmpxchg-shared-expected.wast', - 'precompute-gc-atomics-rmw.wast', # contains too many segments to run in a wasm VM 'limit-segments_disable-bulk-memory.wast', # https://github.com/WebAssembly/binaryen/issues/7176 diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index eea080256a6..ec79f662753 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -540,6 +540,8 @@ class TranslateToFuzzReader { bool maybeSignedGet(const Field& field); Expression* makeStructGet(Type type); + Expression* makeStructRMW(Type type); + Expression* makeStructCmpxchg(Type type); Expression* makeStructSet(Type type); Expression* makeArrayGet(Type type); Expression* makeArraySet(Type type); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 1ba58260022..4795423c920 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -2598,6 +2598,12 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { if (typeStructFields.find(type) != typeStructFields.end()) { options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeStructGet); + options.add(FeatureSet::ReferenceTypes | FeatureSet::GC | + FeatureSet::Atomics | FeatureSet::SharedEverything, + &Self::makeStructRMW); + options.add(FeatureSet::ReferenceTypes | FeatureSet::GC | + FeatureSet::Atomics | FeatureSet::SharedEverything, + &Self::makeStructCmpxchg); } if (typeArrays.find(type) != typeArrays.end()) { options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, @@ -5583,8 +5589,66 @@ Expression* TranslateToFuzzReader::makeStructGet(Type type) { auto [structType, fieldIndex] = pick(structFields); auto* ref = makeTrappingRefUse(structType); auto signed_ = maybeSignedGet(structType.getStruct().fields[fieldIndex]); - return builder.makeStructGet( - fieldIndex, ref, MemoryOrder::Unordered, type, signed_); + auto order = MemoryOrder::Unordered; + if (wasm.features.hasAtomics() && wasm.features.hasSharedEverything() && + oneIn(2)) { + order = oneIn(2) ? MemoryOrder::SeqCst : MemoryOrder::AcqRel; + } + return builder.makeStructGet(fieldIndex, ref, order, type, signed_); +} + +Expression* TranslateToFuzzReader::makeStructRMW(Type type) { + bool isAny = + type.isRef() && + Type::isSubType( + type, + Type(HeapTypes::any.getBasic(type.getHeapType().getShared()), Nullable)); + if (type != Type::i32 && type != Type::i64 && !isAny) { + // Not a valid field type for an RMW operation. + return makeStructGet(type); + } + auto& structFields = typeStructFields[type]; + assert(!structFields.empty()); + auto [structType, fieldIndex] = pick(structFields); + const auto& field = structType.getStruct().fields[fieldIndex]; + if (field.isPacked() || field.mutable_ == Immutable) { + // Cannot RMW a packed or immutable field. + return makeStructGet(type); + } + AtomicRMWOp op = RMWXchg; + if (type == Type::i32 || type == Type::i64) { + op = pick(RMWAdd, RMWSub, RMWAnd, RMWOr, RMWXor, RMWXchg); + } + auto* ref = makeTrappingRefUse(structType); + auto* value = make(type); + auto order = oneIn(2) ? MemoryOrder::SeqCst : MemoryOrder::AcqRel; + return builder.makeStructRMW(op, fieldIndex, ref, value, order); +} + +Expression* TranslateToFuzzReader::makeStructCmpxchg(Type type) { + bool isEq = + type.isRef() && + Type::isSubType( + type, + Type(HeapTypes::eq.getBasic(type.getHeapType().getShared()), Nullable)); + if (type != Type::i32 && type != Type::i64 && !isEq) { + // Not a valid field type for a cmpxchg operation. + return makeStructGet(type); + } + auto& structFields = typeStructFields[type]; + assert(!structFields.empty()); + auto [structType, fieldIndex] = pick(structFields); + const auto& field = structType.getStruct().fields[fieldIndex]; + if (field.isPacked() || field.mutable_ == Immutable) { + // Cannot RMW a packed or immutable field. + return makeStructGet(type); + } + auto* ref = makeTrappingRefUse(structType); + auto* expected = make(type); + auto* replacement = make(type); + auto order = oneIn(2) ? MemoryOrder::SeqCst : MemoryOrder::AcqRel; + return builder.makeStructCmpxchg( + fieldIndex, ref, expected, replacement, order); } Expression* TranslateToFuzzReader::makeStructSet(Type type) { @@ -5596,7 +5660,12 @@ Expression* TranslateToFuzzReader::makeStructSet(Type type) { auto fieldType = structType.getStruct().fields[fieldIndex].type; auto* ref = makeTrappingRefUse(structType); auto* value = make(fieldType); - return builder.makeStructSet(fieldIndex, ref, value, MemoryOrder::Unordered); + auto order = MemoryOrder::Unordered; + if (wasm.features.hasAtomics() && wasm.features.hasSharedEverything() && + oneIn(2)) { + order = oneIn(2) ? MemoryOrder::SeqCst : MemoryOrder::AcqRel; + } + return builder.makeStructSet(fieldIndex, ref, value, order); } // Make a bounds check for an array operation, given a ref + index. An optional From 9008648b1176792e0845d21b62778b883ca9d7fe Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Sun, 29 Mar 2026 00:27:01 -0700 Subject: [PATCH 2/4] use getTop --- src/tools/fuzzing/fuzzing.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 4795423c920..9496ba7ff94 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -5600,9 +5600,7 @@ Expression* TranslateToFuzzReader::makeStructGet(Type type) { Expression* TranslateToFuzzReader::makeStructRMW(Type type) { bool isAny = type.isRef() && - Type::isSubType( - type, - Type(HeapTypes::any.getBasic(type.getHeapType().getShared()), Nullable)); + HeapType(type.getHeapType().getTop()).isMaybeShared(HeapType::any); if (type != Type::i32 && type != Type::i64 && !isAny) { // Not a valid field type for an RMW operation. return makeStructGet(type); @@ -5628,9 +5626,7 @@ Expression* TranslateToFuzzReader::makeStructRMW(Type type) { Expression* TranslateToFuzzReader::makeStructCmpxchg(Type type) { bool isEq = type.isRef() && - Type::isSubType( - type, - Type(HeapTypes::eq.getBasic(type.getHeapType().getShared()), Nullable)); + HeapType(type.getHeapType().getTop()).isMaybeShared(HeapType::any); if (type != Type::i32 && type != Type::i64 && !isEq) { // Not a valid field type for a cmpxchg operation. return makeStructGet(type); From ea6836321299c1a6bf345d7b2e5ba527859beb34 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Sun, 29 Mar 2026 00:34:17 -0700 Subject: [PATCH 3/4] Generate eq expected fields where possible --- src/tools/fuzzing/fuzzing.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 9496ba7ff94..339d34e8eba 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -5624,9 +5624,9 @@ Expression* TranslateToFuzzReader::makeStructRMW(Type type) { } Expression* TranslateToFuzzReader::makeStructCmpxchg(Type type) { - bool isEq = - type.isRef() && - HeapType(type.getHeapType().getTop()).isMaybeShared(HeapType::any); + bool isShared = type.isRef() && type.getHeapType().isShared(); + Type eq(HeapTypes::eq.getBasic(isShared ? Shared : Unshared), Nullable); + bool isEq = Type::isSubType(type, eq); if (type != Type::i32 && type != Type::i64 && !isEq) { // Not a valid field type for a cmpxchg operation. return makeStructGet(type); @@ -5641,7 +5641,7 @@ Expression* TranslateToFuzzReader::makeStructCmpxchg(Type type) { } auto* ref = makeTrappingRefUse(structType); auto* expected = make(type); - auto* replacement = make(type); + auto* replacement = make(isEq ? eq : type); auto order = oneIn(2) ? MemoryOrder::SeqCst : MemoryOrder::AcqRel; return builder.makeStructCmpxchg( fieldIndex, ref, expected, replacement, order); From f14fb84bc09a41fae263c6f808a3708be70045a0 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Sun, 29 Mar 2026 00:43:06 -0700 Subject: [PATCH 4/4] oops --- src/tools/fuzzing/fuzzing.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 339d34e8eba..077e6a0d9e3 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -5640,8 +5640,11 @@ Expression* TranslateToFuzzReader::makeStructCmpxchg(Type type) { return makeStructGet(type); } auto* ref = makeTrappingRefUse(structType); - auto* expected = make(type); - auto* replacement = make(isEq ? eq : type); + // For reference fields, expected can be a subtype of eq. Only do use the + // extra flexibility occasionally because it makes the expected value less + // likely to be equal to the actual value. + auto* expected = make(isEq && oneIn(4) ? eq : type); + auto* replacement = make(type); auto order = oneIn(2) ? MemoryOrder::SeqCst : MemoryOrder::AcqRel; return builder.makeStructCmpxchg( fieldIndex, ref, expected, replacement, order);