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
122 changes: 121 additions & 1 deletion crates/fuzzing/src/generators/gc_ops/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ struct WasmEncodingBases {
struct_type_base: u32,
typed_first_func_index: u32,
struct_local_idx: u32,
eq_local_idx: u32,
typed_local_base: u32,
struct_global_idx: u32,
eq_global_idx: u32,
typed_global_base: u32,
struct_table_idx: u32,
eq_table_idx: u32,
typed_table_base: u32,
}

Expand Down Expand Up @@ -103,6 +106,18 @@ impl GcOps {
vec![],
);

// 5: `take_eq`
types.ty().function(
vec![ValType::Ref(RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Eq,
},
})],
vec![],
);

let struct_type_base: u32 = types.len();

// Build the type-id-to-wasm-index map from the pre-computed
Expand Down Expand Up @@ -174,6 +189,7 @@ impl GcOps {
imports.import("", "take_refs", EntityType::Function(2));
imports.import("", "make_refs", EntityType::Function(3));
imports.import("", "take_struct", EntityType::Function(4));
imports.import("", "take_eq", EntityType::Function(5));

// For each of our concrete struct types, define a function
// import that takes an argument of that concrete type.
Expand Down Expand Up @@ -210,6 +226,15 @@ impl GcOps {
shared: false,
});

let eq_table_idx = tables.len();
tables.table(TableType {
element_type: RefType::EQREF,
minimum: u64::from(self.limits.table_size),
maximum: None,
table64: false,
shared: false,
});

let typed_table_base = tables.len();
for i in 0..struct_count {
let concrete = struct_type_base + i;
Expand Down Expand Up @@ -258,6 +283,20 @@ impl GcOps {
}),
);

// Add exactly one (ref.null eq) global.
let eq_global_idx = globals.len();
globals.global(
wasm_encoder::GlobalType {
val_type: ValType::Ref(RefType::EQREF),
mutable: true,
shared: false,
},
&ConstExpr::ref_null(wasm_encoder::HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Eq,
}),
);

// Add one typed (ref <type>) global per struct type.
let typed_global_base = globals.len();
for i in 0..struct_count {
Expand Down Expand Up @@ -301,7 +340,10 @@ impl GcOps {
}),
));

let typed_local_base: u32 = struct_local_idx + 1;
let eq_local_idx = struct_local_idx + 1;
local_decls.push((1, ValType::Ref(RefType::EQREF)));

let typed_local_base: u32 = eq_local_idx + 1;
for i in 0..struct_count {
let concrete = struct_type_base + i;
local_decls.push((
Expand All @@ -317,10 +359,13 @@ impl GcOps {
struct_type_base,
typed_first_func_index,
struct_local_idx,
eq_local_idx,
typed_local_base,
struct_global_idx,
eq_global_idx,
typed_global_base,
struct_table_idx,
eq_table_idx,
typed_table_base,
};

Expand Down Expand Up @@ -599,6 +644,48 @@ macro_rules! for_each_gc_op {
#[results([Struct(None)])]
NullStruct,

#[operands([])]
#[results([Eq])]
NullEq,

#[operands([Some(Eq)])]
#[results([])]
TakeEqCall,

#[operands([Some(Eq)])]
#[results([])]
EqLocalSet,

#[operands([])]
#[results([Eq])]
EqLocalGet,

#[operands([Some(Eq)])]
#[results([])]
EqGlobalSet,

#[operands([])]
#[results([Eq])]
EqGlobalGet,

#[operands([Some(Eq)])]
#[results([])]
#[fixup(|limits, _num_types| {
// Add one to make sure that out-of-bounds table accesses are
// possible, but still rare.
elem_index = elem_index % (limits.table_size + 1);
})]
EqTableSet { elem_index: u32 },

#[operands([])]
#[results([Eq])]
#[fixup(|limits, _num_types| {
// Add one to make sure that out-of-bounds table accesses are
// possible, but still rare.
elem_index = elem_index % (limits.table_size + 1);
})]
EqTableGet { elem_index: u32 },

