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
2 changes: 2 additions & 0 deletions crates/fuzzing/src/generators/gc_ops/limits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub const MAX_REC_GROUPS_RANGE: RangeInclusive<u32> = 0..=10;
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 fields that can be inlined in a struct type.
pub const MAX_INLINE_CONSTRUCTION: u32 = 8;

/// Limits controlling the structure of a generated Wasm module.
#[derive(Clone, Debug, Serialize, Deserialize, mutatis::Mutate)]
Expand Down
141 changes: 102 additions & 39 deletions crates/fuzzing/src/generators/gc_ops/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

use crate::generators::gc_ops::types::StackType;
use crate::generators::gc_ops::{
limits::GcOpsLimits,
types::{CompositeType, RecGroupId, StructField, TypeId, Types},
limits::{GcOpsLimits, MAX_INLINE_CONSTRUCTION},
types::{CompositeType, EmitCtx, RecGroupId, StructField, TypeId, Types, emit_new},
};
use mutatis::Generate;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -39,6 +39,22 @@ fn array_element<'a>(
})
}

/// The fields of the struct type at `type_index`, or `None` if that index is
/// out of range or names an array type.
fn struct_fields<'a>(
types: &'a Types,
encoding_order: &[TypeId],
type_index: u32,
) -> Option<&'a [StructField]> {
encoding_order
.get(usize::try_from(type_index).unwrap())
.and_then(|tid| types.type_defs.get(tid))
.and_then(|def| match &def.composite_type {
CompositeType::Struct(st) => Some(st.fields.as_slice()),
CompositeType::Array(_) => None,
})
}

