Allocate less while constructing cranelift-fuzzgen tests (#4863)

* Improve panic message if typevar_operand is None

* cranelift-fuzzgen: Don't allocate for each choice

I don't think the performance of test-case generation is at all
important here. I'm actually doing this in preparation for a bigger
refactor where I want to be able to borrow the list of valid choices for
a given opcode without worrying about lifetimes.

* cranelift-fuzzgen: Remove next_func_index

It's only used locally within `generate_funcrefs`, so it doesn't need to
be in the FunctionBuilder struct.

Also there's already a local counter that I think is good enough for
this. As far as I know, the function indexes only need to be distinct,
not contiguous.

* cranelift-fuzzgen: Separate resources from config

The function-global variables, blocks, etc that are generated before
generating instructions are all owned collections without any lifetime
parameters. By contrast, the Unstructured and Config are both borrowed.
Separating them will make it easier to borrow from the owned resources.
This commit is contained in:
Jamey Sharp
2022-09-07 12:19:55 -07:00
committed by GitHub
parent f57b4412ec
commit e694a6f5d4
2 changed files with 77 additions and 88 deletions

View File

@@ -864,7 +864,12 @@ impl DataFlowGraph {
self.value_type( self.value_type(
self[inst] self[inst]
.typevar_operand(&self.value_lists) .typevar_operand(&self.value_lists)
.expect("Instruction format doesn't have a designated operand, bad opcode."), .unwrap_or_else(|| {
panic!(
"Instruction format for {:?} doesn't have a designated operand",
self[inst]
)
}),
) )
} else { } else {
self.value_type(self.first_result(inst)) self.value_type(self.first_result(inst))

View File

@@ -72,7 +72,7 @@ fn insert_call(
_rets: &'static [Type], _rets: &'static [Type],
) -> Result<()> { ) -> Result<()> {
assert_eq!(opcode, Opcode::Call, "only call handled at the moment"); assert_eq!(opcode, Opcode::Call, "only call handled at the moment");
let (sig, func_ref) = fgen.u.choose(&fgen.func_refs)?.clone(); let (sig, func_ref) = fgen.u.choose(&fgen.resources.func_refs)?.clone();
let actuals = fgen.generate_values_for_signature( let actuals = fgen.generate_values_for_signature(
builder, builder,
@@ -92,7 +92,7 @@ fn insert_stack_load(
) -> Result<()> { ) -> Result<()> {
let typevar = rets[0]; let typevar = rets[0];
let type_size = typevar.bytes(); let type_size = typevar.bytes();
let (slot, slot_size) = fgen.stack_slot_with_size(builder, type_size)?; let (slot, slot_size) = fgen.stack_slot_with_size(type_size)?;
let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32; let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;
let val = builder.ins().stack_load(typevar, slot, offset); let val = builder.ins().stack_load(typevar, slot, offset);
@@ -111,7 +111,7 @@ fn insert_stack_store(
) -> Result<()> { ) -> Result<()> {
let typevar = args[0]; let typevar = args[0];
let type_size = typevar.bytes(); let type_size = typevar.bytes();
let (slot, slot_size) = fgen.stack_slot_with_size(builder, type_size)?; let (slot, slot_size) = fgen.stack_slot_with_size(type_size)?;
let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32; let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;
let arg0 = fgen.get_variable_of_type(typevar)?; let arg0 = fgen.get_variable_of_type(typevar)?;
@@ -558,12 +558,17 @@ where
{ {
u: &'r mut Unstructured<'data>, u: &'r mut Unstructured<'data>,
config: &'r Config, config: &'r Config,
vars: Vec<(Type, Variable)>, resources: Resources,
}
#[derive(Default)]
struct Resources {
vars: HashMap<Type, Vec<Variable>>,
blocks: Vec<(Block, BlockSignature)>, blocks: Vec<(Block, BlockSignature)>,
blocks_without_params: Vec<Block>,
jump_tables: Vec<JumpTable>, jump_tables: Vec<JumpTable>,
func_refs: Vec<(Signature, FuncRef)>, func_refs: Vec<(Signature, FuncRef)>,
next_func_index: u32, stack_slots: Vec<(StackSlot, StackSize)>,
static_stack_slots: Vec<StackSlot>,
} }
impl<'r, 'data> FunctionGenerator<'r, 'data> impl<'r, 'data> FunctionGenerator<'r, 'data>
@@ -574,12 +579,7 @@ where
Self { Self {
u, u,
config, config,
vars: vec![], resources: Resources::default(),
blocks: vec![],
jump_tables: vec![],
func_refs: vec![],
static_stack_slots: vec![],
next_func_index: 0,
} }
} }
@@ -646,19 +646,12 @@ where
} }
/// Finds a stack slot with size of at least n bytes /// Finds a stack slot with size of at least n bytes
fn stack_slot_with_size( fn stack_slot_with_size(&mut self, n: u32) -> Result<(StackSlot, StackSize)> {
&mut self, let first = self
builder: &mut FunctionBuilder, .resources
n: u32, .stack_slots
) -> Result<(StackSlot, StackSize)> { .partition_point(|&(_slot, size)| size < n);
let opts: Vec<_> = self Ok(*self.u.choose(&self.resources.stack_slots[first..])?)
.static_stack_slots
.iter()
.map(|ss| (*ss, builder.func.sized_stack_slots[*ss].size))
.filter(|(_, size)| *size >= n)
.collect();
Ok(*self.u.choose(&opts[..])?)
} }
/// Generates an address that should allow for a store or a load. /// Generates an address that should allow for a store or a load.
@@ -679,7 +672,7 @@ where
// TODO: Currently our only source of addresses is stack_addr, but we should // TODO: Currently our only source of addresses is stack_addr, but we should
// add heap_addr, global_value, symbol_value eventually // add heap_addr, global_value, symbol_value eventually
let (addr, available_size) = { let (addr, available_size) = {
let (ss, slot_size) = self.stack_slot_with_size(builder, min_size)?; let (ss, slot_size) = self.stack_slot_with_size(min_size)?;
let max_offset = slot_size.saturating_sub(min_size); let max_offset = slot_size.saturating_sub(min_size);
let offset = self.u.int_in_range(0..=max_offset)? as i32; let offset = self.u.int_in_range(0..=max_offset)? as i32;
let base_addr = builder.ins().stack_addr(I64, ss, offset); let base_addr = builder.ins().stack_addr(I64, ss, offset);
@@ -697,27 +690,10 @@ where
Ok((addr, offset.into())) Ok((addr, offset.into()))
} }
/// Creates a new var
fn create_var(&mut self, builder: &mut FunctionBuilder, ty: Type) -> Result<Variable> {
let id = self.vars.len();
let var = Variable::new(id);
builder.declare_var(var, ty);
self.vars.push((ty, var));
Ok(var)
}
fn vars_of_type(&self, ty: Type) -> Vec<Variable> {
self.vars
.iter()
.filter(|(var_ty, _)| *var_ty == ty)
.map(|(_, v)| *v)
.collect()
}
/// Get a variable of type `ty` from the current function /// Get a variable of type `ty` from the current function
fn get_variable_of_type(&mut self, ty: Type) -> Result<Variable> { fn get_variable_of_type(&mut self, ty: Type) -> Result<Variable> {
let opts = self.vars_of_type(ty); let opts = self.resources.vars.get(&ty).map_or(&[][..], Vec::as_slice);
let var = self.u.choose(&opts[..])?; let var = self.u.choose(opts)?;
Ok(*var) Ok(*var)
} }
@@ -761,22 +737,12 @@ where
&mut self, &mut self,
builder: &mut FunctionBuilder, builder: &mut FunctionBuilder,
) -> Result<(Block, Vec<Value>)> { ) -> Result<(Block, Vec<Value>)> {
let block_targets = &self.blocks[1..]; let block_targets = &self.resources.blocks[1..];
let (block, signature) = self.u.choose(block_targets)?.clone(); let (block, signature) = self.u.choose(block_targets)?.clone();
let args = self.generate_values_for_signature(builder, signature.into_iter())?; let args = self.generate_values_for_signature(builder, signature.into_iter())?;
Ok((block, args)) Ok((block, args))
} }
/// Valid blocks for jump tables have to have no parameters in the signature, and must also
/// not be the first block.
fn generate_valid_jumptable_target_blocks(&mut self) -> Vec<Block> {
self.blocks[1..]
.iter()
.filter(|(_, sig)| sig.len() == 0)
.map(|(b, _)| *b)
.collect()
}
fn generate_values_for_signature<I: Iterator<Item = Type>>( fn generate_values_for_signature<I: Iterator<Item = Type>>(
&mut self, &mut self,
builder: &mut FunctionBuilder, builder: &mut FunctionBuilder,
@@ -813,10 +779,9 @@ where
let var = self.get_variable_of_type(I32)?; // br_table only supports I32 let var = self.get_variable_of_type(I32)?; // br_table only supports I32
let val = builder.use_var(var); let val = builder.use_var(var);
let valid_blocks = self.generate_valid_jumptable_target_blocks(); let default_block = *self.u.choose(&self.resources.blocks_without_params)?;
let default_block = *self.u.choose(&valid_blocks[..])?;
let jt = *self.u.choose(&self.jump_tables[..])?; let jt = *self.u.choose(&self.resources.jump_tables)?;
builder.ins().br_table(val, default_block, jt); builder.ins().br_table(val, default_block, jt);
Ok(()) Ok(())
} }
@@ -872,8 +837,7 @@ where
let switch_var = self.get_variable_of_type(_type)?; let switch_var = self.get_variable_of_type(_type)?;
let switch_val = builder.use_var(switch_var); let switch_val = builder.use_var(switch_var);
let valid_blocks = self.generate_valid_jumptable_target_blocks(); let default_block = *self.u.choose(&self.resources.blocks_without_params)?;
let default_block = *self.u.choose(&valid_blocks[..])?;
// Build this into a HashMap since we cannot have duplicate entries. // Build this into a HashMap since we cannot have duplicate entries.
let mut entries = HashMap::new(); let mut entries = HashMap::new();
@@ -894,7 +858,7 @@ where
// Build the switch entries // Build the switch entries
for i in 0..range_size { for i in 0..range_size {
let index = range_start.wrapping_add(i) % ty_max; let index = range_start.wrapping_add(i) % ty_max;
let block = *self.u.choose(&valid_blocks[..])?; let block = *self.u.choose(&self.resources.blocks_without_params)?;
entries.insert(index, block); entries.insert(index, block);
} }
} }
@@ -936,26 +900,25 @@ where
} }
fn generate_jumptables(&mut self, builder: &mut FunctionBuilder) -> Result<()> { fn generate_jumptables(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let valid_blocks = self.generate_valid_jumptable_target_blocks();
for _ in 0..self.param(&self.config.jump_tables_per_function)? { for _ in 0..self.param(&self.config.jump_tables_per_function)? {
let mut jt_data = JumpTableData::new(); let mut jt_data = JumpTableData::new();
for _ in 0..self.param(&self.config.jump_table_entries)? { for _ in 0..self.param(&self.config.jump_table_entries)? {
let block = *self.u.choose(&valid_blocks[..])?; let block = *self.u.choose(&self.resources.blocks_without_params)?;
jt_data.push_entry(block); jt_data.push_entry(block);
} }
self.jump_tables.push(builder.create_jump_table(jt_data)); self.resources
.jump_tables
.push(builder.create_jump_table(jt_data));
} }
Ok(()) Ok(())
} }
fn generate_funcrefs(&mut self, builder: &mut FunctionBuilder) -> Result<()> { fn generate_funcrefs(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
for _ in 0..self.param(&self.config.funcrefs_per_function)? { let count = self.param(&self.config.funcrefs_per_function)?;
for func_index in 0..count.try_into().unwrap() {
let (ext_name, sig) = if self.u.arbitrary::<bool>()? { let (ext_name, sig) = if self.u.arbitrary::<bool>()? {
let func_index = self.next_func_index;
self.next_func_index = self.next_func_index.wrapping_add(1);
let user_func_ref = builder let user_func_ref = builder
.func .func
.declare_imported_user_function(UserExternalName { .declare_imported_user_function(UserExternalName {
@@ -980,7 +943,7 @@ where
colocated: self.u.arbitrary()?, colocated: self.u.arbitrary()?,
}); });
self.func_refs.push((sig, func_ref)); self.resources.func_refs.push((sig, func_ref));
} }
Ok(()) Ok(())
@@ -991,9 +954,13 @@ where
let bytes = self.param(&self.config.static_stack_slot_size)? as u32; let bytes = self.param(&self.config.static_stack_slot_size)? as u32;
let ss_data = StackSlotData::new(StackSlotKind::ExplicitSlot, bytes); let ss_data = StackSlotData::new(StackSlotKind::ExplicitSlot, bytes);
let slot = builder.create_sized_stack_slot(ss_data); let slot = builder.create_sized_stack_slot(ss_data);
self.resources.stack_slots.push((slot, bytes));
self.static_stack_slots.push(slot);
} }
self.resources
.stack_slots
.sort_unstable_by_key(|&(_slot, bytes)| bytes);
Ok(()) Ok(())
} }
@@ -1005,8 +972,7 @@ where
let i16_zero = builder.ins().iconst(I16, 0); let i16_zero = builder.ins().iconst(I16, 0);
let i8_zero = builder.ins().iconst(I8, 0); let i8_zero = builder.ins().iconst(I8, 0);
for &slot in self.static_stack_slots.iter() { for &(slot, init_size) in self.resources.stack_slots.iter() {
let init_size = builder.func.sized_stack_slots[slot].size;
let mut size = init_size; let mut size = init_size;
// Insert the largest available store for the remaining size. // Insert the largest available store for the remaining size.
@@ -1038,7 +1004,7 @@ where
// the entry block. // the entry block.
let block_count = 1 + extra_block_count; let block_count = 1 + extra_block_count;
let blocks = (0..block_count) (0..block_count)
.map(|i| { .map(|i| {
let is_entry = i == 0; let is_entry = i == 0;
let block = builder.create_block(); let block = builder.create_block();
@@ -1063,9 +1029,7 @@ where
Ok((block, sig)) Ok((block, sig))
} }
}) })
.collect::<Result<Vec<_>>>()?; .collect()
Ok(blocks)
} }
fn generate_block_signature(&mut self) -> Result<BlockSignature> { fn generate_block_signature(&mut self) -> Result<BlockSignature> {
@@ -1080,21 +1044,33 @@ where
fn build_variable_pool(&mut self, builder: &mut FunctionBuilder) -> Result<()> { fn build_variable_pool(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let block = builder.current_block().unwrap(); let block = builder.current_block().unwrap();
let func_params = builder.func.signature.params.clone();
// Define variables for the function signature // Define variables for the function signature
for (i, param) in func_params.iter().enumerate() { let mut vars: Vec<_> = builder
let var = self.create_var(builder, param.value_type)?; .func
let block_param = builder.block_params(block)[i]; .signature
builder.def_var(var, block_param); .params
} .iter()
.map(|param| param.value_type)
.zip(builder.block_params(block).iter().copied())
.collect();
// Create a pool of vars that are going to be used in this function // Create a pool of vars that are going to be used in this function
for _ in 0..self.param(&self.config.vars_per_function)? { for _ in 0..self.param(&self.config.vars_per_function)? {
let ty = self.generate_type()?; let ty = self.generate_type()?;
let var = self.create_var(builder, ty)?;
let value = self.generate_const(builder, ty)?; let value = self.generate_const(builder, ty)?;
vars.push((ty, value));
}
for (id, (ty, value)) in vars.into_iter().enumerate() {
let var = Variable::new(id);
builder.declare_var(var, ty);
builder.def_var(var, value); builder.def_var(var, value);
self.resources
.vars
.entry(ty)
.or_insert_with(Vec::new)
.push(var);
} }
Ok(()) Ok(())
@@ -1117,7 +1093,15 @@ where
let mut builder = FunctionBuilder::new(&mut func, &mut fn_builder_ctx); let mut builder = FunctionBuilder::new(&mut func, &mut fn_builder_ctx);
self.blocks = self.generate_blocks(&mut builder, &sig)?; self.resources.blocks = self.generate_blocks(&mut builder, &sig)?;
// Valid blocks for jump tables have to have no parameters in the signature, and must also
// not be the first block.
self.resources.blocks_without_params = self.resources.blocks[1..]
.iter()
.filter(|(_, sig)| sig.len() == 0)
.map(|(b, _)| *b)
.collect();
// Function preamble // Function preamble
self.generate_jumptables(&mut builder)?; self.generate_jumptables(&mut builder)?;
@@ -1125,7 +1109,7 @@ where
self.generate_stack_slots(&mut builder)?; self.generate_stack_slots(&mut builder)?;
// Main instruction generation loop // Main instruction generation loop
for (i, (block, block_sig)) in self.blocks.clone().iter().enumerate() { for (i, (block, block_sig)) in self.resources.blocks.clone().iter().enumerate() {
let is_block0 = i == 0; let is_block0 = i == 0;
builder.switch_to_block(*block); builder.switch_to_block(*block);