fuzzgen: Initial SIMD support (#5885)

* fuzzgen: Initial SIMD support

* riscv64: Address PR Feedback

Thanks!
This commit is contained in:
Afonso Bordado
2023-02-28 11:33:11 +00:00
committed by GitHub
parent ae881407cd
commit 480c45b854
5 changed files with 69 additions and 36 deletions

View File

@@ -2,7 +2,7 @@ test verifier
set enable_simd set enable_simd
function %incorrect_constant_size() { function %incorrect_constant_size() {
const13 = [1 2 3 4 5] ; this constant has 5 bytes const13 = 0x0102030405 ; this constant has 5 bytes
block0: block0:
v0 = vconst.i32x4 const13 ; error: The instruction expects const13 to have a size of 16 bytes but it has 5 v0 = vconst.i32x4 const13 ; error: The instruction expects const13 to have a size of 16 bytes but it has 5
return return

View File

@@ -7,27 +7,39 @@ use cranelift::codegen::isa::CallConv;
use arbitrary::Unstructured; use arbitrary::Unstructured;
use cranelift::prelude::{Ieee32, Ieee64}; use cranelift::prelude::{Ieee32, Ieee64};
use target_lexicon::Architecture;
/// A trait for generating random Cranelift datastructures. /// A trait for generating random Cranelift datastructures.
pub trait CraneliftArbitrary { pub trait CraneliftArbitrary {
fn _type(&mut self) -> Result<Type>; fn _type(&mut self, architecture: Architecture) -> Result<Type>;
fn callconv(&mut self) -> Result<CallConv>; fn callconv(&mut self) -> Result<CallConv>;
fn abi_param(&mut self) -> Result<AbiParam>; fn abi_param(&mut self, architecture: Architecture) -> Result<AbiParam>;
fn signature(&mut self, max_params: usize, max_rets: usize) -> Result<Signature>; fn signature(
&mut self,
architecture: Architecture,
max_params: usize,
max_rets: usize,
) -> Result<Signature>;
fn datavalue(&mut self, ty: Type) -> Result<DataValue>; fn datavalue(&mut self, ty: Type) -> Result<DataValue>;
} }
impl<'a> CraneliftArbitrary for &mut Unstructured<'a> { impl<'a> CraneliftArbitrary for &mut Unstructured<'a> {
fn _type(&mut self) -> Result<Type> { fn _type(&mut self, architecture: Architecture) -> Result<Type> {
// TODO: It would be nice if we could get these directly from cranelift // TODO: It would be nice if we could get these directly from cranelift
let scalars = [ // TODO: RISCV does not support SIMD yet
I8, I16, I32, I64, I128, F32, F64, let supports_simd = !matches!(architecture, Architecture::Riscv64(_));
// R32, R64, let choices = if supports_simd {
]; &[
// TODO: vector types I8, I16, I32, I64, I128, // Scalar Integers
F32, F64, // Scalar Floats
I8X16, I16X8, I32X4, I64X2, // SIMD Integers
F32X4, F64X2, // SIMD Floats
][..]
} else {
&[I8, I16, I32, I64, I128, F32, F64][..]
};
let ty = self.choose(&scalars[..])?; Ok(*self.choose(choices)?)
Ok(*ty)
} }
fn callconv(&mut self) -> Result<CallConv> { fn callconv(&mut self) -> Result<CallConv> {
@@ -35,8 +47,8 @@ impl<'a> CraneliftArbitrary for &mut Unstructured<'a> {
Ok(CallConv::SystemV) Ok(CallConv::SystemV)
} }
fn abi_param(&mut self) -> Result<AbiParam> { fn abi_param(&mut self, architecture: Architecture) -> Result<AbiParam> {
let value_type = self._type()?; let value_type = self._type(architecture)?;
// TODO: There are more argument purposes to be explored... // TODO: There are more argument purposes to be explored...
let purpose = ArgumentPurpose::Normal; let purpose = ArgumentPurpose::Normal;
let extension = if value_type.is_int() { let extension = if value_type.is_int() {
@@ -56,16 +68,21 @@ impl<'a> CraneliftArbitrary for &mut Unstructured<'a> {
}) })
} }
fn signature(&mut self, max_params: usize, max_rets: usize) -> Result<Signature> { fn signature(
&mut self,
architecture: Architecture,
max_params: usize,
max_rets: usize,
) -> Result<Signature> {
let callconv = self.callconv()?; let callconv = self.callconv()?;
let mut sig = Signature::new(callconv); let mut sig = Signature::new(callconv);
for _ in 0..max_params { for _ in 0..max_params {
sig.params.push(self.abi_param()?); sig.params.push(self.abi_param(architecture)?);
} }
for _ in 0..max_rets { for _ in 0..max_rets {
sig.returns.push(self.abi_param()?); sig.returns.push(self.abi_param(architecture)?);
} }
Ok(sig) Ok(sig)
@@ -88,6 +105,9 @@ impl<'a> CraneliftArbitrary for &mut Unstructured<'a> {
// such as Signaling NaN's / NaN's with payload, so generate floats from integers. // such as Signaling NaN's / NaN's with payload, so generate floats from integers.
F32 => DataValue::F32(Ieee32::with_bits(self.arbitrary::<u32>()?)), F32 => DataValue::F32(Ieee32::with_bits(self.arbitrary::<u32>()?)),
F64 => DataValue::F64(Ieee64::with_bits(self.arbitrary::<u64>()?)), F64 => DataValue::F64(Ieee64::with_bits(self.arbitrary::<u64>()?)),
ty if ty.is_vector() && ty.bits() == 128 => {
DataValue::V128(self.arbitrary::<[u8; 16]>()?)
}
_ => unimplemented!(), _ => unimplemented!(),
}) })
} }

View File

@@ -565,6 +565,12 @@ fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) -
} }
Architecture::Riscv64(_) => { Architecture::Riscv64(_) => {
// RISC-V Does not support SIMD at all
let is_simd = args.iter().chain(rets).any(|t| t.is_vector());
if is_simd {
return false;
}
exceptions!( exceptions!(
// TODO // TODO
(Opcode::IaddCout), (Opcode::IaddCout),
@@ -737,6 +743,10 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
(Opcode::Iabs, &[I32], &[I32], insert_opcode), (Opcode::Iabs, &[I32], &[I32], insert_opcode),
(Opcode::Iabs, &[I64], &[I64], insert_opcode), (Opcode::Iabs, &[I64], &[I64], insert_opcode),
(Opcode::Iabs, &[I128], &[I128], insert_opcode), (Opcode::Iabs, &[I128], &[I128], insert_opcode),
(Opcode::Iabs, &[I8X16, I8X16], &[I8X16], insert_opcode),
(Opcode::Iabs, &[I16X8, I16X8], &[I16X8], insert_opcode),
(Opcode::Iabs, &[I32X4, I32X4], &[I32X4], insert_opcode),
(Opcode::Iabs, &[I64X2, I64X2], &[I64X2], insert_opcode),
// Smin // Smin
(Opcode::Smin, &[I8, I8], &[I8], insert_opcode), (Opcode::Smin, &[I8, I8], &[I8], insert_opcode),
(Opcode::Smin, &[I16, I16], &[I16], insert_opcode), (Opcode::Smin, &[I16, I16], &[I16], insert_opcode),
@@ -1552,6 +1562,11 @@ where
} }
DataValue::F32(f) => builder.ins().f32const(f), DataValue::F32(f) => builder.ins().f32const(f),
DataValue::F64(f) => builder.ins().f64const(f), DataValue::F64(f) => builder.ins().f64const(f),
DataValue::V128(bytes) => {
let data = bytes.to_vec().into();
let handle = builder.func.dfg.constants.insert(data);
builder.ins().vconst(ty, handle)
}
_ => unimplemented!(), _ => unimplemented!(),
}) })
} }
@@ -1922,7 +1937,7 @@ where
let mut params = Vec::with_capacity(param_count); let mut params = Vec::with_capacity(param_count);
for _ in 0..param_count { for _ in 0..param_count {
params.push(self.u._type()?); params.push(self.u._type(self.target_triple.architecture)?);
} }
Ok(params) Ok(params)
} }
@@ -1942,7 +1957,7 @@ where
// 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.u._type()?; let ty = self.u._type(self.target_triple.architecture)?;
let value = self.generate_const(builder, ty)?; let value = self.generate_const(builder, ty)?;
vars.push((ty, value)); vars.push((ty, value));
} }

