fuzzgen: Refactor name and signature generation (#5764)
* fuzzgen: Move cranelift type generation into CraneliftArbitrary * fuzzgen: Deduplicate DataValue generation * fuzzgen: Remove unused code * fuzzgen: Pass allowed function calls into `FunctionGenerator`
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
use crate::codegen::ir::{ArgumentExtension, ArgumentPurpose};
|
||||
use crate::config::Config;
|
||||
use crate::cranelift_arbitrary::CraneliftArbitrary;
|
||||
use anyhow::Result;
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use cranelift::codegen::data_value::DataValue;
|
||||
use cranelift::codegen::ir::instructions::InstructionFormat;
|
||||
use cranelift::codegen::ir::stackslot::StackSize;
|
||||
use cranelift::codegen::ir::{types::*, FuncRef, LibCall, UserExternalName, UserFuncName};
|
||||
use cranelift::codegen::ir::{
|
||||
AbiParam, Block, ExternalName, Function, Opcode, Signature, StackSlot, Type, Value,
|
||||
Block, ExternalName, Function, Opcode, Signature, StackSlot, Type, Value,
|
||||
};
|
||||
use cranelift::codegen::isa::CallConv;
|
||||
use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Switch, Variable};
|
||||
@@ -1279,16 +1280,6 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
|
||||
(Opcode::Call, &[], &[], insert_call),
|
||||
];
|
||||
|
||||
/// These libcalls need a interpreter implementation in `cranelift-fuzzgen.rs`
|
||||
const ALLOWED_LIBCALLS: &'static [LibCall] = &[
|
||||
LibCall::CeilF32,
|
||||
LibCall::CeilF64,
|
||||
LibCall::FloorF32,
|
||||
LibCall::FloorF64,
|
||||
LibCall::TruncF32,
|
||||
LibCall::TruncF64,
|
||||
];
|
||||
|
||||
pub struct FunctionGenerator<'r, 'data>
|
||||
where
|
||||
'data: 'r,
|
||||
@@ -1297,6 +1288,8 @@ where
|
||||
config: &'r Config,
|
||||
resources: Resources,
|
||||
target_triple: Triple,
|
||||
name: UserFuncName,
|
||||
signature: Signature,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -1325,6 +1318,8 @@ struct Resources {
|
||||
block_terminators: Vec<BlockTerminator>,
|
||||
func_refs: Vec<(Signature, FuncRef)>,
|
||||
stack_slots: Vec<(StackSlot, StackSize)>,
|
||||
usercalls: Vec<(UserExternalName, Signature)>,
|
||||
libcalls: Vec<LibCall>,
|
||||
}
|
||||
|
||||
impl Resources {
|
||||
@@ -1359,12 +1354,26 @@ impl<'r, 'data> FunctionGenerator<'r, 'data>
|
||||
where
|
||||
'data: 'r,
|
||||
{
|
||||
pub fn new(u: &'r mut Unstructured<'data>, config: &'r Config, target_triple: Triple) -> Self {
|
||||
pub fn new(
|
||||
u: &'r mut Unstructured<'data>,
|
||||
config: &'r Config,
|
||||
target_triple: Triple,
|
||||
name: UserFuncName,
|
||||
signature: Signature,
|
||||
usercalls: Vec<(UserExternalName, Signature)>,
|
||||
libcalls: Vec<LibCall>,
|
||||
) -> Self {
|
||||
Self {
|
||||
u,
|
||||
config,
|
||||
resources: Resources::default(),
|
||||
resources: Resources {
|
||||
usercalls,
|
||||
libcalls,
|
||||
..Resources::default()
|
||||
},
|
||||
target_triple,
|
||||
name,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1373,65 +1382,12 @@ where
|
||||
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 system_callconv(&mut self) -> CallConv {
|
||||
// TODO: This currently only runs on linux, so this is the only choice
|
||||
// We should improve this once we generate flags and targets
|
||||
CallConv::SystemV
|
||||
}
|
||||
|
||||
fn generate_type(&mut self) -> Result<Type> {
|
||||
// TODO: It would be nice if we could get these directly from cranelift
|
||||
let scalars = [
|
||||
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 = if value_type.is_int() {
|
||||
*self.u.choose(&[
|
||||
ArgumentExtension::Sext,
|
||||
ArgumentExtension::Uext,
|
||||
ArgumentExtension::None,
|
||||
])?
|
||||
} else {
|
||||
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, n: u32) -> Result<(StackSlot, StackSize)> {
|
||||
let first = self
|
||||
@@ -1494,31 +1450,18 @@ where
|
||||
|
||||
/// 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>()?);
|
||||
Ok(match self.u.datavalue(ty)? {
|
||||
DataValue::I8(i) => builder.ins().iconst(ty, i as i64),
|
||||
DataValue::I16(i) => builder.ins().iconst(ty, i as i64),
|
||||
DataValue::I32(i) => builder.ins().iconst(ty, i as i64),
|
||||
DataValue::I64(i) => builder.ins().iconst(ty, i as i64),
|
||||
DataValue::I128(i) => {
|
||||
let hi = builder.ins().iconst(I64, (i >> 64) as i64);
|
||||
let lo = builder.ins().iconst(I64, i as 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)
|
||||
}
|
||||
// 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)?)),
|
||||
DataValue::F32(f) => builder.ins().f32const(f),
|
||||
DataValue::F64(f) => builder.ins().f64const(f),
|
||||
_ => unimplemented!(),
|
||||
})
|
||||
}
|
||||
@@ -1650,34 +1593,38 @@ where
|
||||
}
|
||||
|
||||
fn generate_funcrefs(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
|
||||
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 user_func_ref = builder
|
||||
.func
|
||||
.declare_imported_user_function(UserExternalName {
|
||||
namespace: 0,
|
||||
index: func_index,
|
||||
});
|
||||
let usercalls: Vec<(ExternalName, Signature)> = self
|
||||
.resources
|
||||
.usercalls
|
||||
.iter()
|
||||
.map(|(name, signature)| {
|
||||
let user_func_ref = builder.func.declare_imported_user_function(name.clone());
|
||||
let name = ExternalName::User(user_func_ref);
|
||||
let signature = self.generate_signature()?;
|
||||
(name, signature)
|
||||
} else {
|
||||
let libcall = *self.u.choose(ALLOWED_LIBCALLS)?;
|
||||
// TODO: Use [CallConv::for_libcall] once we generate flags.
|
||||
let callconv = self.system_callconv();
|
||||
let signature = libcall.signature(callconv);
|
||||
(ExternalName::LibCall(libcall), signature)
|
||||
};
|
||||
(name, signature.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let sig_ref = builder.import_signature(sig.clone());
|
||||
let lib_callconv = self.system_callconv();
|
||||
let libcalls: Vec<(ExternalName, Signature)> = self
|
||||
.resources
|
||||
.libcalls
|
||||
.iter()
|
||||
.map(|libcall| {
|
||||
let signature = libcall.signature(lib_callconv);
|
||||
let name = ExternalName::LibCall(*libcall);
|
||||
(name, signature)
|
||||
})
|
||||
.collect();
|
||||
|
||||
for (name, signature) in usercalls.into_iter().chain(libcalls) {
|
||||
let sig_ref = builder.import_signature(signature.clone());
|
||||
let func_ref = builder.import_function(ExtFuncData {
|
||||
name: ext_name,
|
||||
name,
|
||||
signature: sig_ref,
|
||||
colocated: self.u.arbitrary()?,
|
||||
});
|
||||
|
||||
self.resources.func_refs.push((sig, func_ref));
|
||||
self.resources.func_refs.push((signature, func_ref));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1727,7 +1674,7 @@ where
|
||||
}
|
||||
|
||||
/// Creates a random amount of blocks in this function
|
||||
fn generate_blocks(&mut self, builder: &mut FunctionBuilder, sig: &Signature) -> Result<()> {
|
||||
fn generate_blocks(&mut self, builder: &mut FunctionBuilder) -> Result<()> {
|
||||
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
|
||||
@@ -1751,7 +1698,10 @@ where
|
||||
// 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()))
|
||||
Ok((
|
||||
block,
|
||||
self.signature.params.iter().map(|a| a.value_type).collect(),
|
||||
))
|
||||
} else {
|
||||
let sig = self.generate_block_signature()?;
|
||||
sig.iter().for_each(|ty| {
|
||||
@@ -1882,7 +1832,7 @@ where
|
||||
|
||||
let mut params = Vec::with_capacity(param_count);
|
||||
for _ in 0..param_count {
|
||||
params.push(self.generate_type()?);
|
||||
params.push(self.u._type()?);
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
@@ -1902,7 +1852,7 @@ where
|
||||
|
||||
// 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 ty = self.u._type()?;
|
||||
let value = self.generate_const(builder, ty)?;
|
||||
vars.push((ty, value));
|
||||
}
|
||||
@@ -1930,15 +1880,12 @@ where
|
||||
/// 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();
|
||||
// function name must be in a different namespace than TESTFILE_NAMESPACE (0)
|
||||
let mut func = Function::with_name_signature(UserFuncName::user(1, 0), sig.clone());
|
||||
let mut func = Function::with_name_signature(self.name.clone(), self.signature.clone());
|
||||
|
||||
let mut builder = FunctionBuilder::new(&mut func, &mut fn_builder_ctx);
|
||||
|
||||
self.generate_blocks(&mut builder, &sig)?;
|
||||
self.generate_blocks(&mut builder)?;
|
||||
|
||||
// Function preamble
|
||||
self.generate_funcrefs(&mut builder)?;
|
||||
|
||||
Reference in New Issue
Block a user