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
8 changes: 5 additions & 3 deletions crates/fuzzing/src/generators/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -881,10 +881,12 @@ impl WasmtimeConfig {
// must not conflict. Set it to None so the default matches.
mcfg.gc_heap_may_move = None;

// Don't let the initial size of a GC heap exceed the maximum
// allowed by the pooling allocator.
// The GC heap rounds its min size up to a wasm page, so clamp the
// initial size to `max_memory_size` rounded *down* to a page.
const WASM_PAGE_SIZE: u64 = 1 << 16;
let cap = (pcfg.max_memory_size as u64) / WASM_PAGE_SIZE * WASM_PAGE_SIZE;
if let Some(amt) = mcfg.gc_heap_initial_size {
mcfg.gc_heap_initial_size = Some(amt.min(pcfg.max_memory_size as u64));
mcfg.gc_heap_initial_size = Some(amt.min(cap));
}
}

Expand Down
6 changes: 6 additions & 0 deletions crates/fuzzing/src/generators/gc_ops/limits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub const TABLE_SIZE_RANGE: RangeInclusive<u32> = 0..=100;
pub const MAX_REC_GROUPS_RANGE: RangeInclusive<u32> = 0..=10;
/// Range for the maximum number of fields per struct type.
pub const MAX_FIELDS_RANGE: RangeInclusive<u32> = 0..=8;
/// Range for the length of created arrays.
pub const ARRAY_LENGTH_RANGE: RangeInclusive<u32> = 1..=16;
/// Maximum number of operations.
pub const MAX_OPS: usize = 100;

Expand All @@ -27,6 +29,7 @@ pub struct GcOpsLimits {
pub(crate) max_rec_groups: u32,
pub(crate) max_types: u32,
pub(crate) max_fields: u32,
pub(crate) array_length: u32,
}

impl Default for GcOpsLimits {
Expand All @@ -38,6 +41,7 @@ impl Default for GcOpsLimits {
max_rec_groups: 5,
max_types: 5,
max_fields: 5,
array_length: 5,
}
}
}
Expand All @@ -52,6 +56,7 @@ impl GcOpsLimits {
max_rec_groups,
max_types,
max_fields,
array_length,
} = self;

let clamp = |limit: &mut u32, range: RangeInclusive<u32>| {
Expand All @@ -63,5 +68,6 @@ impl GcOpsLimits {
clamp(max_rec_groups, MAX_REC_GROUPS_RANGE);
clamp(max_types, MAX_TYPES_RANGE);
clamp(max_fields, MAX_FIELDS_RANGE);
clamp(array_length, ARRAY_LENGTH_RANGE);
}
}
111 changes: 95 additions & 16 deletions crates/fuzzing/src/generators/gc_ops/mutator.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Mutators for the `gc` operations.
use crate::generators::gc_ops::limits::GcOpsLimits;
use crate::generators::gc_ops::ops::{GcOp, GcOps};
use crate::generators::gc_ops::types::{CompositeType, FieldType, StructField, TypeId, Types};
use crate::generators::gc_ops::types::{
ArrayType, CompositeType, FieldType, StructField, TypeId, Types,
};
use mutatis::{
Candidates, Context, DefaultMutate, Generate, Mutate, Result as MutResult, mutators as m,
};
Expand Down Expand Up @@ -50,14 +52,70 @@ impl TypesMutator {
} else {
None
};
// Add struct with no fields; fields can be added later by `mutate_struct_fields`.
// Add struct with no fields; fields can be added later by `mutate_type_members`.
types.insert_struct(tid, gid, is_final, supertype, Vec::new());
log::debug!("Added struct {tid:?} to rec group {gid:?}");
Ok(())
})?;
Ok(())
}