/// The base offsets and indices for various Wasm entities within
/// their index spaces in the the encoded Wasm binary.
#[derive(Clone, Copy)]
Expand Down Expand Up @@ -499,7 +515,10 @@ impl GcOps {

let typed_local_base: u32 = array_local_idx + 1;
let typed_local2_base: u32 = typed_local_base + concrete_count;
for _ in 0..2 {
// A third set holds one shared prototype per concrete type; see
// `Types::prototype_types`.
let proto_local_base: u32 = typed_local2_base + concrete_count;
for _ in 0..3 {
for i in 0..concrete_count {
let concrete = struct_type_base + i;
local_decls.push((
Expand Down Expand Up @@ -534,16 +553,48 @@ impl GcOps {
array_length: self.limits.array_length,
};

let mut inhabitable = BTreeMap::new();
self.types.inhabitable(&mut inhabitable);
let struct_ref_target = self.types.least_rank_inhabitable_struct(&inhabitable);

// Map each prototyped type to the local holding its instance. A type
// absent from this map is cheap enough to rebuild at every use.
let proto_order = self
.types
.prototype_types(&inhabitable, MAX_INLINE_CONSTRUCTION);
let protos: BTreeMap<TypeId, u32> = proto_order
.iter()
.map(|tid| {
let dense = type_ids_to_index[tid] - struct_type_base;
(*tid, proto_local_base + dense)
})
.collect();

let ctx = EmitCtx {
types: &self.types,
struct_ref_target,
protos: &protos,
type_ids_to_index: &type_ids_to_index,
};

let mut func = Function::new(local_decls);
func.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty));

// Refill the prototypes at the top of every iteration, so the ops below
// never read a null local and each iteration allocates a fresh set of
// objects for the collector to find and reclaim. `proto_order` is in
// rank order, so a prototype's referents are already built.
for tid in &proto_order {
emit_new(*tid, &mut func, ctx);
func.instruction(&Instruction::LocalSet(protos[tid]));
}
for op in &self.ops {
op.encode(
&mut func,
scratch_local,
storage_bases,
&self.types,
ctx,
&encoding_order,
&type_ids_to_index,
);
}
func.instruction(&Instruction::Br(0));
Expand Down Expand Up @@ -1363,10 +1414,10 @@ impl GcOp {
func: &mut Function,
scratch_local: u32,
encoding_bases: WasmEncodingBases,
types: &Types,
ctx: EmitCtx<'_>,
encoding_order: &[TypeId],
type_ids_to_index: &BTreeMap<TypeId, u32>,
) {
let types = ctx.types;
let gc_func_idx = 0;
let take_refs_func_idx = 1;
let make_refs_func_idx = 2;
Expand Down Expand Up @@ -1459,37 +1510,57 @@ impl GcOp {
)));
}
Self::StructNew { type_index: x } => {
if let Some(tid) = encoding_order.get(usize::try_from(x).unwrap()) {
if let Some(CompositeType::Struct(st)) =
types.type_defs.get(tid).map(|def| &def.composite_type)
{
for field in &st.fields {
field.field_type.emit_default_const(func, type_ids_to_index);
}
}
for field in struct_fields(types, encoding_order, x).unwrap_or(&[]) {
field.field_type.emit_default_const(func, ctx);
}
func.instruction(&Instruction::StructNew(encoding_bases.struct_type_base + x));
}
Self::StructNewDefault { type_index: x } => {
func.instruction(&Instruction::StructNewDefault(
encoding_bases.struct_type_base + x,
));
// `struct.new_default` requires every field to be defaultable,
// which a non-nullable reference is not. Build those fields
// explicitly instead; the resulting value and stack effect are
// the same.
let fields = struct_fields(types, encoding_order, x).unwrap_or(&[]);
if fields.iter().all(|f| f.field_type.is_defaultable()) {
func.instruction(&Instruction::StructNewDefault(
encoding_bases.struct_type_base + x,
));
} else {
for field in fields {
field.field_type.emit_default_const(func, ctx);
}
func.instruction(&Instruction::StructNew(encoding_bases.struct_type_base + x));
}
}
Self::ArrayNewDefault { type_index: x } => {
// Create a default-initialized array of a fixed length so most
// subsequent indexed accesses are in-bounds.
func.instruction(&Instruction::I32Const(
encoding_bases.array_length.cast_signed(),
));
func.instruction(&Instruction::ArrayNewDefault(
encoding_bases.struct_type_base + x,
));
match array_element(types, encoding_order, x) {
// As above, `array.new_default` needs a defaultable
// element. `array.new` takes the initial value explicitly,
// so it covers non-nullable elements at the same length.
Some(element) if !element.field_type.is_defaultable() => {
element.field_type.emit_default_const(func, ctx);
func.instruction(&Instruction::I32Const(
encoding_bases.array_length.cast_signed(),
));
func.instruction(&Instruction::ArrayNew(
encoding_bases.struct_type_base + x,
));
}
_ => {
func.instruction(&Instruction::I32Const(
encoding_bases.array_length.cast_signed(),
));
func.instruction(&Instruction::ArrayNewDefault(
encoding_bases.struct_type_base + x,
));
}
}
}
Self::ArrayNew { type_index: x } => {
if let Some(element) = array_element(types, encoding_order, x) {
element
.field_type
.emit_default_const(func, type_ids_to_index);
element.field_type.emit_default_const(func, ctx);
}
func.instruction(&Instruction::I32Const(
encoding_bases.array_length.cast_signed(),
Expand All @@ -1499,9 +1570,7 @@ impl GcOp {
Self::ArrayNewFixed { type_index: x, n } => {
if let Some(element) = array_element(types, encoding_order, x) {
for _ in 0..n {
element
.field_type
.emit_default_const(func, type_ids_to_index);
element.field_type.emit_default_const(func, ctx);
}
}
func.instruction(&Instruction::ArrayNewFixed {
Expand Down Expand Up @@ -1749,9 +1818,7 @@ impl GcOp {
func.instruction(&Instruction::If(wasm_encoder::BlockType::Empty));
func.instruction(&Instruction::Else);
func.instruction(&Instruction::LocalGet(typed_local));
fields[idx]
.field_type
.emit_default_const(func, type_ids_to_index);
fields[idx].field_type.emit_default_const(func, ctx);
let idx = u32::try_from(idx).unwrap();
func.instruction(&Instruction::StructSet {
struct_type_index: wasm_type,
Expand Down Expand Up @@ -1942,9 +2009,7 @@ impl GcOp {
func.instruction(&Instruction::Else);
func.instruction(&Instruction::LocalGet(typed_local));
func.instruction(&Instruction::I32Const(index.cast_signed()));
element
.field_type
.emit_default_const(func, type_ids_to_index);
element.field_type.emit_default_const(func, ctx);
func.instruction(&Instruction::ArraySet(wasm_type));
func.instruction(&Instruction::End);
}
Expand All @@ -1969,9 +2034,7 @@ impl GcOp {
func.instruction(&Instruction::Else);
func.instruction(&Instruction::LocalGet(typed_local));
func.instruction(&Instruction::I32Const(offset.cast_signed()));
element
.field_type
.emit_default_const(func, type_ids_to_index);
element.field_type.emit_default_const(func, ctx);
func.instruction(&Instruction::I32Const(len.cast_signed()));
func.instruction(&Instruction::ArrayFill(wasm_type));
func.instruction(&Instruction::End);
Expand Down
Loading
Loading