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
9 changes: 9 additions & 0 deletions cranelift/fuzzgen/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ pub struct Config {
/// Number of variables that we allocate per function
/// This value does not include the signature params
pub vars_per_function: RangeInclusive<usize>,
/// Number of blocks that we generate per function.
/// This value does not include the entry block
pub blocks_per_function: RangeInclusive<usize>,
/// Number of params a block should take
/// This value does not apply to block0 which takes the function params
/// and is thus governed by `signature_params`
pub block_signature_params: RangeInclusive<usize>,
}

impl Default for Config {
Expand All @@ -19,6 +26,8 @@ impl Default for Config {
signature_rets: 0..=16,
instructions_per_block: 0..=64,
vars_per_function: 0..=16,
blocks_per_function: 0..=16,
block_signature_params: 0..=16,
}
}
}
301 changes: 252 additions & 49 deletions cranelift/fuzzgen/src/function_generator.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use crate::config::Config;
use anyhow::Result;
use arbitrary::Unstructured;
use arbitrary::{Arbitrary, Unstructured};
use cranelift::codegen::ir::types::*;
use cranelift::codegen::ir::{AbiParam, ExternalName, Function, Opcode, Signature, Type, Value};
use cranelift::codegen::ir::{
AbiParam, Block, ExternalName, Function, Opcode, Signature, Type, Value,
};
use cranelift::codegen::isa::CallConv;
use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
use cranelift::prelude::{EntityRef, InstBuilder};
use cranelift::prelude::{EntityRef, InstBuilder, IntCC};

type BlockSignature = Vec<Type>;

fn insert_opcode_arity_0(
_fgen: &mut FunctionGenerator,
Expand Down Expand Up @@ -92,6 +96,7 @@ where
u: &'r mut Unstructured<'data>,
config: &'r Config,
vars: Vec<(Type, Variable)>,
blocks: Vec<(Block, BlockSignature)>,
}

impl<'r, 'data> FunctionGenerator<'r, 'data>
Expand All @@ -103,6 +108,7 @@ where
u,
config,
vars: vec![],
blocks: vec![],
}
}

Expand All @@ -111,11 +117,30 @@ where
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,
B1, // B8, B16, B32, B64, B128,
I8, I16, I32, I64,
// I128,
// F32, F64,
Expand Down Expand Up @@ -174,78 +199,256 @@ where

/// Generates an instruction(`iconst`/`fconst`/etc...) to introduce a constant value
fn generate_const(&mut self, builder: &mut FunctionBuilder, ty: Type) -> Result<Value> {
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!(),
};
let val = builder.ins().iconst(ty, imm64);
Ok(match ty {
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(B1, bool::arbitrary(self.u)?),
_ => unimplemented!(),
})
}

Ok(val)
/// 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))
}

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 ret_params = builder.func.signature.returns.clone();
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())?;

let vars = ret_params
.iter()
.map(|p| self.get_variable_of_type(p.value_type))
.collect::<Result<Vec<_>>>()?;
builder.ins().return_(&vals[..]);
Ok(())
}

let vals = vars
.into_iter()
.map(|v| builder.use_var(v))
.collect::<Vec<_>>();
fn generate_jump(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let (block, args) = self.generate_target_block(builder)?;
builder.ins().jump(block, &args[..]);
Ok(())
}

builder.ins().return_(&vals[..]);
/// 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, // TODO: 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(())
}

/// Inserts a random instruction into the block
fn generate_instruction(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
let (op, args, rets, inserter) = *self.u.choose(OPCODE_SIGNATURES)?;
inserter(self, builder, op, args, rets)
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, // TODO: I128
];
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(())
}

pub fn generate(mut self) -> Result<Function> {
let sig = self.generate_signature()?;
/// 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_jump,
Self::generate_return,
][..],
)?;

gen(self, builder)
}

let mut fn_builder_ctx = FunctionBuilderContext::new();
let mut func = Function::with_name_signature(ExternalName::user(0, 0), sig.clone());
/// Fills the current block with random instructions
fn generate_instructions(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
for _ in 0..self
.u
.int_in_range(self.config.instructions_per_block.clone())?
{
let (op, args, rets, inserter) = *self.u.choose(OPCODE_SIGNATURES)?;
inserter(self, builder, op, args, rets)?;
}

let mut builder = FunctionBuilder::new(&mut func, &mut fn_builder_ctx);
let block0 = builder.create_block();
builder.append_block_params_for_function_params(block0);
builder.switch_to_block(block0);
builder.seal_block(block0);
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
.u
.int_in_range(self.config.blocks_per_function.clone())?;

// 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 block = builder.create_block();

// The first block has to have the function signature, but for the rest of them we generate
// a random signature;
if i == 0 {
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
.u
.int_in_range(self.config.block_signature_params.clone())?;

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 sig.params.iter().enumerate() {
let var = self.create_var(&mut builder, param.value_type)?;
let block_param = builder.block_params(block0)[i];
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.u.int_in_range(self.config.vars_per_function.clone())? {
let ty = self.generate_type()?;
let var = self.create_var(&mut builder, ty)?;
let value = self.generate_const(&mut builder, ty)?;
let var = self.create_var(builder, ty)?;
let value = self.generate_const(builder, ty)?;
builder.def_var(var, value);
}

for _ in 0..self
.u
.int_in_range(self.config.instructions_per_block.clone())?
{
self.generate_instruction(&mut builder)?;
}
Ok(())
}

// TODO: We should make this part of the regular instruction selection
self.generate_return(&mut builder)?;
/// 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, 0), sig.clone());

let mut builder = FunctionBuilder::new(&mut func, &mut fn_builder_ctx);

self.blocks = self.generate_blocks(&mut builder, &sig)?;

// 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)?;
} 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)
Expand Down
Loading