/// Add an array type with a random element to a random existing rec group,
/// or create a rec group when there are none (if `limits` allow).
fn add_array(
&mut self,
c: &mut Candidates<'_>,
types: &mut Types,
limits: &GcOpsLimits,
) -> mutatis::Result<()> {
if c.shrink() || types.type_defs.len() >= usize::try_from(limits.max_types).unwrap() {
return Ok(());
}

let max_rec_groups = usize::try_from(limits.max_rec_groups).unwrap();
if types.rec_groups.is_empty() && max_rec_groups == 0 {
return Ok(());
}

c.mutation(|ctx| {
let gid = if types.rec_groups.is_empty() {
let new_gid = types.fresh_rec_group_id(ctx.rng());
types.insert_rec_group(new_gid);
new_gid
} else {
let Some(gid) = ctx.rng().choose(types.rec_groups.keys()).copied() else {
return Ok(());
};
gid
};

let tid = types.fresh_type_id(ctx.rng());
let is_final = (ctx.rng().gen_u32() % 4) == 0;
let supertype = if (ctx.rng().gen_u32() % 4) == 0 {
ctx.rng().choose(types.type_defs.keys()).copied()
} else {
None
};

// Arrays always have exactly one element, so generate it now.
let candidates: Vec<TypeId> = types.type_defs.keys().copied().collect();
let element = StructField {
field_type: FieldType::generate(ctx.rng(), &candidates),
mutable: (ctx.rng().gen_u32() % 2) == 0,
};
types.insert_type(
tid,
gid,
is_final,
supertype,
CompositeType::Array(ArrayType { element }),
);
log::debug!("Added array {tid:?} to rec group {gid:?}");
Ok(())
})?;
Ok(())
}

/// Remove a random type from its rec group.
fn remove_struct(&mut self, c: &mut Candidates<'_>, types: &mut Types) -> mutatis::Result<()> {
if types.type_defs.is_empty() {
Expand Down Expand Up @@ -213,14 +271,19 @@ impl TypesMutator {
return Ok(());
}

// Collect (TypeId, is_final, supertype, fields) for members of the source group.
let members: SmallVec<[(TypeId, bool, Option<TypeId>, Vec<StructField>); 32]> =
// Collect (TypeId, is_final, supertype, composite_type) for members
// of the source group (works for both structs and arrays).
let members: SmallVec<[(TypeId, bool, Option<TypeId>, CompositeType); 32]> =
src_members
.iter()
.filter_map(|tid| {
types.type_defs.get(tid).map(|def| {
let CompositeType::Struct(ref st) = def.composite_type;
(*tid, def.is_final, def.supertype, st.fields.clone())
(
*tid,
def.is_final,
def.supertype,
def.composite_type.clone(),
)
})
})
.collect();
Expand All @@ -240,10 +303,16 @@ impl TypesMutator {
}

// Insert duplicated defs, rewriting intra-group supertype edges to cloned ids.
for (old_tid, is_final, supertype, fields) in &members {
for (old_tid, is_final, supertype, composite_type) in &members {
let new_tid = old_to_new[old_tid];
let mapped_super = supertype.map(|st| *old_to_new.get(&st).unwrap_or(&st));
types.insert_struct(new_tid, new_gid, *is_final, mapped_super, fields.clone());
types.insert_type(
new_tid,
new_gid,
*is_final,
mapped_super,
composite_type.clone(),
);
}

log::debug!(
Expand Down Expand Up @@ -385,20 +454,29 @@ impl TypesMutator {
Ok(())
}

/// Mutate struct fields (add/remove/modify via `m::vec`).
fn mutate_struct_fields(
/// Mutate struct fields (add/remove/modify via `m::vec`) and array elements.
fn mutate_type_members(
&mut self,
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 {
candidates: &candidates,
})
.mutate(c, &mut st.fields)?;
match &mut def.composite_type {
CompositeType::Struct(st) => {
m::vec(StructFieldMutator {
candidates: &candidates,
})
.mutate(c, &mut st.fields)?;
}
CompositeType::Array(at) => {
StructFieldMutator {
candidates: &candidates,
}
.mutate(c, &mut at.element)?;
}
}
}
Ok(())
}
Expand All @@ -411,14 +489,15 @@ impl TypesMutator {
limits: &GcOpsLimits,
) -> mutatis::Result<()> {
self.add_struct(c, types, limits)?;
self.add_array(c, types, limits)?;
self.remove_struct(c, types)?;
self.swap_within_group(c, types)?;
self.move_between_groups(c, types)?;
self.duplicate_group(c, types, limits)?;
self.remove_group(c, types)?;
self.merge_groups(c, types)?;
self.split_group(c, types, limits)?;
self.mutate_struct_fields(c, types)?;
self.mutate_type_members(c, types)?;

Ok(())
}
Expand Down
Loading
Loading