Skip to content
Merged
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
23 changes: 19 additions & 4 deletions crates/fuzzing/src/generators/gc_ops/mutator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,39 @@ use std::collections::BTreeMap;
pub struct TypesMutator;

impl TypesMutator {
/// Add an empty struct type to a random existing rec group.
/// Add an empty struct in a random existing rec group, or create a rec group
/// and add there when `rec_groups` is empty (if `limits.max_rec_groups` allows).
fn add_struct(
&mut self,
c: &mut Candidates<'_>,
types: &mut Types,
limits: &GcOpsLimits,
) -> mutatis::Result<()> {
if c.shrink()
|| types.rec_groups.is_empty()
|| types.type_defs.len()
>= usize::try_from(limits.max_types).expect("max_types is too large")
{
return Ok(());
}

let max_rec_groups =
usize::try_from(limits.max_rec_groups).expect("max_rec_groups is too large");
if types.rec_groups.is_empty() && max_rec_groups == 0 {
return Ok(());
}

c.mutation(|ctx| {
let Some(gid) = ctx.rng().choose(types.rec_groups.keys()).copied() else {
return Ok(());
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 {
Expand Down
72 changes: 28 additions & 44 deletions crates/fuzzing/src/generators/gc_ops/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ impl GcOps {
/// fuel. It also is not guaranteed to avoid traps: it may access
/// out-of-bounds of the table.
pub fn to_wasm_binary(&mut self) -> Vec<u8> {
self.fixup();
let mut encoding_order_grouped = Vec::with_capacity(self.types.rec_groups.len());
self.fixup(&mut encoding_order_grouped);

let mut module = Module::new();

Expand Down Expand Up @@ -106,43 +107,15 @@ impl GcOps {

let struct_type_base: u32 = types.len();

// Sort all types globally so supertypes come before subtypes.
// This is used to order types *within* each rec group.
let mut type_order = Vec::new();
self.types.sort_types_topo(&mut type_order);

// Build a position map so we can sort each group's members
// according to the global type order.
let type_position: BTreeMap<TypeId, usize> = type_order
.iter()
.enumerate()
.map(|(i, &id)| (id, i))
.collect();

// Topological sort of rec groups: if a type in group G has a
// supertype in group H, then H appears before G.
let mut group_order = Vec::new();
self.types.sort_rec_groups_topo(&mut group_order);

// For each group, collect its members sorted by the global type order.
let mut group_members: BTreeMap<RecGroupId, Vec<TypeId>> = BTreeMap::new();
for &gid in &group_order {
if let Some(member_set) = self.types.rec_groups.get(&gid) {
let mut members: Vec<TypeId> = member_set.iter().copied().collect();
members.sort_by_key(|tid| type_position.get(tid).copied().unwrap_or(usize::MAX));
group_members.insert(gid, members);
}
}

// Build the type-id-to-wasm-index map directly.
// Build the type-id-to-wasm-index map from the pre-computed
// encoding order (rec groups in topo order, members sorted by
// supertype-first within each group).
let mut type_ids_to_index: BTreeMap<TypeId, u32> = BTreeMap::new();
let mut next_idx = struct_type_base;
for g in &group_order {
if let Some(members) = group_members.get(g) {
for &tid in members {
type_ids_to_index.insert(tid, next_idx);
next_idx += 1;
}
for (_, members) in &encoding_order_grouped {
for &tid in members {
type_ids_to_index.insert(tid, next_idx);
next_idx += 1;
}
}

Expand All @@ -166,12 +139,12 @@ impl GcOps {

let mut struct_count = 0;

// Emit rec groups in the derived order.
for g in &group_order {
let type_ids = group_members.get(g).map(|v| v.as_slice()).unwrap_or(&[]);
let members: Vec<wasm_encoder::SubType> = type_ids.iter().map(encode_ty_id).collect();
// Emit rec groups in the pre-computed order.
for (_, group_members) in &encoding_order_grouped {
let members: Vec<wasm_encoder::SubType> =
group_members.iter().map(encode_ty_id).collect();
types.ty().rec(members);
struct_count += u32::try_from(type_ids.len()).unwrap();
struct_count += u32::try_from(group_members.len()).unwrap();
}

let typed_fn_type_base: u32 = struct_type_base + struct_count;
Expand Down Expand Up @@ -378,9 +351,13 @@ impl GcOps {
/// pre-mutation test cases are even valid! Therefore, we always call this
/// method before translating this "AST"-style representation into a raw
/// Wasm binary.
pub fn fixup(&mut self) {
pub fn fixup(&mut self, encoding_order_grouped: &mut Vec<(RecGroupId, Vec<TypeId>)>) {
self.limits.fixup();
self.types.fixup(&self.limits);
self.types.fixup(&self.limits, encoding_order_grouped);
let encoding_order: Vec<TypeId> = encoding_order_grouped
.iter()
.flat_map(|(_, members)| members.iter().copied())
.collect();

let mut new_ops = Vec::with_capacity(self.ops.len());
let mut stack: Vec<StackType> = Vec::new();
Expand All @@ -395,7 +372,14 @@ impl GcOps {
debug_assert!(operand_types.is_empty());
op.operand_types(&mut operand_types);
for ty in operand_types.drain(..) {
StackType::fixup(ty, &mut stack, &mut new_ops, num_types);
StackType::fixup(
ty,
&mut stack,
&mut new_ops,
num_types,
&self.types,
&encoding_order,
);
}

// Finally, emit the op itself (updates stack abstractly)
Expand Down
Loading
Loading