Files
wasmtime/cranelift/fuzzgen/src/function_generator.rs
Afonso Bordado 4d2a2cfae6 cranelift: Use cranelift-jit in runtests (#4453)
* cranelift: Use JIT in runtests

Using `cranelift-jit` in run tests allows us to preform relocations and
libcalls. This is important since some instruction lowerings fallback
to libcall's when an extension is missing, or when it's too complicated
to implement manually.

This is also a first step to being able to test `call`'s between functions
in the runtest suite. It should also make it easier to eventually test
TLS relocations, symbol resolution and ABI's.

Another benefit of this is that we also get to test the JIT more, since
it now runs the runtests, and gets some fuzzing via `fuzzgen` (which
uses the `SingleFunctionCompiler`).

This change causes regressions in terms of runtime for the filetests.
I haven't done any serious benchmarking but what I've been seeing is
that it now takes about ~3 seconds to run the testsuite while it
previously took around 2 seconds.

* Add FMA tests for X86
2022-08-09 14:54:25 -07:00

758 lines
27 KiB
Rust

use crate::codegen::ir::{ArgumentExtension, ArgumentPurpose, ValueList};
use crate::config::Config;
use anyhow::Result;
use arbitrary::{Arbitrary, Unstructured};
use cranelift::codegen::ir::types::*;
use cranelift::codegen::ir::{
AbiParam, Block, ExternalName, Function, JumpTable, Opcode, Signature, StackSlot, Type, Value,
};
use cranelift::codegen::isa::CallConv;
use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Switch, Variable};
use cranelift::prelude::{
EntityRef, InstBuilder, IntCC, JumpTableData, StackSlotData, StackSlotKind,
};
use std::collections::HashMap;
use std::ops::RangeInclusive;
type BlockSignature = Vec<Type>;
fn insert_opcode(
fgen: &mut FunctionGenerator,
builder: &mut FunctionBuilder,
opcode: Opcode,
args: &'static [Type],
rets: &'static [Type],
) -> Result<()> {
let mut arg_vals = ValueList::new();
for &arg in args.into_iter() {
let var = fgen.get_variable_of_type(arg)?;
let val = builder.use_var(var);
arg_vals.push(val, &mut builder.func.dfg.value_lists);
}
let typevar = rets.first().copied().unwrap_or(INVALID);
let (inst, dfg) = builder.ins().MultiAry(opcode, typevar, arg_vals);
let results = dfg.inst_results(inst).to_vec();
for (val, &ty) in results.into_iter().zip(rets) {
let var = fgen.get_variable_of_type(ty)?;
builder.def_var(var, val);
}
Ok(())
}
fn insert_stack_load(
fgen: &mut FunctionGenerator,
builder: &mut FunctionBuilder,
_opcode: Opcode,
_args: &'static [Type],
rets: &'static [Type],
) -> Result<()> {
let typevar = rets[0];
let slot = fgen.stack_slot_with_size(builder, typevar.bytes())?;
let slot_size = builder.func.sized_stack_slots[slot].size;
let type_size = typevar.bytes();
let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;
let val = builder.ins().stack_load(typevar, slot, offset);
let var = fgen.get_variable_of_type(typevar)?;
builder.def_var(var, val);
Ok(())
}
fn insert_stack_store(
fgen: &mut FunctionGenerator,
builder: &mut FunctionBuilder,
_opcode: Opcode,
args: &'static [Type],
_rets: &'static [Type],
) -> Result<()> {
let typevar = args[0];
let slot = fgen.stack_slot_with_size(builder, typevar.bytes())?;
let slot_size = builder.func.sized_stack_slots[slot].size;
let type_size = typevar.bytes();
let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;
let arg0 = fgen.get_variable_of_type(typevar)?;
let arg0 = builder.use_var(arg0);
builder.ins().stack_store(arg0, slot, offset);
Ok(())
}
fn insert_const(
fgen: &mut FunctionGenerator,
builder: &mut FunctionBuilder,
_opcode: Opcode,
_args: &'static [Type],
rets: &'static [Type],
) -> Result<()> {
let typevar = rets[0];
let var = fgen.get_variable_of_type(typevar)?;
let val = fgen.generate_const(builder, typevar)?;
builder.def_var(var, val);
Ok(())
}
type OpcodeInserter = fn(
fgen: &mut FunctionGenerator,
builder: &mut FunctionBuilder,
Opcode,
&'static [Type],
&'static [Type],
) -> Result<()>;
// TODO: Derive this from the `cranelift-meta` generator.
const OPCODE_SIGNATURES: &'static [(
Opcode,
&'static [Type], // Args
&'static [Type], // Rets
OpcodeInserter,
)] = &[
(Opcode::Nop, &[], &[], insert_opcode),
// Iadd
(Opcode::Iadd, &[I8, I8], &[I8], insert_opcode),
(Opcode::Iadd, &[I16, I16], &[I16], insert_opcode),
(Opcode::Iadd, &[I32, I32], &[I32], insert_opcode),
(Opcode::Iadd, &[I64, I64], &[I64], insert_opcode),
(Opcode::Iadd, &[I128, I128], &[I128], insert_opcode),
// Isub
(Opcode::Isub, &[I8, I8], &[I8], insert_opcode),
(Opcode::Isub, &[I16, I16], &[I16], insert_opcode),
(Opcode::Isub, &[I32, I32], &[I32], insert_opcode),
(Opcode::Isub, &[I64, I64], &[I64], insert_opcode),
(Opcode::Isub, &[I128, I128], &[I128], insert_opcode),
// Imul
(Opcode::Imul, &[I8, I8], &[I8], insert_opcode),
(Opcode::Imul, &[I16, I16], &[I16], insert_opcode),
(Opcode::Imul, &[I32, I32], &[I32], insert_opcode),
(Opcode::Imul, &[I64, I64], &[I64], insert_opcode),
(Opcode::Imul, &[I128, I128], &[I128], insert_opcode),
// Udiv
(Opcode::Udiv, &[I8, I8], &[I8], insert_opcode),
(Opcode::Udiv, &[I16, I16], &[I16], insert_opcode),
(Opcode::Udiv, &[I32, I32], &[I32], insert_opcode),
(Opcode::Udiv, &[I64, I64], &[I64], insert_opcode),
(Opcode::Udiv, &[I128, I128], &[I128], insert_opcode),
// Sdiv
(Opcode::Sdiv, &[I8, I8], &[I8], insert_opcode),
(Opcode::Sdiv, &[I16, I16], &[I16], insert_opcode),
(Opcode::Sdiv, &[I32, I32], &[I32], insert_opcode),
(Opcode::Sdiv, &[I64, I64], &[I64], insert_opcode),
(Opcode::Sdiv, &[I128, I128], &[I128], insert_opcode),
// Fadd
(Opcode::Fadd, &[F32, F32], &[F32], insert_opcode),
(Opcode::Fadd, &[F64, F64], &[F64], insert_opcode),
// Fmul
(Opcode::Fmul, &[F32, F32], &[F32], insert_opcode),
(Opcode::Fmul, &[F64, F64], &[F64], insert_opcode),
// Fsub
(Opcode::Fsub, &[F32, F32], &[F32], insert_opcode),
(Opcode::Fsub, &[F64, F64], &[F64], insert_opcode),
// Fdiv
(Opcode::Fdiv, &[F32, F32], &[F32], insert_opcode),
(Opcode::Fdiv, &[F64, F64], &[F64], insert_opcode),
// Fmin
(Opcode::Fmin, &[F32, F32], &[F32], insert_opcode),
(Opcode::Fmin, &[F64, F64], &[F64], insert_opcode),
// Fmax
(Opcode::Fmax, &[F32, F32], &[F32], insert_opcode),
(Opcode::Fmax, &[F64, F64], &[F64], insert_opcode),
// FminPseudo
(Opcode::FminPseudo, &[F32, F32], &[F32], insert_opcode),
(Opcode::FminPseudo, &[F64, F64], &[F64], insert_opcode),
// FmaxPseudo
(Opcode::FmaxPseudo, &[F32, F32], &[F32], insert_opcode),
(Opcode::FmaxPseudo, &[F64, F64], &[F64], insert_opcode),
// Fcopysign
(Opcode::Fcopysign, &[F32, F32], &[F32], insert_opcode),
(Opcode::Fcopysign, &[F64, F64], &[F64], insert_opcode),
// Fma
(Opcode::Fma, &[F32, F32, F32], &[F32], insert_opcode),
(Opcode::Fma, &[F64, F64, F64], &[F64], insert_opcode),
// Fabs
(Opcode::Fabs, &[F32], &[F32], insert_opcode),
(Opcode::Fabs, &[F64], &[F64], insert_opcode),
// Fneg
(Opcode::Fneg, &[F32], &[F32], insert_opcode),
(Opcode::Fneg, &[F64], &[F64], insert_opcode),
// Sqrt
(Opcode::Sqrt, &[F32], &[F32], insert_opcode),
(Opcode::Sqrt, &[F64], &[F64], insert_opcode),
// Ceil
(Opcode::Ceil, &[F32], &[F32], insert_opcode),
(Opcode::Ceil, &[F64], &[F64], insert_opcode),
// Floor
(Opcode::Floor, &[F32], &[F32], insert_opcode),
(Opcode::Floor, &[F64], &[F64], insert_opcode),
// Trunc
(Opcode::Trunc, &[F32], &[F32], insert_opcode),
(Opcode::Trunc, &[F64], &[F64], insert_opcode),
// Nearest
(Opcode::Nearest, &[F32], &[F32], insert_opcode),
(Opcode::Nearest, &[F64], &[F64], insert_opcode),
// Stack Access
(Opcode::StackStore, &[I8], &[], insert_stack_store),
(Opcode::StackStore, &[I16], &[], insert_stack_store),
(Opcode::StackStore, &[I32], &[], insert_stack_store),
(Opcode::StackStore, &[I64], &[], insert_stack_store),
(Opcode::StackStore, &[I128], &[], insert_stack_store),
(Opcode::StackLoad, &[], &[I8], insert_stack_load),
(Opcode::StackLoad, &[], &[I16], insert_stack_load),
(Opcode::StackLoad, &[], &[I32], insert_stack_load),
(Opcode::StackLoad, &[], &[I64], insert_stack_load),
(Opcode::StackLoad, &[], &[I128], insert_stack_load),
// Integer Consts
(Opcode::Iconst, &[], &[I8], insert_const),
(Opcode::Iconst, &[], &[I16], insert_const),
(Opcode::Iconst, &[], &[I32], insert_const),
(Opcode::Iconst, &[], &[I64], insert_const),
(Opcode::Iconst, &[], &[I128], insert_const),
// Float Consts
(Opcode::F32const, &[], &[F32], insert_const),
(Opcode::F64const, &[], &[F64], insert_const),
// Bool Consts
(Opcode::Bconst, &[], &[B1], insert_const),
];
pub struct FunctionGenerator<'r, 'data>
where
'data: 'r,
{
u: &'r mut Unstructured<'data>,
config: &'r Config,
vars: Vec<(Type, Variable)>,
blocks: Vec<(Block, BlockSignature)>,
jump_tables: Vec<JumpTable>,
static_stack_slots: Vec<StackSlot>,
}
impl<'r, 'data> FunctionGenerator<'r, 'data>
where
'data: 'r,
{
pub fn new(u: &'r mut Unstructured<'data>, config: &'r Config) -> Self {
Self {
u,
config,
vars: vec![],
blocks: vec![],
jump_tables: vec![],
static_stack_slots: vec![],
}
}
/// Generates a random value for config `param`
fn param(&mut self, param: &RangeInclusive<usize>) -> Result<usize> {
Ok(self.u.int_in_range(param.clone())?)
}
fn generate_callconv(&mut self) -> Result<CallConv> {
// TODO: Generate random CallConvs per target
Ok(CallConv::SystemV)
}
fn generate_intcc(&mut self) -> Result<IntCC> {
Ok(*self.u.choose(
&[
IntCC::Equal,
IntCC::NotEqual,
IntCC::SignedLessThan,
IntCC::SignedGreaterThanOrEqual,
IntCC::SignedGreaterThan,
IntCC::SignedLessThanOrEqual,
IntCC::UnsignedLessThan,
IntCC::UnsignedGreaterThanOrEqual,
IntCC::UnsignedGreaterThan,
IntCC::UnsignedLessThanOrEqual,
IntCC::Overflow,
IntCC::NotOverflow,
][..],
)?)
}
fn generate_type(&mut self) -> Result<Type> {
// TODO: It would be nice if we could get these directly from cranelift
let scalars = [
// IFLAGS, FFLAGS,
B1, // B8, B16, B32, B64, B128,
I8, I16, I32, I64, I128, F32, F64,
// R32, R64,
];
// TODO: vector types
let ty = self.u.choose(&scalars[..])?;
Ok(*ty)
}
fn generate_abi_param(&mut self) -> Result<AbiParam> {
let value_type = self.generate_type()?;
// TODO: There are more argument purposes to be explored...
let purpose = ArgumentPurpose::Normal;
let extension = match self.u.int_in_range(0..=2)? {
2 => ArgumentExtension::Sext,
1 => ArgumentExtension::Uext,
_ => ArgumentExtension::None,
};
Ok(AbiParam {
value_type,
purpose,
extension,
})
}
fn generate_signature(&mut self) -> Result<Signature> {
let callconv = self.generate_callconv()?;
let mut sig = Signature::new(callconv);
for _ in 0..self.param(&self.config.signature_params)? {
sig.params.push(self.generate_abi_param()?);
}
for _ in 0..self.param(&self.config.signature_rets)? {
sig.returns.push(self.generate_abi_param()?);
}
Ok(sig)
}
/// Finds a stack slot with size of at least n bytes
fn stack_slot_with_size(&mut self, builder: &mut FunctionBuilder, n: u32) -> Result<StackSlot> {
let opts: Vec<_> = self
.static_stack_slots
.iter()
.filter(|ss| builder.func.sized_stack_slots[**ss].size >= n)
.map(|ss| *ss)
.collect();
Ok(*self.u.choose(&opts[..])?)
}
/// 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
fn get_variable_of_type(&mut self, ty: Type) -> Result<Variable> {
let opts = self.vars_of_type(ty);
let var = self.u.choose(&opts[..])?;
Ok(*var)
}
/// Generates an instruction(`iconst`/`fconst`/etc...) to introduce a constant value
fn generate_const(&mut self, builder: &mut FunctionBuilder, ty: Type) -> Result<Value> {
Ok(match ty {
I128 => {
// See: https://github.com/bytecodealliance/wasmtime/issues/2906
let hi = builder.ins().iconst(I64, self.u.arbitrary::<i64>()?);
let lo = builder.ins().iconst(I64, self.u.arbitrary::<i64>()?);
builder.ins().iconcat(lo, hi)
}
ty if ty.is_int() => {
let imm64 = match ty {
I8 => self.u.arbitrary::<i8>()? as i64,
I16 => self.u.arbitrary::<i16>()? as i64,
I32 => self.u.arbitrary::<i32>()? as i64,
I64 => self.u.arbitrary::<i64>()?,
_ => unreachable!(),
};
builder.ins().iconst(ty, imm64)
}
ty if ty.is_bool() => builder.ins().bconst(ty, bool::arbitrary(self.u)?),
// f{32,64}::arbitrary does not generate a bunch of important values
// such as Signaling NaN's / NaN's with payload, so generate floats from integers.
F32 => builder
.ins()
.f32const(f32::from_bits(u32::arbitrary(self.u)?)),
F64 => builder
.ins()
.f64const(f64::from_bits(u64::arbitrary(self.u)?)),
_ => unimplemented!(),
})
}
/// Chooses a random block which can be targeted by a jump / branch.
/// This means any block that is not the first block.
///
/// For convenience we also generate values that match the block's signature
fn generate_target_block(
&mut self,
builder: &mut FunctionBuilder,
) -> Result<(Block, Vec<Value>)> {
let block_targets = &self.blocks[1..];
let (block, signature) = self.u.choose(block_targets)?.clone();
let args = self.generate_values_for_signature(builder, signature.into_iter())?;
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>>(
&mut self,
builder: &mut FunctionBuilder,
signature: I,
) -> Result<Vec<Value>> {
signature
.map(|ty| {
let var = self.get_variable_of_type(ty)?;
let val = builder.use_var(var);
Ok(val)
})
.collect()
}
fn generate_return(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let types: Vec<Type> = {
let rets = &builder.func.signature.returns;
rets.iter().map(|p| p.value_type).collect()
};
let vals = self.generate_values_for_signature(builder, types.into_iter())?;
builder.ins().return_(&vals[..]);
Ok(())
}
fn generate_jump(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let (block, args) = self.generate_target_block(builder)?;
builder.ins().jump(block, &args[..]);
Ok(())
}
/// Generates a br_table into a random block
fn generate_br_table(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let var = self.get_variable_of_type(I32)?; // br_table only supports I32
let val = builder.use_var(var);
let valid_blocks = self.generate_valid_jumptable_target_blocks();
let default_block = *self.u.choose(&valid_blocks[..])?;
let jt = *self.u.choose(&self.jump_tables[..])?;
builder.ins().br_table(val, default_block, jt);
Ok(())
}
/// Generates a brz/brnz into a random block
fn generate_br(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let (block, args) = self.generate_target_block(builder)?;
let condbr_types = [I8, I16, I32, I64, I128, B1];
let _type = *self.u.choose(&condbr_types[..])?;
let var = self.get_variable_of_type(_type)?;
let val = builder.use_var(var);
if bool::arbitrary(self.u)? {
builder.ins().brz(val, block, &args[..]);
} else {
builder.ins().brnz(val, block, &args[..]);
}
// After brz/brnz we must generate a jump
self.generate_jump(builder)?;
Ok(())
}
fn generate_bricmp(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let (block, args) = self.generate_target_block(builder)?;
let cond = self.generate_intcc()?;
let bricmp_types = [
I8, I16, I32,
I64,
// I128 - TODO: https://github.com/bytecodealliance/wasmtime/issues/4406
];
let _type = *self.u.choose(&bricmp_types[..])?;
let lhs_var = self.get_variable_of_type(_type)?;
let lhs_val = builder.use_var(lhs_var);
let rhs_var = self.get_variable_of_type(_type)?;
let rhs_val = builder.use_var(rhs_var);
builder
.ins()
.br_icmp(cond, lhs_val, rhs_val, block, &args[..]);
// After bricmp's we must generate a jump
self.generate_jump(builder)?;
Ok(())
}
fn generate_switch(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let _type = *self.u.choose(&[I8, I16, I32, I64, I128][..])?;
let switch_var = self.get_variable_of_type(_type)?;
let switch_val = builder.use_var(switch_var);
let valid_blocks = self.generate_valid_jumptable_target_blocks();
let default_block = *self.u.choose(&valid_blocks[..])?;
// Build this into a HashMap since we cannot have duplicate entries.
let mut entries = HashMap::new();
for _ in 0..self.param(&self.config.switch_cases)? {
// The Switch API only allows for entries that are addressable by the index type
// so we need to limit the range of values that we generate.
let (ty_min, ty_max) = _type.bounds(false);
let range_start = self.u.int_in_range(ty_min..=ty_max)?;
// We can either insert a contiguous range of blocks or a individual block
// This is done because the Switch API specializes contiguous ranges.
let range_size = if bool::arbitrary(self.u)? {
1
} else {
self.param(&self.config.switch_max_range_size)?
} as u128;
// Build the switch entries
for i in 0..range_size {
let index = range_start.wrapping_add(i) % ty_max;
let block = *self.u.choose(&valid_blocks[..])?;
entries.insert(index, block);
}
}
let mut switch = Switch::new();
for (entry, block) in entries.into_iter() {
switch.set_entry(entry, block);
}
switch.emit(builder, switch_val, default_block);
Ok(())
}
/// We always need to exit safely out of a block.
/// This either means a jump into another block or a return.
fn finalize_block(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let gen = self.u.choose(
&[
Self::generate_bricmp,
Self::generate_br,
Self::generate_br_table,
Self::generate_jump,
Self::generate_return,
Self::generate_switch,
][..],
)?;
gen(self, builder)
}
/// Fills the current block with random instructions
fn generate_instructions(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
for _ in 0..self.param(&self.config.instructions_per_block)? {
let (op, args, rets, inserter) = *self.u.choose(OPCODE_SIGNATURES)?;
inserter(self, builder, op, args, rets)?;
}
Ok(())
}
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)? {
let mut jt_data = JumpTableData::new();
for _ in 0..self.param(&self.config.jump_table_entries)? {
let block = *self.u.choose(&valid_blocks[..])?;
jt_data.push_entry(block);
}
self.jump_tables.push(builder.create_jump_table(jt_data));
}
Ok(())
}
fn generate_stack_slots(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
for _ in 0..self.param(&self.config.static_stack_slots_per_function)? {
let bytes = self.param(&self.config.static_stack_slot_size)? as u32;
let ss_data = StackSlotData::new(StackSlotKind::ExplicitSlot, bytes);
let slot = builder.create_sized_stack_slot(ss_data);
self.static_stack_slots.push(slot);
}
Ok(())
}
/// Zero initializes the stack slot by inserting `stack_store`'s.
fn initialize_stack_slots(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let i128_zero = builder.ins().iconst(I128, 0);
let i64_zero = builder.ins().iconst(I64, 0);
let i32_zero = builder.ins().iconst(I32, 0);
let i16_zero = builder.ins().iconst(I16, 0);
let i8_zero = builder.ins().iconst(I8, 0);
for &slot in self.static_stack_slots.iter() {
let init_size = builder.func.sized_stack_slots[slot].size;
let mut size = init_size;
// Insert the largest available store for the remaining size.
while size != 0 {
let offset = (init_size - size) as i32;
let (val, filled) = match size {
sz if sz / 16 > 0 => (i128_zero, 16),
sz if sz / 8 > 0 => (i64_zero, 8),
sz if sz / 4 > 0 => (i32_zero, 4),
sz if sz / 2 > 0 => (i16_zero, 2),
_ => (i8_zero, 1),
};
builder.ins().stack_store(val, slot, offset);
size -= filled;
}
}
Ok(())
}
/// Creates a random amount of blocks in this function
fn generate_blocks(
&mut self,
builder: &mut FunctionBuilder,
sig: &Signature,
) -> Result<Vec<(Block, BlockSignature)>> {
let extra_block_count = self.param(&self.config.blocks_per_function)?;
// We must always have at least one block, so we generate the "extra" blocks and add 1 for
// the entry block.
let block_count = 1 + extra_block_count;
let blocks = (0..block_count)
.map(|i| {
let is_entry = i == 0;
let block = builder.create_block();
// Optionally mark blocks that are not the entry block as cold
if !is_entry {
if bool::arbitrary(self.u)? {
builder.set_cold_block(block);
}
}
// The first block has to have the function signature, but for the rest of them we generate
// a random signature;
if is_entry {
builder.append_block_params_for_function_params(block);
Ok((block, sig.params.iter().map(|a| a.value_type).collect()))
} else {
let sig = self.generate_block_signature()?;
sig.iter().for_each(|ty| {
builder.append_block_param(block, *ty);
});
Ok((block, sig))
}
})
.collect::<Result<Vec<_>>>()?;
Ok(blocks)
}
fn generate_block_signature(&mut self) -> Result<BlockSignature> {
let param_count = self.param(&self.config.block_signature_params)?;
let mut params = Vec::with_capacity(param_count);
for _ in 0..param_count {
params.push(self.generate_type()?);
}
Ok(params)
}
fn build_variable_pool(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let block = builder.current_block().unwrap();
let func_params = builder.func.signature.params.clone();
// Define variables for the function signature
for (i, param) in func_params.iter().enumerate() {
let var = self.create_var(builder, param.value_type)?;
let block_param = builder.block_params(block)[i];
builder.def_var(var, block_param);
}
// Create a pool of vars that are going to be used in this function
for _ in 0..self.param(&self.config.vars_per_function)? {
let ty = self.generate_type()?;
let var = self.create_var(builder, ty)?;
let value = self.generate_const(builder, ty)?;
builder.def_var(var, value);
}
Ok(())
}
/// We generate a function in multiple stages:
///
/// * First we generate a random number of empty blocks
/// * Then we generate a random pool of variables to be used throughout the function
/// * We then visit each block and generate random instructions
///
/// Because we generate all blocks and variables up front we already know everything that
/// we need when generating instructions (i.e. jump targets / variables)
pub fn generate(mut self) -> Result<Function> {
let sig = self.generate_signature()?;
let mut fn_builder_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ExternalName::user(0, 1), sig.clone());
let mut builder = FunctionBuilder::new(&mut func, &mut fn_builder_ctx);
self.blocks = self.generate_blocks(&mut builder, &sig)?;
// Function preamble
self.generate_jumptables(&mut builder)?;
self.generate_stack_slots(&mut builder)?;
// Main instruction generation loop
for (i, (block, block_sig)) in self.blocks.clone().iter().enumerate() {
let is_block0 = i == 0;
builder.switch_to_block(*block);
if is_block0 {
// The first block is special because we must create variables both for the
// block signature and for the variable pool. Additionally, we must also define
// initial values for all variables that are not the function signature.
self.build_variable_pool(&mut builder)?;
// Stack slots have random bytes at the beginning of the function
// initialize them to a constant value so that execution stays predictable.
self.initialize_stack_slots(&mut builder)?;
} else {
// Define variables for the block params
for (i, ty) in block_sig.iter().enumerate() {
let var = self.get_variable_of_type(*ty)?;
let block_param = builder.block_params(*block)[i];
builder.def_var(var, block_param);
}
}
// Generate block instructions
self.generate_instructions(&mut builder)?;
self.finalize_block(&mut builder)?;
}
builder.seal_all_blocks();
builder.finalize();
Ok(func)
}
}