diff --git a/crates/fuzzing/src/generators/gc_ops/ops.rs b/crates/fuzzing/src/generators/gc_ops/ops.rs index ab995211d449..f3aafc41baab 100644 --- a/crates/fuzzing/src/generators/gc_ops/ops.rs +++ b/crates/fuzzing/src/generators/gc_ops/ops.rs @@ -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, } @@ -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 @@ -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. @@ -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; @@ -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 ) global per struct type. let typed_global_base = globals.len(); for i in 0..struct_count { @@ -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(( @@ -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, }; @@ -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| { @@ -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 => { @@ -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, diff --git a/crates/fuzzing/src/generators/gc_ops/types.rs b/crates/fuzzing/src/generators/gc_ops/types.rs index e6db2e2c9f71..0083aa96aa70 100644 --- a/crates/fuzzing/src/generators/gc_ops/types.rs +++ b/crates/fuzzing/src/generators/gc_ops/types.rs @@ -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, } }; } @@ -708,6 +712,8 @@ impl Types { pub enum StackType { /// `externref`. ExternRef, + /// `eqref`. + Eq, /// `(ref $*)` — optionally with a concrete type index. Struct(Option), } @@ -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)))) => { diff --git a/crates/fuzzing/src/oracles.rs b/crates/fuzzing/src/oracles.rs index 21e71891a710..11ddf2ca32db 100644 --- a/crates/fuzzing/src/oracles.rs +++ b/crates/fuzzing/src/oracles.rs @@ -847,6 +847,21 @@ pub fn gc_ops(mut fuzz_config: generators::Config, mut ops: GcOps) -> Result, _params, _results| { + log::info!("gc_ops: take_eq()"); + Ok(()) + } + }); + + linker.define(&store, "", "take_eq", func).unwrap(); + for imp in module.imports() { if imp.module() == "" { let name = imp.name();