Skip to content

Commit dad2293

Browse files
authored
gc_ops: Add support for struct subtypes (#12931)
* Add struct subtypes * Supertypes with probability
1 parent 071c406 commit dad2293

5 files changed

Lines changed: 643 additions & 22 deletions

File tree

crates/fuzzing/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ workspace = true
1515
wasmtime-test-util = { workspace = true, features = ['wast'] }
1616

1717
[dependencies]
18+
wasmtime-environ = { workspace = true }
1819
backtrace = { workspace = true }
1920
arbitrary = { workspace = true, features = ["derive"] }
2021
env_logger = { workspace = true }

crates/fuzzing/src/generators/gc_ops/mutator.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use mutatis::{
66
Candidates, Context, DefaultMutate, Generate, Mutate, Result as MutResult, mutators as m,
77
};
88
use smallvec::SmallVec;
9+
use std::collections::BTreeMap;
910

1011
/// Mutator for [`Types`]: handles adding/removing types and all rec-group
1112
/// structural mutations (duplicate, remove, merge, split, move).
@@ -32,7 +33,13 @@ impl TypesMutator {
3233
return Ok(());
3334
};
3435
let tid = types.fresh_type_id(ctx.rng());
35-
types.insert_empty_struct(tid, gid);
36+
let is_final = (ctx.rng().gen_u32() % 4) == 0;
37+
let supertype = if (ctx.rng().gen_u32() % 4) == 0 {
38+
ctx.rng().choose(types.type_defs.keys()).copied()
39+
} else {
40+
None
41+
};
42+
types.insert_empty_struct(tid, gid, is_final, supertype);
3643
log::debug!("Added empty struct {tid:?} to rec group {gid:?}");
3744
Ok(())
3845
})?;
@@ -189,22 +196,49 @@ impl TypesMutator {
189196
let Some(src_gid) = ctx.rng().choose(types.rec_groups.keys()).copied() else {
190197
return Ok(());
191198
};
192-
let Some(count) = types.rec_groups.get(&src_gid).map(|s| s.len()) else {
199+
let Some(src_members) = types.rec_groups.get(&src_gid) else {
193200
return Ok(());
194201
};
195-
if count == 0 {
202+
if src_members.is_empty() {
203+
return Ok(());
204+
}
205+
206+
// Collect (TypeId, is_final, supertype) for members of the source group.
207+
let members: SmallVec<[(TypeId, bool, Option<TypeId>); 32]> = src_members
208+
.iter()
209+
.filter_map(|tid| {
210+
types
211+
.type_defs
212+
.get(tid)
213+
.map(|def| (*tid, def.is_final, def.supertype))
214+
})
215+
.collect();
216+
217+
if members.is_empty() {
196218
return Ok(());
197219
}
198220

221+
// Create a new rec group.
199222
let new_gid = types.fresh_rec_group_id(ctx.rng());
200223
types.insert_rec_group(new_gid);
201224

202-
for _ in 0..count {
203-
let tid = types.fresh_type_id(ctx.rng());
204-
types.insert_empty_struct(tid, new_gid);
225+
// Allocate fresh type ids for each member and build old-to-new map.
226+
let mut old_to_new: BTreeMap<TypeId, TypeId> = BTreeMap::new();
227+
for (old_tid, _, _) in &members {
228+
old_to_new.insert(*old_tid, types.fresh_type_id(ctx.rng()));
205229
}
206230

207-
log::debug!("Duplicated rec group {src_gid:?} as {new_gid:?} ({count} types)");
231+
// Insert duplicated defs, rewriting intra-group supertype edges to cloned ids.
232+
for (old_tid, is_final, supertype) in &members {
233+
let new_tid = old_to_new[old_tid];
234+
let mapped_super = supertype.map(|st| *old_to_new.get(&st).unwrap_or(&st));
235+
types.insert_empty_struct(new_tid, new_gid, *is_final, mapped_super);
236+
}
237+
238+
log::debug!(
239+
"Duplicated rec group {src_gid:?} as {new_gid:?} ({count} types)",
240+
count = members.len()
241+
);
208242
Ok(())
209243
})?;
210244
Ok(())

crates/fuzzing/src/generators/gc_ops/ops.rs

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
use crate::generators::gc_ops::types::StackType;
44
use crate::generators::gc_ops::{
55
limits::GcOpsLimits,
6-
types::{CompositeType, StructType, TypeId, Types},
6+
types::{CompositeType, RecGroupId, StructType, TypeId, Types},
77
};
88
use serde::{Deserialize, Serialize};
9+
use std::collections::BTreeMap;
910
use wasm_encoder::{
1011
CodeSection, ConstExpr, EntityType, ExportKind, ExportSection, Function, FunctionSection,
1112
GlobalSection, ImportSection, Instruction, Module, RefType, TableSection, TableType,
@@ -105,12 +106,52 @@ impl GcOps {
105106

106107
let struct_type_base: u32 = types.len();
107108

109+
// Sort all types globally so supertypes come before subtypes.
110+
// This is used to order types *within* each rec group.
111+
let mut type_order = Vec::new();
112+
self.types.sort_types_topo(&mut type_order);
113+
114+
// Build a position map so we can sort each group's members
115+
// according to the global type order.
116+
let type_position: BTreeMap<TypeId, usize> = type_order
117+
.iter()
118+
.enumerate()
119+
.map(|(i, &id)| (id, i))
120+
.collect();
121+
122+
// Topological sort of rec groups: if a type in group G has a
123+
// supertype in group H, then H appears before G.
124+
let mut group_order = Vec::new();
125+
self.types.sort_rec_groups_topo(&mut group_order);
126+
127+
// For each group, collect its members sorted by the global type order.
128+
let mut group_members: BTreeMap<RecGroupId, Vec<TypeId>> = BTreeMap::new();
129+
for &gid in &group_order {
130+
if let Some(member_set) = self.types.rec_groups.get(&gid) {
131+
let mut members: Vec<TypeId> = member_set.iter().copied().collect();
132+
members.sort_by_key(|tid| type_position.get(tid).copied().unwrap_or(usize::MAX));
133+
group_members.insert(gid, members);
134+
}
135+
}
136+
137+
// Build the type-id-to-wasm-index map directly.
138+
let mut type_ids_to_index: BTreeMap<TypeId, u32> = BTreeMap::new();
139+
let mut next_idx = struct_type_base;
140+
for g in &group_order {
141+
if let Some(members) = group_members.get(g) {
142+
for &tid in members {
143+
type_ids_to_index.insert(tid, next_idx);
144+
next_idx += 1;
145+
}
146+
}
147+
}
148+
108149
let encode_ty_id = |ty_id: &TypeId| -> wasm_encoder::SubType {
109150
let def = &self.types.type_defs[ty_id];
110151
match &def.composite_type {
111152
CompositeType::Struct(StructType {}) => wasm_encoder::SubType {
112-
is_final: true,
113-
supertype_idx: None,
153+
is_final: def.is_final,
154+
supertype_idx: def.supertype.map(|st| type_ids_to_index[&st]),
114155
composite_type: wasm_encoder::CompositeType {
115156
inner: wasm_encoder::CompositeInnerType::Struct(wasm_encoder::StructType {
116157
fields: Box::new([]),
@@ -125,13 +166,12 @@ impl GcOps {
125166

126167
let mut struct_count = 0;
127168

128-
for member_set in self.types.rec_groups.values() {
129-
let member_types: Vec<wasm_encoder::SubType> =
130-
member_set.iter().map(|tid| encode_ty_id(tid)).collect();
131-
let member_count = u32::try_from(member_types.len())
132-
.expect("member_types len should be within u32 range");
133-
types.ty().rec(member_types);
134-
struct_count += member_count;
169+
// Emit rec groups in the derived order.
170+
for g in &group_order {
171+
let type_ids = group_members.get(g).map(|v| v.as_slice()).unwrap_or(&[]);
172+
let members: Vec<wasm_encoder::SubType> = type_ids.iter().map(encode_ty_id).collect();
173+
types.ty().rec(members);
174+
struct_count += u32::try_from(type_ids.len()).unwrap();
135175
}
136176

137177
let typed_fn_type_base: u32 = struct_type_base + struct_count;

0 commit comments

Comments
 (0)