Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions crates/fuzzing/src/generators/gc_ops/limits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub const MAX_FIELDS_RANGE: RangeInclusive<u32> = 0..=8;
pub const MAX_OPS: usize = 100;

/// Limits controlling the structure of a generated Wasm module.
#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
pub struct GcOpsLimits {
pub(crate) num_params: u32,
pub(crate) num_globals: u32,
Expand All @@ -29,11 +29,22 @@ pub struct GcOpsLimits {
pub(crate) max_fields: u32,
}

impl Default for GcOpsLimits {
fn default() -> Self {
Self {
num_params: 5,
num_globals: 5,
table_size: 5,
max_rec_groups: 5,
max_types: 5,
max_fields: 5,
}
}
}

impl GcOpsLimits {
/// Fixup the limits to ensure they are within the valid range.
pub(crate) fn fixup(&mut self) {
// NB: Exhaustively match so that we remember to fixup any other new
// limits we add in the future.
let Self {
num_params,
num_globals,
Expand Down
28 changes: 15 additions & 13 deletions crates/fuzzing/src/generators/gc_ops/mutator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,14 @@ impl TypesMutator {
c: &mut Candidates<'_>,
types: &mut Types,
) -> mutatis::Result<()> {
// Snapshot target types up front so fields can reference any type (incl. self) without borrowing `types`.
let candidates: Vec<TypeId> = types.type_defs.keys().copied().collect();
for (_, def) in types.type_defs.iter_mut() {
let CompositeType::Struct(ref mut st) = def.composite_type;
m::vec(StructFieldMutator).mutate(c, &mut st.fields)?;
m::vec(StructFieldMutator {
candidates: &candidates,
})
.mutate(c, &mut st.fields)?;
}
Ok(())
}
Expand All @@ -419,16 +424,17 @@ impl TypesMutator {
}
}

/// Mutator for [`StructField`]: used by `m::vec` to add, remove, and
/// modify fields within a struct type.
#[derive(Debug, Default)]
pub struct StructFieldMutator;
/// Mutator for [`StructField`] (add/remove/modify fields).
#[derive(Debug)]
pub struct StructFieldMutator<'a> {
candidates: &'a [TypeId],
}

impl Mutate<StructField> for StructFieldMutator {
impl Mutate<StructField> for StructFieldMutator<'_> {
fn mutate(&mut self, c: &mut Candidates<'_>, field: &mut StructField) -> MutResult<()> {
c.mutation(|ctx| {
let old = format!("{field:?}");
field.field_type = FieldType::random(ctx.rng());
field.field_type = FieldType::generate(ctx.rng(), self.candidates);
field.mutable = (ctx.rng().gen_u32() % 2) == 0;
log::debug!("Mutated field {old} -> {field:?}");
Ok(())
Expand All @@ -437,21 +443,17 @@ impl Mutate<StructField> for StructFieldMutator {
}
}

impl Generate<StructField> for StructFieldMutator {
impl Generate<StructField> for StructFieldMutator<'_> {
fn generate(&mut self, ctx: &mut Context) -> MutResult<StructField> {
let field = StructField {
field_type: FieldType::random(ctx.rng()),
field_type: FieldType::generate(ctx.rng(), self.candidates),
mutable: (ctx.rng().gen_u32() % 2) == 0,
};
log::debug!("Generated field {field:?}");
Ok(field)
}
}

impl DefaultMutate for StructField {
type DefaultMutate = StructFieldMutator;
}

/// Mutator for [`GcOps`].
///
/// Also implements [`Mutate`] / [`Generate`] for [`GcOp`] so `m::vec` can mutate
Expand Down
10 changes: 7 additions & 3 deletions crates/fuzzing/src/generators/gc_ops/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ impl GcOps {
.fields
.iter()
.map(|f| wasm_encoder::FieldType {
element_type: f.field_type.to_storage_type(),
element_type: f.field_type.to_storage_type(&type_ids_to_index),
mutable: f.mutable,
})
.collect();
Expand Down Expand Up @@ -425,6 +425,7 @@ impl GcOps {
storage_bases,
&self.types,
&encoding_order,
&type_ids_to_index,
);
}
func.instruction(&Instruction::Br(0));
Expand Down Expand Up @@ -1021,6 +1022,7 @@ impl GcOp {
encoding_bases: WasmEncodingBases,
types: &Types,
encoding_order: &[TypeId],
type_ids_to_index: &BTreeMap<TypeId, u32>,
) {
let gc_func_idx = 0;
let take_refs_func_idx = 1;
Expand Down Expand Up @@ -1115,7 +1117,7 @@ impl GcOp {
if let Some(def) = types.type_defs.get(tid) {
let CompositeType::Struct(ref st) = def.composite_type;
for field in &st.fields {
field.field_type.emit_default_const(func);
field.field_type.emit_default_const(func, type_ids_to_index);
}
}
}
Expand Down Expand Up @@ -1326,7 +1328,9 @@ impl GcOp {
func.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
func.instruction(&Instruction::Else);
func.instruction(&Instruction::LocalGet(typed_local));
fields[idx].field_type.emit_default_const(func);
fields[idx]
.field_type
.emit_default_const(func, type_ids_to_index);
let idx = u32::try_from(idx).unwrap();
func.instruction(&Instruction::StructSet {
struct_type_index: wasm_type,
Expand Down
46 changes: 23 additions & 23 deletions crates/fuzzing/src/generators/gc_ops/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::generators::gc_ops::{
use mutatis;
use rand::rngs::StdRng;
use rand::{RngExt, SeedableRng};
use std::collections::BTreeSet;
use wasmparser;
use wasmprinter;

Expand Down Expand Up @@ -118,7 +119,6 @@ fn test_ops(num_params: u32, num_globals: u32, table_size: u32) -> GcOps {
fn assert_valid_wasm(ops: &mut GcOps) {
let wasm = ops.to_wasm_binary();
let feats = wasmparser::WasmFeatures::default();
eprintln!("wat: {}", wasmprinter::print_bytes(&wasm).unwrap());
feats.reference_types();
feats.gc();
let mut validator = wasmparser::Validator::new_with_features(feats);
Expand Down Expand Up @@ -220,7 +220,7 @@ fn every_op_generated() -> mutatis::Result<()> {
let mut res = empty_test_ops();
let mut session = mutatis::Session::new().seed(0xC0FFEE);

'outer: for _ in 0..=1024 {
'outer: for _ in 0..=8192 {
session.mutate(&mut res)?;
for op in &res.ops {
unseen_ops.remove(op.name());
Expand Down Expand Up @@ -528,7 +528,7 @@ fn sort_rec_groups_topo_orders_dependencies_first() {
}

#[test]
fn break_rec_group_cycles() {
fn merge_rec_group_cycles() {
let _ = env_logger::try_init();

let mut types = Types::new();
Expand Down Expand Up @@ -568,11 +568,15 @@ fn break_rec_group_cycles() {
// -----------| g3 |
// +----+
//
// After: back edges dropped, clean chain
// All four groups are mutually reachable (g0->g1->g2->g0 and
// g1->g2->g3->g1), so they form a single strongly-connected component.
//
// +----+ +----+ +----+ +----+
// | g0 |------>| g1 |------>| g2 |------>| g3 |
// +----+ +----+ +----+ +----+
// After: the whole SCC is merged into one rec group, with every supertype
// edge preserved (nothing is dropped).
//
// +-------------------------+
// | g0 ∪ g1 ∪ g2 ∪ g3 |
// +-------------------------+

types.insert_struct(a0, g0, false, Some(b0), Vec::new()); // g0 -> g1
types.insert_struct(a1, g0, false, None, Vec::new());
Expand All @@ -598,32 +602,28 @@ fn break_rec_group_cycles() {
assert_eq!(types.rec_groups.len(), 4);

let type_to_group = types.type_to_group_map();
types.break_rec_group_cycles(&type_to_group);

// All four groups preserved.
assert_eq!(types.rec_groups.len(), 4);
assert!(types.rec_groups.contains_key(&g0));
assert!(types.rec_groups.contains_key(&g1));
assert!(types.rec_groups.contains_key(&g2));
assert!(types.rec_groups.contains_key(&g3));
types.merge_rec_group_cycles(&type_to_group);

// Back edge (g2->g0): c1's supertype cleared.
assert_eq!(types.type_defs.get(&c1).unwrap().supertype, None);
// The whole cycle collapses into a single rec group...
assert_eq!(types.rec_groups.len(), 1);

// Back edge (g3->g1): d1's supertype cleared.
assert_eq!(types.type_defs.get(&d1).unwrap().supertype, None);
// ...which contains every type from all four original groups.
let merged: BTreeSet<TypeId> = types.rec_groups.values().flatten().copied().collect();
let expected: BTreeSet<TypeId> = [a0, a1, b0, b1, c0, c1, c2, d0, d1].into_iter().collect();
assert_eq!(merged, expected);

// All other cross-group supertypes preserved.
// Merging drops no edges: every supertype relationship is preserved.
assert_eq!(types.type_defs.get(&a0).unwrap().supertype, Some(b0));
assert_eq!(types.type_defs.get(&b1).unwrap().supertype, Some(c0));
assert_eq!(types.type_defs.get(&c1).unwrap().supertype, Some(a1));
assert_eq!(types.type_defs.get(&c2).unwrap().supertype, Some(d0));
assert_eq!(types.type_defs.get(&d1).unwrap().supertype, Some(b0));

// Result is a clean chain: g0 -> g1 -> g2 -> g3
// With a single group there are no cross-group ordering constraints left.
let type_to_group = types.type_to_group_map();
let mut topo = Vec::new();
types.sort_rec_groups_topo(&mut topo, &type_to_group);
assert_eq!(topo.len(), 4);
assert_eq!(topo, vec![g3, g2, g1, g0]);
assert_eq!(topo.len(), 1);
}

#[test]
Expand Down
Loading
Loading