View File

@@ -256,7 +256,9 @@ where
fn generate_func(&mut self, target_triple: Triple) -> Result<Function> { fn generate_func(&mut self, target_triple: Triple) -> Result<Function> {
let max_params = self.u.int_in_range(self.config.signature_params.clone())?; let max_params = self.u.int_in_range(self.config.signature_params.clone())?;
let max_rets = self.u.int_in_range(self.config.signature_rets.clone())?; let max_rets = self.u.int_in_range(self.config.signature_rets.clone())?;
let sig = self.u.signature(max_params, max_rets)?; let sig = self
.u
.signature(target_triple.architecture, max_params, max_rets)?;
// Function name must be in a different namespace than TESTFILE_NAMESPACE (0) // Function name must be in a different namespace than TESTFILE_NAMESPACE (0)
let fname = UserFuncName::user(1, 0); let fname = UserFuncName::user(1, 0);
@@ -266,7 +268,9 @@ where
.map(|i| { .map(|i| {
let max_params = self.u.int_in_range(self.config.signature_params.clone())?; let max_params = self.u.int_in_range(self.config.signature_params.clone())?;
let max_rets = self.u.int_in_range(self.config.signature_rets.clone())?; let max_rets = self.u.int_in_range(self.config.signature_rets.clone())?;
let sig = self.u.signature(max_params, max_rets)?; let sig = self
.u
.signature(target_triple.architecture, max_params, max_rets)?;
let name = UserExternalName { let name = UserExternalName {
namespace: 2, namespace: 2,
index: i as u32, index: i as u32,

View File

@@ -729,16 +729,6 @@ impl<'a> Parser<'a> {
} }
} }
// Match and consume a sequence of immediate bytes (uimm8); e.g. [0x42 0x99 0x32]
fn match_constant_data(&mut self) -> ParseResult<ConstantData> {
self.match_token(Token::LBracket, "expected an opening left bracket")?;
let mut data = ConstantData::default();
while !self.optional(Token::RBracket) {
data = data.append(self.match_uimm8("expected a sequence of bytes (uimm8)")?);
}
Ok(data)
}
// Match and consume either a hexadecimal Uimm128 immediate (e.g. 0x000102...) or its literal // Match and consume either a hexadecimal Uimm128 immediate (e.g. 0x000102...) or its literal
// list form (e.g. [0 1 2...]). For convenience, since uimm128 values are stored in the // list form (e.g. [0 1 2...]). For convenience, since uimm128 values are stored in the
// `ConstantPool`, this returns `ConstantData`. // `ConstantPool`, this returns `ConstantData`.
@@ -1840,7 +1830,7 @@ impl<'a> Parser<'a> {
let ty = self.match_type("expected type of constant")?; let ty = self.match_type("expected type of constant")?;
self.match_uimm128(ty) self.match_uimm128(ty)
} else { } else {
self.match_constant_data() self.match_hexadecimal_constant("expected an immediate hexadecimal operand")
}?; }?;
// Collect any trailing comments. // Collect any trailing comments.
@@ -3440,14 +3430,18 @@ mod tests {
#[test] #[test]
fn parse_unbounded_constants() { fn parse_unbounded_constants() {
// Unlike match_uimm128, match_constant_data can parse byte sequences of any size: // Unlike match_uimm128, match_hexadecimal_constant can parse byte sequences of any size:
assert_eq!( assert_eq!(
Parser::new("[0 1]").match_constant_data().unwrap(), Parser::new("0x0100")
.match_hexadecimal_constant("err message")
.unwrap(),
vec![0, 1].into() vec![0, 1].into()
); );
// Only parse byte literals: // Only parse hexadecimal constants:
assert!(Parser::new("[256]").match_constant_data().is_err()); assert!(Parser::new("228")
.match_hexadecimal_constant("err message")
.is_err());
} }
#[test] #[test]