From 9781802c7de93d413541ff4ec4c62b328cbfe70f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 2 Oct 2026 17:10:41 -0700 Subject: [PATCH] Fuzzer: Emit more br_on_cast_desc_eq and br_on_cast_desc_eq_fail instructions Previously, br_on_cast_desc_eq and br_on_cast_desc_eq_fail were rarely emitted (~0.2 per module) because: 1. makeBrOn had low selection weight despite covering 6 instructions. 2. breakableStack searches stopped immediately on Type::none targets (forcing br_on_null) and often lacked reference targets. 3. Descriptor casts were only emitted when getSubType happened by chance to pick a struct with a descriptor. Fix this by: - Tracking described struct types by Shareability in describedTypes and adding hasDescribedSubType / getDescribedSubType helpers. - Increasing makeBrOn weight to Important. - Preferring reference targets (especially those with described subtypes) with randomness when searching breakableStack, and wrapping in a new target block when makeBrOn is called for a reference type without a suitable target. - Selecting BrOnCastDescEq and BrOnCastDescEqFail directly when described subtypes are available instead of upgrading BrOnCast / BrOnCastFail. Across 200 fuzzer modules, this increases br_on_cast_desc_eq from 0.20 to 2.69 per module (16.0% -> 48.0% of modules) and br_on_cast_desc_eq_fail from 0.18 to 2.88 per module (11.5% -> 50.0% of modules), while also increasing br_on_null from 27.40 to 34.46 per module. --- src/tools/fuzzing.h | 6 + src/tools/fuzzing/fuzzing.cpp | 147 +++++++++++++--- ...e-to-fuzz_all-features_metrics_noprint.txt | 165 +++++++++--------- 3 files changed, 215 insertions(+), 103 deletions(-) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index c6085511690..e64e4eea257 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -211,6 +211,10 @@ class TranslateToFuzzReader { // subtypes of it. std::unordered_map> interestingHeapSubTypes; + // The subset of interestingHeapTypes that have a descriptor, indexed by + // Shareability. + std::array, 2> describedTypes; + // Type => list of struct fields that have that type. std::unordered_map> typeStructFields; @@ -600,6 +604,8 @@ class TranslateToFuzzReader { Exactness getSubType(Exactness exactness); HeapType getSubType(HeapType type); Type getSubType(Type type); + bool hasDescribedSubType(Type type); + Type getDescribedSubType(Type type); Nullability getSuperType(Nullability nullability); HeapType getSuperType(HeapType type); Type getSuperType(Type type); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 94e5c204df6..237c65d07ea 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -603,6 +603,9 @@ void TranslateToFuzzReader::setupHeapTypes() { interestingHeapSubTypes[struct_].push_back(type); interestingHeapSubTypes[eq].push_back(type); interestingHeapSubTypes[any].push_back(type); + if (type.getDescriptorType()) { + describedTypes[share].push_back(type); + } // Note the mutable fields and fields that can be waited on. const auto& fields = type.getStruct().fields; for (Index i = 0; i < fields.size(); i++) { @@ -2857,7 +2860,8 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { .add(FeatureSet::ExceptionHandling, &Self::makeTry) .add(FeatureSet::ExceptionHandling, &Self::makeTryTable) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef) - .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeBrOn); + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, + WeightedOption{&Self::makeBrOn, Important}); } if (type.isSingle()) { options @@ -3008,7 +3012,8 @@ Expression* TranslateToFuzzReader::_makenone() { .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeStructSet) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeArraySet) - .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeBrOn) + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, + WeightedOption{&Self::makeBrOn, Important}) .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeArrayBulkMemoryOp); if (tableSetImportName) { @@ -5782,32 +5787,70 @@ Expression* TranslateToFuzzReader::makeRefGetDesc(Type type) { } Expression* TranslateToFuzzReader::makeBrOn(Type type) { - if (funcContext->breakableStack.empty()) { - return makeTrivial(type); - } // We need to find a proper target to break to; try a few times. Finding the // target is harder than flowing out the proper type, so focus on the target, // and fix up the flowing type later. That is, once we find a target to break // to, we can then either drop ourselves or wrap ourselves in a block + // another value, so that we return the proper thing here (which is done below // in fixFlowingType). - int tries = fuzzParams->TRIES; + int tries = funcContext->breakableStack.empty() ? 0 : fuzzParams->TRIES; Name targetName; Type targetType; while (--tries >= 0) { auto* target = pick(funcContext->breakableStack); - targetName = getTargetName(target); - targetType = getTargetType(target); + auto name = getTargetName(target); + auto currTargetType = getTargetType(target); // We can send any reference type, or no value at all, but nothing else. - if (targetType.isRef() || targetType == Type::none) { - break; + // Since Type::none targets are very common on breakableStack and only allow + // BrOnNull, prefer reference targets (and especially ones that have + // subtypes with descriptors) when available, with some randomness so we + // still emit enough BrOnNull and non-descriptor casts. + if (currTargetType.isRef()) { + targetName = name; + targetType = currTargetType; + if (hasDescribedSubType(currTargetType) || oneIn(2)) { + break; + } + } else if (currTargetType == Type::none && !targetName) { + targetName = name; + targetType = currTargetType; + if (oneIn(2)) { + break; + } } } - if (tries < 0) { + // If we are asked to produce a reference type `type` and the br_on itself + // does not flow out a subtype of `type`, fixFlowingType will have to wrap the + // br_on in a block of type `type` anyway. When we found no target at all on + // breakableStack (`missingTarget`), or (with high probability) when the + // target we found either is Type::none (`missingRefTarget`, which only + // permits BrOnNull) or lacks described subtypes that `type` has + // (`missingDescribedTarget`), create that wrapping block with a label and use + // it as our branch target (with targetType = type). + bool makeTargetBlock = false; + if (type.isRef()) { + bool missingTarget = !targetName; + bool missingRefTarget = !targetType.isRef(); + bool missingDescribedTarget = + hasDescribedSubType(type) && !hasDescribedSubType(targetType); + if (missingTarget || + ((missingRefTarget || missingDescribedTarget) && !oneIn(3))) { + makeTargetBlock = true; + targetName = makeLabel(); + targetType = type; + } + } + if (!targetName) { return makeTrivial(type); } auto fixFlowingType = [&](Expression* brOn) -> Expression* { + if (makeTargetBlock) { + if (brOn->type != Type::none) { + brOn = builder.makeDrop(brOn); + } + return builder.makeBlock(targetName, {brOn, make(type)}, type); + } if (Type::isSubType(brOn->type, type)) { // Already of the proper type. return brOn; @@ -5838,7 +5881,17 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) { // BrOnNonNull can handle sending any reference. The casts are more limited. auto op = BrOnNonNull; if (targetType.isCastable()) { - op = pick(BrOnNonNull, BrOnCast, BrOnCastFail); + FeatureOptions options; + using WeightedOption = FeatureOptions::WeightedOption; + options.add(FeatureSet::MVP, BrOnNonNull, BrOnCast, BrOnCastFail); + if (hasDescribedSubType(targetType)) { + // Only a subset of targets have described subtypes, so weight descriptor + // casts more heavily when such a target is available. + options.add(FeatureSet::MVP, + WeightedOption{BrOnCastDescEq, VeryImportant}, + WeightedOption{BrOnCastDescEqFail, VeryImportant}); + } + op = pick(options); } Type castType = Type::none; Type refType; @@ -5894,21 +5947,47 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) { if (castType.isNonNullable() && oneIn(2)) { castType = Type(castType.getHeapType(), Nullable); } - } break; + break; + } + case BrOnCastDescEq: + case BrOnCastDescEqFail: { + bool isFail = op == BrOnCastDescEqFail; + castType = getDescribedSubType(targetType); + if (oneIn(5)) { + refType = getSubType(castType); + } else { + std::vector supers; + for (std::optional super = castType.getHeapType(); super; + super = super->getSuperType()) { + supers.push_back(*super); + if (isFail && *super == targetType.getHeapType()) { + break; + } + } + auto refHeapType = pick(supers); + auto refNullability = isFail ? getSubType(targetType.getNullability()) + : getSuperType(castType.getNullability()); + // Inexact is a supertype of both Exact and Inexact, so `refType` only + // needs to be Exact when it is sent to the target (`isFail`) and + // `targetType` itself is Exact (in which case the loop above stopped + // immediately at `refHeapType == targetType.getHeapType()`). + auto refExactness = isFail ? targetType.getExactness() : Inexact; + refType = Type(refHeapType, refNullability, refExactness); + } + break; + } default: { WASM_UNREACHABLE("bad br_on op"); } } auto* ref = make(refType); - if (op == BrOnCast || op == BrOnCastFail) { + if (op == BrOnCastDescEq || op == BrOnCastDescEqFail) { auto desc = castType.getHeapType().getDescriptorType(); - if (desc && !oneIn(2)) { - auto descOp = op == BrOnCast ? BrOnCastDescEq : BrOnCastDescEqFail; - auto descType = Type(*desc, Nullable, castType.getExactness()); - auto* descRef = makeTrappingRefUse(descType); - auto* brOn = builder.makeBrOn(descOp, targetName, ref, castType, descRef); - return fixFlowingType(brOn); - } + assert(desc); + auto descType = Type(*desc, Nullable, castType.getExactness()); + auto* descRef = makeTrappingRefUse(descType); + auto* brOn = builder.makeBrOn(op, targetName, ref, castType, descRef); + return fixFlowingType(brOn); } return fixFlowingType(builder.makeBrOn(op, targetName, ref, castType)); } @@ -6820,6 +6899,32 @@ Type TranslateToFuzzReader::getSubType(Type type) { } } +bool TranslateToFuzzReader::hasDescribedSubType(Type type) { + if (!wasm.features.hasCustomDescriptors() || !type.isRef()) { + return false; + } + auto heapType = type.getHeapType(); + if (!heapType.isBasic()) { + return bool(heapType.getDescriptorType()); + } + auto share = heapType.getShared(); + return HeapType::isSubType(HeapTypes::struct_.getBasic(share), heapType) && + !describedTypes[share].empty(); +} + +Type TranslateToFuzzReader::getDescribedSubType(Type type) { + assert(hasDescribedSubType(type)); + auto heapType = type.getHeapType(); + if (heapType.isBasic()) { + heapType = pick(describedTypes[heapType.getShared()]); + } else if (!type.isExact()) { + heapType = getSubType(heapType); + } + auto nullability = getSubType(type.getNullability()); + auto exactness = getSubType(type.getExactness()); + return Type(heapType, nullability, exactness); +} + Nullability TranslateToFuzzReader::getSuperType(Nullability nullability) { if (nullability == Nullable) { return Nullable; 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 28d68d4e379..791e0190cd3 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,94 +1,95 @@ Metrics total [exports] : 109 - [funcs] : 205 + [funcs] : 198 [globals] : 22 [imports] : 15 [memories] : 1 [memory-data] : 31 - [table-data] : 66 + [table-data] : 58 [tables] : 2 [tags] : 2 - [total] : 111414 - [vars] : 4479 - ArrayCmpxchg : 15 - ArrayCopy : 47 - ArrayFill : 34 - ArrayGet : 481 - ArrayLen : 642 - ArrayNew : 2316 - ArrayNewFixed : 672 - ArrayRMW : 13 - ArraySet : 77 - AtomicCmpxchg : 49 - AtomicFence : 75 - AtomicNotify : 41 - AtomicRMW : 43 - Binary : 5029 - Block : 6507 - BrOn : 293 - Break : 1012 - Call : 866 - CallIndirect : 250 - CallRef : 259 - Const : 17289 - ContBind : 1 - ContNew : 193 - DataDrop : 9 - Drop : 580 - GlobalGet : 5513 - GlobalSet : 2079 - I31Get : 75 - If : 2566 - Load : 313 - LocalGet : 11499 - LocalSet : 3978 - Loop : 834 - MemoryCopy : 27 - MemoryFill : 18 - MemoryInit : 23 - Nop : 784 - RefAs : 9921 - RefCast : 703 - RefEq : 291 - RefFunc : 2029 - RefGetDesc : 79 - RefI31 : 796 - RefIsNull : 69 - RefNull : 11791 - RefTest : 59 - Return : 389 - SIMDExtract : 132 - SIMDLoad : 2 + [total] : 77178 + [vars] : 4167 + ArrayCmpxchg : 4 + ArrayCopy : 26 + ArrayFill : 27 + ArrayGet : 317 + ArrayLen : 422 + ArrayNew : 1541 + ArrayNewFixed : 444 + ArrayRMW : 10 + ArraySet : 49 + AtomicCmpxchg : 29 + AtomicFence : 53 + AtomicNotify : 35 + AtomicRMW : 44 + Binary : 3488 + Block : 5408 + BrOn : 497 + Break : 695 + Call : 740 + CallIndirect : 153 + CallRef : 177 + Const : 12117 + ContBind : 2 + ContNew : 122 + DataDrop : 18 + Drop : 693 + GlobalGet : 3688 + GlobalSet : 1628 + I31Get : 41 + If : 1900 + Load : 254 + LocalGet : 6479 + LocalSet : 3276 + Loop : 613 + MemoryCopy : 11 + MemoryFill : 14 + MemoryInit : 11 + Nop : 520 + Pop : 243 + RefAs : 6131 + RefCast : 581 + RefEq : 196 + RefFunc : 1337 + RefGetDesc : 56 + RefI31 : 567 + RefIsNull : 46 + RefNull : 8086 + RefTest : 50 + Return : 317 + SIMDExtract : 102 + SIMDLoad : 1 SIMDShift : 1 - SIMDShuffle : 1 - Select : 312 - Store : 142 - StringConcat : 3 - StringConst : 400 - StringEncode : 70 - StringEq : 69 - StringMeasure : 70 - StringNew : 10 + SIMDShuffle : 4 + Select : 242 + Store : 98 + StringConcat : 1 + StringConst : 326 + StringEncode : 41 + StringEq : 61 + StringMeasure : 39 + StringNew : 3 StringSliceWTF : 1 - StringWTF16Get : 55 - StructCmpxchg : 57 - StructGet : 442 - StructNew : 13734 - StructRMW : 54 - StructSet : 84 - StructWait : 78 - Switch : 4 - TableSet : 77 - Throw : 87 + StringWTF16Get : 54 + StructCmpxchg : 51 + StructGet : 349 + StructNew : 8651 + StructRMW : 44 + StructSet : 45 + StructWait : 58 + Switch : 5 + TableSet : 62 + Throw : 46 ThrowRef : 5 - Try : 410 - TryTable : 477 - TupleExtract : 262 - TupleMake : 231 - Unary : 1996 - Unreachable : 1066 - WaitqueueNew : 373 - WaitqueueNotify: 51 - WideIntAddSub : 16 - WideIntMul : 13 + Try : 322 + TryTable : 354 + TupleExtract : 198 + TupleMake : 189 + Unary : 1512 + Unreachable : 837 + WaitqueueNew : 249 + WaitqueueNotify: 48 + WideIntAddSub : 13 + WideIntMul : 10