#[operands([])]
#[results([Struct(Some(type_index))])]
#[fixup(|_limits, num_types| {
Expand Down Expand Up @@ -825,6 +912,7 @@ impl GcOp {
let take_refs_func_idx = 1;
let make_refs_func_idx = 2;
let take_structref_idx = 3;
let take_eqref_idx = 4;

match *self {
Self::Gc => {
Expand Down Expand Up @@ -870,6 +958,38 @@ impl GcOp {
ty: wasm_encoder::AbstractHeapType::Struct,
}));
}
Self::NullEq => {
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Eq,
}));
}
Self::TakeEqCall => {
func.instruction(&Instruction::Call(take_eqref_idx));
}
Self::EqLocalGet => {
func.instruction(&Instruction::LocalGet(encoding_bases.eq_local_idx));
}
Self::EqLocalSet => {
func.instruction(&Instruction::LocalSet(encoding_bases.eq_local_idx));
}
Self::EqGlobalGet => {
func.instruction(&Instruction::GlobalGet(encoding_bases.eq_global_idx));
}
Self::EqGlobalSet => {
func.instruction(&Instruction::GlobalSet(encoding_bases.eq_global_idx));
}
Self::EqTableGet { elem_index } => {
func.instruction(&Instruction::I32Const(elem_index.cast_signed()));
func.instruction(&Instruction::TableGet(encoding_bases.eq_table_idx));
}
Self::EqTableSet { elem_index } => {
// Use eq_local_idx (eqref) to temporarily store the value before table.set.
func.instruction(&Instruction::LocalSet(encoding_bases.eq_local_idx));
func.instruction(&Instruction::I32Const(elem_index.cast_signed()));
func.instruction(&Instruction::LocalGet(encoding_bases.eq_local_idx));
func.instruction(&Instruction::TableSet(encoding_bases.eq_table_idx));
}
Self::NullTypedStruct { type_index } => {
func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::Concrete(
encoding_bases.struct_type_base + type_index,
Expand Down
21 changes: 21 additions & 0 deletions crates/fuzzing/src/generators/gc_ops/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ macro_rules! for_each_field_type {
#[storage(wasm_encoder::StorageType::Val(wasm_encoder::ValType::EXTERNREF))]
#[default_val(wasm_encoder::Instruction::RefNull(wasm_encoder::HeapType::EXTERN))]
ExternRef,

#[storage(wasm_encoder::StorageType::Val(wasm_encoder::ValType::Ref(wasm_encoder::RefType::EQREF)))]
#[default_val(wasm_encoder::Instruction::RefNull(wasm_encoder::HeapType::Abstract { shared: false, ty: wasm_encoder::AbstractHeapType::Eq }))]
EqRef,
}
};
}
Expand Down Expand Up @@ -708,6 +712,8 @@ impl Types {
pub enum StackType {
/// `externref`.
ExternRef,
/// `eqref`.
Eq,
/// `(ref $*)` — optionally with a concrete type index.
Struct(Option<u32>),
}
Expand Down Expand Up @@ -752,6 +758,21 @@ impl StackType {
);
}
},
Some(Self::Eq) => match stack.last() {
// struct <: eq, so a struct on the stack satisfies an eqref requirement.
Some(Self::Eq) | Some(Self::Struct(_)) => {
log::trace!("[StackType::fixup] Eq: top ok -> pop");
stack.pop();
}
other => {
log::trace!("[StackType::fixup] Eq: mismatch top={other:?} -> emit NullEq+pop");
Self::emit(GcOp::NullEq, stack, out, num_types, &mut result_types);
let popped = stack.pop();
log::trace!(
"[StackType::fixup] Eq: after emit pop -> {popped:?} stack={stack:?}"
);
}
},
Some(Self::Struct(wanted)) => {
let ok = match (wanted, stack.last()) {
(Some(wanted), Some(Self::Struct(Some(actual)))) => {
Expand Down
15 changes: 15 additions & 0 deletions crates/fuzzing/src/oracles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,21 @@ pub fn gc_ops(mut fuzz_config: generators::Config, mut ops: GcOps) -> Result<usi

linker.define(&store, "", "take_struct", func).unwrap();

let func_ty = FuncType::new(
store.engine(),
vec![ValType::Ref(RefType::new(true, HeapType::Eq))],
vec![],
);

let func = Func::new(&mut store, func_ty, {
move |_caller: Caller<'_, StoreLimits>, _params, _results| {
log::info!("gc_ops: take_eq(<ref null eq>)");
Ok(())
}
});

linker.define(&store, "", "take_eq", func).unwrap();

for imp in module.imports() {
if imp.module() == "" {
let name = imp.name();
Expand Down
Loading