cranelift_fuzzgen/
cranelift_arbitrary.rs

1use crate::codegen::ir::{ArgumentExtension, ArgumentPurpose};
2use anyhow::Result;
3use cranelift::codegen::data_value::DataValue;
4use cranelift::codegen::ir::types::*;
5use cranelift::codegen::ir::{AbiParam, Signature};
6use cranelift::codegen::isa::CallConv;
7
8use arbitrary::Unstructured;
9use cranelift::prelude::{Ieee32, Ieee64};
10use target_lexicon::Architecture;
11
12/// A trait for generating random Cranelift datastructures.
13pub trait CraneliftArbitrary {
14    fn _type(&mut self, simd_enabled: bool) -> Result<Type>;
15    fn callconv(&mut self, architecture: Architecture) -> Result<CallConv>;
16    fn abi_param(&mut self, simd_enabled: bool) -> Result<AbiParam>;
17    fn signature(
18        &mut self,
19        simd_enabled: bool,
20        architecture: Architecture,
21        max_params: usize,
22        max_rets: usize,
23    ) -> Result<Signature>;
24    fn datavalue(&mut self, ty: Type) -> Result<DataValue>;
25}
26
27impl<'a> CraneliftArbitrary for &mut Unstructured<'a> {
28    fn _type(&mut self, simd_enabled: bool) -> Result<Type> {
29        // TODO: It would be nice if we could get these directly from cranelift
30        let choices: &[Type] = if simd_enabled {
31            &[
32                I8, I16, I32, I64, I128, // Scalar Integers
33                F32, F64, // Scalar Floats
34                I8X16, I16X8, I32X4, I64X2, // SIMD Integers
35                F32X4, F64X2, // SIMD Floats
36            ]
37        } else {
38            &[I8, I16, I32, I64, I128, F32, F64]
39        };
40
41        Ok(*self.choose(choices)?)
42    }
43
44    fn callconv(&mut self, architecture: Architecture) -> Result<CallConv> {
45        // These are implemented and should work on all backends
46        let mut allowed_callconvs = vec![
47            CallConv::Fast,
48            CallConv::Cold,
49            CallConv::SystemV,
50            CallConv::Tail,
51        ];
52
53        // Fastcall is supposed to work on x86 and aarch64
54        if matches!(
55            architecture,
56            Architecture::X86_64 | Architecture::Aarch64(_)
57        ) {
58            allowed_callconvs.push(CallConv::WindowsFastcall);
59        }
60
61        // AArch64 has a few Apple specific calling conventions
62        if matches!(architecture, Architecture::Aarch64(_)) {
63            allowed_callconvs.push(CallConv::AppleAarch64);
64        }
65
66        // The winch calling convention is supposed to work on x64.
67        if matches!(architecture, Architecture::X86_64) {
68            allowed_callconvs.push(CallConv::Winch);
69        }
70
71        Ok(*self.choose(&allowed_callconvs[..])?)
72    }
73
74    fn abi_param(&mut self, simd_enabled: bool) -> Result<AbiParam> {
75        let value_type = self._type(simd_enabled)?;
76        // TODO: There are more argument purposes to be explored...
77        let purpose = ArgumentPurpose::Normal;
78        let extension = if value_type.is_int() {
79            *self.choose(&[
80                ArgumentExtension::Sext,
81                ArgumentExtension::Uext,
82                ArgumentExtension::None,
83            ])?
84        } else {
85            ArgumentExtension::None
86        };
87
88        Ok(AbiParam {
89            value_type,
90            purpose,
91            extension,
92        })
93    }
94
95    fn signature(
96        &mut self,
97        mut simd_enabled: bool,
98        architecture: Architecture,
99        max_params: usize,
100        max_rets: usize,
101    ) -> Result<Signature> {
102        let callconv = self.callconv(architecture)?;
103
104        // Winch doesn't support SIMD yet
105        // https://github.com/bytecodealliance/wasmtime/issues/8093
106        if callconv == CallConv::Winch {
107            simd_enabled = false;
108        }
109
110        let mut sig = Signature::new(callconv);
111
112        for _ in 0..max_params {
113            sig.params.push(self.abi_param(simd_enabled)?);
114        }
115
116        for _ in 0..max_rets {
117            sig.returns.push(self.abi_param(simd_enabled)?);
118        }
119
120        Ok(sig)
121    }
122
123    fn datavalue(&mut self, ty: Type) -> Result<DataValue> {
124        Ok(match ty {
125            ty if ty.is_int() => {
126                let imm = match ty {
127                    I8 => self.arbitrary::<i8>()? as i128,
128                    I16 => self.arbitrary::<i16>()? as i128,
129                    I32 => self.arbitrary::<i32>()? as i128,
130                    I64 => self.arbitrary::<i64>()? as i128,
131                    I128 => self.arbitrary::<i128>()?,
132                    _ => unreachable!(),
133                };
134                DataValue::from_integer(imm, ty)?
135            }
136            // f{32,64}::arbitrary does not generate a bunch of important values
137            // such as Signaling NaN's / NaN's with payload, so generate floats from integers.
138            F32 => DataValue::F32(Ieee32::with_bits(self.arbitrary::<u32>()?)),
139            F64 => DataValue::F64(Ieee64::with_bits(self.arbitrary::<u64>()?)),
140            ty if ty.is_vector() && ty.bits() == 128 => {
141                DataValue::V128(self.arbitrary::<[u8; 16]>()?)
142            }
143            _ => unimplemented!(),
144        })
145    }
146}