Path: blob/main/cranelift/fuzzgen/src/function_generator.rs
3069 views
use crate::config::Config;1use crate::cranelift_arbitrary::CraneliftArbitrary;2use crate::target_isa_extras::TargetIsaExtras;3use anyhow::Result;4use arbitrary::{Arbitrary, Unstructured};5use cranelift::codegen::data_value::DataValue;6use cranelift::codegen::ir::immediates::Offset32;7use cranelift::codegen::ir::instructions::{InstructionFormat, ResolvedConstraint};8use cranelift::codegen::ir::stackslot::StackSize;910use cranelift::codegen::ir::{11AliasRegion, AtomicRmwOp, Block, BlockArg, ConstantData, Endianness, ExternalName, FuncRef,12Function, LibCall, Opcode, SigRef, Signature, StackSlot, UserExternalName, UserFuncName, Value,13types::*,14};15use cranelift::codegen::isa::CallConv;16use cranelift::frontend::{FunctionBuilder, FunctionBuilderContext, Switch, Variable};17use cranelift::prelude::isa::OwnedTargetIsa;18use cranelift::prelude::{19ExtFuncData, FloatCC, InstBuilder, IntCC, JumpTableData, MemFlags, StackSlotData, StackSlotKind,20};21use std::collections::HashMap;22use std::ops::RangeInclusive;23use std::str::FromStr;24use std::sync::LazyLock;25use target_lexicon::{Architecture, Triple};2627type BlockSignature = Vec<Type>;2829fn insert_opcode(30fgen: &mut FunctionGenerator,31builder: &mut FunctionBuilder,32opcode: Opcode,33args: &[Type],34rets: &[Type],35) -> Result<()> {36let mut vals = Vec::with_capacity(args.len());37for &arg in args.into_iter() {38let var = fgen.get_variable_of_type(arg)?;39let val = builder.use_var(var);40vals.push(val);41}4243// Some opcodes require us to look at their input arguments to determine the44// controlling type. This is not the general case, but we can neatly check this45// using `requires_typevar_operand`.46let ctrl_type = if opcode.constraints().requires_typevar_operand() {47args.first()48} else {49rets.first()50}51.copied()52.unwrap_or(INVALID);5354// Choose the appropriate instruction format for this opcode55let (inst, dfg) = match opcode.format() {56InstructionFormat::NullAry => builder.ins().NullAry(opcode, ctrl_type),57InstructionFormat::Unary => builder.ins().Unary(opcode, ctrl_type, vals[0]),58InstructionFormat::Binary => builder.ins().Binary(opcode, ctrl_type, vals[0], vals[1]),59InstructionFormat::Ternary => builder60.ins()61.Ternary(opcode, ctrl_type, vals[0], vals[1], vals[2]),62_ => unimplemented!(),63};64let results = dfg.inst_results(inst).to_vec();6566for (val, &ty) in results.into_iter().zip(rets) {67let var = fgen.get_variable_of_type(ty)?;68builder.def_var(var, val);69}70Ok(())71}7273fn insert_call_to_function(74fgen: &mut FunctionGenerator,75builder: &mut FunctionBuilder,76call_opcode: Opcode,77sig: &Signature,78sig_ref: SigRef,79func_ref: FuncRef,80) -> Result<()> {81let actuals = fgen.generate_values_for_signature(82builder,83sig.params.iter().map(|abi_param| abi_param.value_type),84)?;8586let addr_ty = fgen.isa.pointer_type();87let call = match call_opcode {88Opcode::Call => builder.ins().call(func_ref, &actuals),89Opcode::ReturnCall => builder.ins().return_call(func_ref, &actuals),90Opcode::CallIndirect => {91let addr = builder.ins().func_addr(addr_ty, func_ref);92builder.ins().call_indirect(sig_ref, addr, &actuals)93}94Opcode::ReturnCallIndirect => {95let addr = builder.ins().func_addr(addr_ty, func_ref);96builder.ins().return_call_indirect(sig_ref, addr, &actuals)97}98_ => unreachable!(),99};100101// Assign the return values to random variables102let ret_values = builder.inst_results(call).to_vec();103let ret_types = sig.returns.iter().map(|p| p.value_type);104for (ty, val) in ret_types.zip(ret_values) {105let var = fgen.get_variable_of_type(ty)?;106builder.def_var(var, val);107}108109Ok(())110}111112fn insert_call(113fgen: &mut FunctionGenerator,114builder: &mut FunctionBuilder,115opcode: Opcode,116_args: &[Type],117_rets: &[Type],118) -> Result<()> {119assert!(matches!(opcode, Opcode::Call | Opcode::CallIndirect));120let (sig, sig_ref, func_ref) = fgen.u.choose(&fgen.resources.func_refs)?.clone();121insert_call_to_function(fgen, builder, opcode, &sig, sig_ref, func_ref)122}123124fn insert_stack_load(125fgen: &mut FunctionGenerator,126builder: &mut FunctionBuilder,127_opcode: Opcode,128_args: &[Type],129rets: &[Type],130) -> Result<()> {131let typevar = rets[0];132let type_size = typevar.bytes();133let (slot, slot_size, _align, category) = fgen.stack_slot_with_size(type_size)?;134135// `stack_load` doesn't support setting MemFlags, and it does not set any136// alias analysis bits, so we can only emit it for `Other` slots.137if category != AACategory::Other {138return Err(arbitrary::Error::IncorrectFormat.into());139}140141let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;142143let val = builder.ins().stack_load(typevar, slot, offset);144let var = fgen.get_variable_of_type(typevar)?;145builder.def_var(var, val);146147Ok(())148}149150fn insert_stack_store(151fgen: &mut FunctionGenerator,152builder: &mut FunctionBuilder,153_opcode: Opcode,154args: &[Type],155_rets: &[Type],156) -> Result<()> {157let typevar = args[0];158let type_size = typevar.bytes();159160let (slot, slot_size, _align, category) = fgen.stack_slot_with_size(type_size)?;161162// `stack_store` doesn't support setting MemFlags, and it does not set any163// alias analysis bits, so we can only emit it for `Other` slots.164if category != AACategory::Other {165return Err(arbitrary::Error::IncorrectFormat.into());166}167168let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;169170let arg0 = fgen.get_variable_of_type(typevar)?;171let arg0 = builder.use_var(arg0);172173builder.ins().stack_store(arg0, slot, offset);174Ok(())175}176177fn insert_cmp(178fgen: &mut FunctionGenerator,179builder: &mut FunctionBuilder,180opcode: Opcode,181args: &[Type],182rets: &[Type],183) -> Result<()> {184let lhs = fgen.get_variable_of_type(args[0])?;185let lhs = builder.use_var(lhs);186187let rhs = fgen.get_variable_of_type(args[1])?;188let rhs = builder.use_var(rhs);189190let res = if opcode == Opcode::Fcmp {191let cc = *fgen.u.choose(FloatCC::all())?;192193// We filter out condition codes that aren't supported by the target at194// this point after randomly choosing one, instead of randomly choosing a195// supported one, to avoid invalidating the corpus when these get implemented.196let unimplemented_cc = match (fgen.isa.triple().architecture, cc) {197// Some FloatCC's are not implemented on AArch64, see:198// https://github.com/bytecodealliance/wasmtime/issues/4850199(Architecture::Aarch64(_), FloatCC::OrderedNotEqual) => true,200(Architecture::Aarch64(_), FloatCC::UnorderedOrEqual) => true,201202// Not implemented on aarch64 for vectors203(Architecture::Aarch64(_), FloatCC::UnorderedOrLessThan)204| (Architecture::Aarch64(_), FloatCC::UnorderedOrLessThanOrEqual)205| (Architecture::Aarch64(_), FloatCC::UnorderedOrGreaterThan)206| (Architecture::Aarch64(_), FloatCC::UnorderedOrGreaterThanOrEqual) => {207args[0].is_vector()208}209210// These are not implemented on x86_64, for vectors.211(Architecture::X86_64, FloatCC::UnorderedOrEqual | FloatCC::OrderedNotEqual) => {212args[0].is_vector()213}214_ => false,215};216if unimplemented_cc {217return Err(arbitrary::Error::IncorrectFormat.into());218}219220builder.ins().fcmp(cc, lhs, rhs)221} else {222let cc = *fgen.u.choose(IntCC::all())?;223builder.ins().icmp(cc, lhs, rhs)224};225226let var = fgen.get_variable_of_type(rets[0])?;227builder.def_var(var, res);228229Ok(())230}231232fn insert_const(233fgen: &mut FunctionGenerator,234builder: &mut FunctionBuilder,235_opcode: Opcode,236_args: &[Type],237rets: &[Type],238) -> Result<()> {239let typevar = rets[0];240let var = fgen.get_variable_of_type(typevar)?;241let val = fgen.generate_const(builder, typevar)?;242builder.def_var(var, val);243Ok(())244}245246fn insert_bitcast(247fgen: &mut FunctionGenerator,248builder: &mut FunctionBuilder,249args: &[Type],250rets: &[Type],251) -> Result<()> {252let from_var = fgen.get_variable_of_type(args[0])?;253let from_val = builder.use_var(from_var);254255let to_var = fgen.get_variable_of_type(rets[0])?;256257// TODO: We can generate little/big endian flags here.258let mut memflags = MemFlags::new();259260// When bitcasting between vectors of different lane counts, we need to261// specify the endianness.262if args[0].lane_count() != rets[0].lane_count() {263memflags.set_endianness(Endianness::Little);264}265266let res = builder.ins().bitcast(rets[0], memflags, from_val);267builder.def_var(to_var, res);268Ok(())269}270271fn insert_load_store(272fgen: &mut FunctionGenerator,273builder: &mut FunctionBuilder,274opcode: Opcode,275args: &[Type],276rets: &[Type],277) -> Result<()> {278if opcode == Opcode::Bitcast {279return insert_bitcast(fgen, builder, args, rets);280}281282let ctrl_type = *rets.first().or(args.first()).unwrap();283let type_size = ctrl_type.bytes();284285let is_atomic = [Opcode::AtomicLoad, Opcode::AtomicStore].contains(&opcode);286let (address, flags, offset) =287fgen.generate_address_and_memflags(builder, type_size, is_atomic)?;288289// The variable being loaded or stored into290let var = fgen.get_variable_of_type(ctrl_type)?;291292match opcode.format() {293InstructionFormat::LoadNoOffset => {294let (inst, dfg) = builder295.ins()296.LoadNoOffset(opcode, ctrl_type, flags, address);297298let new_val = dfg.first_result(inst);299builder.def_var(var, new_val);300}301InstructionFormat::StoreNoOffset => {302let val = builder.use_var(var);303304builder305.ins()306.StoreNoOffset(opcode, ctrl_type, flags, val, address);307}308InstructionFormat::Store => {309let val = builder.use_var(var);310311builder312.ins()313.Store(opcode, ctrl_type, flags, offset, val, address);314}315InstructionFormat::Load => {316let (inst, dfg) = builder317.ins()318.Load(opcode, ctrl_type, flags, offset, address);319320let new_val = dfg.first_result(inst);321builder.def_var(var, new_val);322}323_ => unimplemented!(),324}325326Ok(())327}328329fn insert_atomic_rmw(330fgen: &mut FunctionGenerator,331builder: &mut FunctionBuilder,332_: Opcode,333_: &[Type],334rets: &[Type],335) -> Result<()> {336let ctrl_type = *rets.first().unwrap();337let type_size = ctrl_type.bytes();338339let rmw_op = *fgen.u.choose(AtomicRmwOp::all())?;340341let (address, flags, offset) = fgen.generate_address_and_memflags(builder, type_size, true)?;342343// AtomicRMW does not directly support offsets, so add the offset to the address separately.344let address = builder.ins().iadd_imm(address, i64::from(offset));345346// Load and store target variables347let source_var = fgen.get_variable_of_type(ctrl_type)?;348let target_var = fgen.get_variable_of_type(ctrl_type)?;349350let source_val = builder.use_var(source_var);351let new_val = builder352.ins()353.atomic_rmw(ctrl_type, flags, rmw_op, address, source_val);354355builder.def_var(target_var, new_val);356Ok(())357}358359fn insert_atomic_cas(360fgen: &mut FunctionGenerator,361builder: &mut FunctionBuilder,362_: Opcode,363_: &[Type],364rets: &[Type],365) -> Result<()> {366let ctrl_type = *rets.first().unwrap();367let type_size = ctrl_type.bytes();368369let (address, flags, offset) = fgen.generate_address_and_memflags(builder, type_size, true)?;370371// AtomicCas does not directly support offsets, so add the offset to the address separately.372let address = builder.ins().iadd_imm(address, i64::from(offset));373374// Source and Target variables375let expected_var = fgen.get_variable_of_type(ctrl_type)?;376let store_var = fgen.get_variable_of_type(ctrl_type)?;377let loaded_var = fgen.get_variable_of_type(ctrl_type)?;378379let expected_val = builder.use_var(expected_var);380let store_val = builder.use_var(store_var);381let new_val = builder382.ins()383.atomic_cas(flags, address, expected_val, store_val);384385builder.def_var(loaded_var, new_val);386Ok(())387}388389fn insert_shuffle(390fgen: &mut FunctionGenerator,391builder: &mut FunctionBuilder,392opcode: Opcode,393_: &[Type],394rets: &[Type],395) -> Result<()> {396let ctrl_type = *rets.first().unwrap();397398let lhs = builder.use_var(fgen.get_variable_of_type(ctrl_type)?);399let rhs = builder.use_var(fgen.get_variable_of_type(ctrl_type)?);400401let mask = {402let mut lanes = [0u8; 16];403for lane in lanes.iter_mut() {404*lane = fgen.u.int_in_range(0..=31)?;405}406let lanes = ConstantData::from(lanes.as_ref());407builder.func.dfg.immediates.push(lanes)408};409410// This function is called for any `InstructionFormat::Shuffle`. Which today is just411// `shuffle`, but lets assert that, just to be sure we don't accidentally insert412// something else.413assert_eq!(opcode, Opcode::Shuffle);414let res = builder.ins().shuffle(lhs, rhs, mask);415416let target_var = fgen.get_variable_of_type(ctrl_type)?;417builder.def_var(target_var, res);418419Ok(())420}421422fn insert_ins_ext_lane(423fgen: &mut FunctionGenerator,424builder: &mut FunctionBuilder,425opcode: Opcode,426args: &[Type],427rets: &[Type],428) -> Result<()> {429let vector_type = *args.first().unwrap();430let ret_type = *rets.first().unwrap();431432let lhs = builder.use_var(fgen.get_variable_of_type(vector_type)?);433let max_lane = (vector_type.lane_count() as u8) - 1;434let lane = fgen.u.int_in_range(0..=max_lane)?;435436let res = match opcode {437Opcode::Insertlane => {438let rhs = builder.use_var(fgen.get_variable_of_type(args[1])?);439builder.ins().insertlane(lhs, rhs, lane)440}441Opcode::Extractlane => builder.ins().extractlane(lhs, lane),442_ => todo!(),443};444445let target_var = fgen.get_variable_of_type(ret_type)?;446builder.def_var(target_var, res);447448Ok(())449}450451type OpcodeInserter = fn(452fgen: &mut FunctionGenerator,453builder: &mut FunctionBuilder,454Opcode,455&[Type],456&[Type],457) -> Result<()>;458459macro_rules! exceptions {460($op:expr, $args:expr, $rets:expr, $(($($cases:pat),*)),* $(,)?) => {461match ($op, $args, $rets) {462$( ($($cases,)* ..) => return false, )*463_ => true,464}465}466}467468/// Returns true if we believe this `OpcodeSignature` should compile correctly469/// for the given target triple. We currently have a range of known issues470/// with specific lowerings on specific backends, and we don't want to get471/// fuzz bug reports for those. Over time our goal is to eliminate all of these472/// exceptions.473fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) -> bool {474// Rule out invalid combinations that we don't yet have a good way of rejecting with the475// instruction DSL type constraints.476match op {477Opcode::FcvtToUintSat | Opcode::FcvtToSintSat => {478assert_eq!(args.len(), 1);479assert_eq!(rets.len(), 1);480481let arg = args[0];482let ret = rets[0];483484// Vector arguments must produce vector results, and scalar arguments must produce485// scalar results.486if arg.is_vector() != ret.is_vector() {487return false;488}489490if arg.is_vector() && ret.is_vector() {491// Vector conversions must have the same number of lanes, and the lanes must be the492// same bit-width.493if arg.lane_count() != ret.lane_count() {494return false;495}496497if arg.lane_of().bits() != ret.lane_of().bits() {498return false;499}500}501}502503Opcode::Bitcast => {504assert_eq!(args.len(), 1);505assert_eq!(rets.len(), 1);506507let arg = args[0];508let ret = rets[0];509510// The opcode generator still allows bitcasts between different sized types, but these511// are rejected in the verifier.512if arg.bits() != ret.bits() {513return false;514}515}516517// This requires precise runtime integration so it's not supported at518// all in fuzzgen just yet.519Opcode::StackSwitch => return false,520521_ => {}522}523524match triple.architecture {525Architecture::X86_64 => {526exceptions!(527op,528args,529rets,530(Opcode::UmulOverflow | Opcode::SmulOverflow, &[I128, I128]),531(Opcode::Imul, &[I8X16, I8X16]),532// https://github.com/bytecodealliance/wasmtime/issues/4756533(Opcode::Udiv | Opcode::Sdiv, &[I128, I128]),534// https://github.com/bytecodealliance/wasmtime/issues/5474535(Opcode::Urem | Opcode::Srem, &[I128, I128]),536// https://github.com/bytecodealliance/wasmtime/issues/3370537(538Opcode::Smin | Opcode::Umin | Opcode::Smax | Opcode::Umax,539&[I128, I128]540),541// https://github.com/bytecodealliance/wasmtime/issues/5107542(Opcode::Cls, &[I8], &[I8]),543(Opcode::Cls, &[I16], &[I16]),544(Opcode::Cls, &[I32], &[I32]),545(Opcode::Cls, &[I64], &[I64]),546(Opcode::Cls, &[I128], &[I128]),547// TODO548(Opcode::Bitselect, &[_, _, _], &[F32 | F64]),549// https://github.com/bytecodealliance/wasmtime/issues/4897550// https://github.com/bytecodealliance/wasmtime/issues/4899551(552Opcode::FcvtToUint553| Opcode::FcvtToUintSat554| Opcode::FcvtToSint555| Opcode::FcvtToSintSat,556&[F32 | F64],557&[I8 | I16 | I128]558),559(Opcode::FcvtToUint | Opcode::FcvtToSint, &[F32X4], &[I32X4]),560(561Opcode::FcvtToUint562| Opcode::FcvtToUintSat563| Opcode::FcvtToSint564| Opcode::FcvtToSintSat,565&[F64X2],566&[I64X2]567),568// https://github.com/bytecodealliance/wasmtime/issues/4900569(Opcode::FcvtFromUint, &[I128], &[F32 | F64]),570// This has a lowering, but only when preceded by `uwiden_low`.571(Opcode::FcvtFromUint, &[I64X2], &[F64X2]),572// https://github.com/bytecodealliance/wasmtime/issues/4900573(Opcode::FcvtFromSint, &[I128], &[F32 | F64]),574(Opcode::FcvtFromSint, &[I64X2], &[F64X2]),575(576Opcode::Umulhi | Opcode::Smulhi,577&([I8X16, I8X16] | [I16X8, I16X8] | [I32X4, I32X4] | [I64X2, I64X2])578),579(580Opcode::UaddSat | Opcode::SaddSat | Opcode::UsubSat | Opcode::SsubSat,581&([I32X4, I32X4] | [I64X2, I64X2])582),583(Opcode::Fcopysign, &([F32X4, F32X4] | [F64X2, F64X2])),584(Opcode::Popcnt, &([I8X16] | [I16X8] | [I32X4] | [I64X2])),585(586Opcode::Umax | Opcode::Smax | Opcode::Umin | Opcode::Smin,587&[I64X2, I64X2]588),589// https://github.com/bytecodealliance/wasmtime/issues/6104590(Opcode::Bitcast, &[I128], &[_]),591(Opcode::Bitcast, &[_], &[I128]),592(Opcode::Uunarrow),593(Opcode::Snarrow | Opcode::Unarrow, &[I64X2, I64X2]),594(Opcode::SqmulRoundSat, &[I32X4, I32X4]),595// This Icmp is not implemented: #5529596(Opcode::Icmp, &[I64X2, I64X2]),597// IaddPairwise is implemented, but only for some types, and with some preceding ops.598(Opcode::IaddPairwise),599// Nothing wrong with this select. But we have an isle rule that can optimize it600// into a `min`/`max` instructions, which we don't have implemented yet.601(Opcode::Select, &[_, I128, I128]),602// These stack accesses can cause segfaults if they are merged into an SSE instruction.603// See: #5922604(605Opcode::StackStore,606&[I8X16 | I16X8 | I32X4 | I64X2 | F32X4 | F64X2]607),608(609Opcode::StackLoad,610&[],611&[I8X16 | I16X8 | I32X4 | I64X2 | F32X4 | F64X2]612),613// TODO614(615Opcode::Sshr | Opcode::Ushr | Opcode::Ishl,616&[I8X16 | I16X8 | I32X4 | I64X2, I128]617),618(619Opcode::Rotr | Opcode::Rotl,620&[I8X16 | I16X8 | I32X4 | I64X2, _]621),622)623}624625Architecture::Aarch64(_) => {626exceptions!(627op,628args,629rets,630(Opcode::UmulOverflow | Opcode::SmulOverflow, &[I128, I128]),631// https://github.com/bytecodealliance/wasmtime/issues/4864632(Opcode::Udiv | Opcode::Sdiv, &[I128, I128]),633// https://github.com/bytecodealliance/wasmtime/issues/5472634(Opcode::Urem | Opcode::Srem, &[I128, I128]),635// https://github.com/bytecodealliance/wasmtime/issues/4313636(637Opcode::Smin | Opcode::Umin | Opcode::Smax | Opcode::Umax,638&[I128, I128]639),640// https://github.com/bytecodealliance/wasmtime/issues/4870641(Opcode::Bnot, &[F32 | F64]),642(643Opcode::Band644| Opcode::Bor645| Opcode::Bxor646| Opcode::BandNot647| Opcode::BorNot648| Opcode::BxorNot,649&([F32, F32] | [F64, F64])650),651// https://github.com/bytecodealliance/wasmtime/issues/5198652(Opcode::Bitselect, &[I128, I128, I128]),653// https://github.com/bytecodealliance/wasmtime/issues/4934654(655Opcode::FcvtToUint656| Opcode::FcvtToUintSat657| Opcode::FcvtToSint658| Opcode::FcvtToSintSat,659&[F32 | F64],660&[I128]661),662// https://github.com/bytecodealliance/wasmtime/issues/4933663(664Opcode::FcvtFromUint | Opcode::FcvtFromSint,665&[I128],666&[F32 | F64]667),668(669Opcode::Umulhi | Opcode::Smulhi,670&([I8X16, I8X16] | [I16X8, I16X8] | [I32X4, I32X4] | [I64X2, I64X2])671),672(Opcode::Popcnt, &[I16X8 | I32X4 | I64X2]),673// Nothing wrong with this select. But we have an isle rule that can optimize it674// into a `min`/`max` instructions, which we don't have implemented yet.675(Opcode::Select, &[I8, I128, I128]),676// https://github.com/bytecodealliance/wasmtime/issues/6104677(Opcode::Bitcast, &[I128], &[_]),678(Opcode::Bitcast, &[_], &[I128]),679// TODO680(681Opcode::Sshr | Opcode::Ushr | Opcode::Ishl,682&[I8X16 | I16X8 | I32X4 | I64X2, I128]683),684(685Opcode::Rotr | Opcode::Rotl,686&[I8X16 | I16X8 | I32X4 | I64X2, _]687),688// TODO689(Opcode::Bitselect, &[_, _, _], &[F32 | F64]),690(Opcode::VhighBits, &[F32X4 | F64X2]),691)692}693694Architecture::S390x => {695exceptions!(696op,697args,698rets,699(Opcode::UaddOverflow | Opcode::SaddOverflow),700(Opcode::UsubOverflow | Opcode::SsubOverflow),701(Opcode::UmulOverflow | Opcode::SmulOverflow),702(703Opcode::Udiv | Opcode::Sdiv | Opcode::Urem | Opcode::Srem,704&[I128, I128]705),706(Opcode::Bnot, &[F32 | F64]),707(708Opcode::Band709| Opcode::Bor710| Opcode::Bxor711| Opcode::BandNot712| Opcode::BorNot713| Opcode::BxorNot,714&([F32, F32] | [F64, F64])715),716(717Opcode::FcvtToUint718| Opcode::FcvtToUintSat719| Opcode::FcvtToSint720| Opcode::FcvtToSintSat,721&[F32 | F64],722&[I128]723),724(725Opcode::FcvtFromUint | Opcode::FcvtFromSint,726&[I128],727&[F32 | F64]728),729(Opcode::SsubSat | Opcode::SaddSat, &[I64X2, I64X2]),730// https://github.com/bytecodealliance/wasmtime/issues/6104731(Opcode::Bitcast, &[I128], &[_]),732(Opcode::Bitcast, &[_], &[I128]),733// TODO734(Opcode::Bitselect, &[_, _, _], &[F32 | F64]),735)736}737738Architecture::Riscv64(_) => {739exceptions!(740op,741args,742rets,743// TODO744(Opcode::UaddOverflow | Opcode::SaddOverflow),745(Opcode::UsubOverflow | Opcode::SsubOverflow),746(Opcode::UmulOverflow | Opcode::SmulOverflow),747// TODO748(749Opcode::Udiv | Opcode::Sdiv | Opcode::Urem | Opcode::Srem,750&[I128, I128]751),752// TODO753(Opcode::Iabs, &[I128]),754// TODO755(Opcode::Bitselect, &[I128, I128, I128]),756// https://github.com/bytecodealliance/wasmtime/issues/5528757(758Opcode::FcvtToUint | Opcode::FcvtToSint,759[F32 | F64],760&[I128]761),762(763Opcode::FcvtToUintSat | Opcode::FcvtToSintSat,764&[F32 | F64],765&[I128]766),767// https://github.com/bytecodealliance/wasmtime/issues/5528768(769Opcode::FcvtFromUint | Opcode::FcvtFromSint,770&[I128],771&[F32 | F64]772),773// TODO774(775Opcode::SelectSpectreGuard,776&[_, _, _],777&[F32 | F64 | I8X16 | I16X8 | I32X4 | I64X2 | F64X2 | F32X4]778),779// TODO780(Opcode::Bitselect, &[_, _, _], &[F32 | F64]),781(782Opcode::Rotr | Opcode::Rotl,783&[I8X16 | I16X8 | I32X4 | I64X2, _]784),785)786}787788_ => true,789}790}791792type OpcodeSignature = (Opcode, Vec<Type>, Vec<Type>);793794static OPCODE_SIGNATURES: LazyLock<Vec<OpcodeSignature>> = LazyLock::new(|| {795let types = &[796I8, I16, I32, I64, I128, // Scalar Integers797F32, F64, // Scalar Floats798I8X16, I16X8, I32X4, I64X2, // SIMD Integers799F32X4, F64X2, // SIMD Floats800];801802// When this env variable is passed, we only generate instructions for the opcodes listed in803// the comma-separated list. This is useful for debugging, as it allows us to focus on a few804// specific opcodes.805let allowed_opcodes = std::env::var("FUZZGEN_ALLOWED_OPS").ok().map(|s| {806s.split(',')807.map(|s| s.trim())808.filter(|s| !s.is_empty())809.map(|s| Opcode::from_str(s).expect("Unrecoginzed opcode"))810.collect::<Vec<_>>()811});812813Opcode::all()814.iter()815.filter(|op| {816match op {817// Control flow opcodes should not be generated through `generate_instructions`.818Opcode::BrTable819| Opcode::Brif820| Opcode::Jump821| Opcode::Return822| Opcode::ReturnCall823| Opcode::ReturnCallIndirect824| Opcode::TryCall825| Opcode::TryCallIndirect => false,826827// Constants are generated outside of `generate_instructions`828Opcode::Iconst => false,829830// TODO: extract_vector raises exceptions during return type generation because it831// uses dynamic vectors.832Opcode::ExtractVector => false,833834_ => true,835}836})837.flat_map(|op| {838let constraints = op.constraints();839840let ctrl_types = if let Some(ctrls) = constraints.ctrl_typeset() {841Vec::from_iter(types.iter().copied().filter(|ty| ctrls.contains(*ty)))842} else {843vec![INVALID]844};845846ctrl_types.into_iter().flat_map(move |ctrl_type| {847let rets = Vec::from_iter(848(0..constraints.num_fixed_results())849.map(|i| constraints.result_type(i, ctrl_type)),850);851852// Cols is a vector whose length will match `num_fixed_value_arguments`, and whose853// elements will be vectors of types that are valid for that fixed argument854// position.855let mut cols = vec![];856857for i in 0..constraints.num_fixed_value_arguments() {858match constraints.value_argument_constraint(i, ctrl_type) {859ResolvedConstraint::Bound(ty) => cols.push(Vec::from([ty])),860ResolvedConstraint::Free(tys) => cols.push(Vec::from_iter(861types.iter().copied().filter(|ty| tys.contains(*ty)),862)),863}864}865866// Generate the cartesian product of cols to produce a vector of argument lists,867// argss. The argss vector is seeded with the empty argument list, so there's an868// initial value to be extended in the loop below.869let mut argss = vec![vec![]];870let mut cols = cols.as_slice();871while let Some((col, rest)) = cols.split_last() {872cols = rest;873874let mut next = vec![];875for current in argss.iter() {876// Extend the front of each argument candidate with every type in `col`.877for ty in col {878let mut args = vec![*ty];879args.extend_from_slice(¤t);880next.push(args);881}882}883884let _ = std::mem::replace(&mut argss, next);885}886887argss.into_iter().map(move |args| (*op, args, rets.clone()))888})889})890.filter(|(op, args, rets)| {891// These op/signature combinations need to be vetted892exceptions!(893op,894args.as_slice(),895rets.as_slice(),896(Opcode::Debugtrap),897(Opcode::Trap),898(Opcode::Trapz),899(Opcode::Trapnz),900(Opcode::CallIndirect, &[I32]),901(Opcode::FuncAddr),902(Opcode::X86Pshufb),903(Opcode::AvgRound),904(Opcode::Uload8x8),905(Opcode::Sload8x8),906(Opcode::Uload16x4),907(Opcode::Sload16x4),908(Opcode::Uload32x2),909(Opcode::Sload32x2),910(Opcode::StackAddr),911(Opcode::DynamicStackLoad),912(Opcode::DynamicStackStore),913(Opcode::DynamicStackAddr),914(Opcode::GlobalValue),915(Opcode::SymbolValue),916(Opcode::TlsValue),917(Opcode::GetPinnedReg),918(Opcode::SetPinnedReg),919(Opcode::GetFramePointer),920(Opcode::GetStackPointer),921(Opcode::GetReturnAddress),922(Opcode::Blendv),923(Opcode::IcmpImm),924(Opcode::X86Pmulhrsw),925(Opcode::IaddImm),926(Opcode::ImulImm),927(Opcode::UdivImm),928(Opcode::SdivImm),929(Opcode::UremImm),930(Opcode::SremImm),931(Opcode::IrsubImm),932(Opcode::UaddOverflowCin),933(Opcode::SaddOverflowCin),934(Opcode::UaddOverflowTrap),935(Opcode::UsubOverflowBin),936(Opcode::SsubOverflowBin),937(Opcode::BandImm),938(Opcode::BorImm),939(Opcode::BxorImm),940(Opcode::RotlImm),941(Opcode::RotrImm),942(Opcode::IshlImm),943(Opcode::UshrImm),944(Opcode::SshrImm),945(Opcode::ScalarToVector),946(Opcode::X86Pmaddubsw),947(Opcode::X86Cvtt2dq),948(Opcode::Umulhi, &[I128, I128], &[I128]),949(Opcode::Smulhi, &[I128, I128], &[I128]),950// https://github.com/bytecodealliance/wasmtime/issues/6073951(Opcode::Iconcat, &[I32, I32], &[I64]),952(Opcode::Iconcat, &[I16, I16], &[I32]),953(Opcode::Iconcat, &[I8, I8], &[I16]),954// https://github.com/bytecodealliance/wasmtime/issues/6073955(Opcode::Isplit, &[I64], &[I32, I32]),956(Opcode::Isplit, &[I32], &[I16, I16]),957(Opcode::Isplit, &[I16], &[I8, I8]),958(Opcode::Fmin, &[F32X4, F32X4], &[F32X4]),959(Opcode::Fmin, &[F64X2, F64X2], &[F64X2]),960(Opcode::Fmax, &[F32X4, F32X4], &[F32X4]),961(Opcode::Fmax, &[F64X2, F64X2], &[F64X2]),962(Opcode::FcvtToUintSat, &[F32X4], &[I8]),963(Opcode::FcvtToUintSat, &[F64X2], &[I8]),964(Opcode::FcvtToUintSat, &[F32X4], &[I16]),965(Opcode::FcvtToUintSat, &[F64X2], &[I16]),966(Opcode::FcvtToUintSat, &[F32X4], &[I32]),967(Opcode::FcvtToUintSat, &[F64X2], &[I32]),968(Opcode::FcvtToUintSat, &[F32X4], &[I64]),969(Opcode::FcvtToUintSat, &[F64X2], &[I64]),970(Opcode::FcvtToUintSat, &[F32X4], &[I128]),971(Opcode::FcvtToUintSat, &[F64X2], &[I128]),972(Opcode::FcvtToUintSat, &[F32], &[I8X16]),973(Opcode::FcvtToUintSat, &[F64], &[I8X16]),974(Opcode::FcvtToUintSat, &[F32X4], &[I8X16]),975(Opcode::FcvtToUintSat, &[F64X2], &[I8X16]),976(Opcode::FcvtToUintSat, &[F32], &[I16X8]),977(Opcode::FcvtToUintSat, &[F64], &[I16X8]),978(Opcode::FcvtToUintSat, &[F32X4], &[I16X8]),979(Opcode::FcvtToUintSat, &[F64X2], &[I16X8]),980(Opcode::FcvtToUintSat, &[F32], &[I32X4]),981(Opcode::FcvtToUintSat, &[F64], &[I32X4]),982(Opcode::FcvtToUintSat, &[F64X2], &[I32X4]),983(Opcode::FcvtToUintSat, &[F32], &[I64X2]),984(Opcode::FcvtToUintSat, &[F64], &[I64X2]),985(Opcode::FcvtToUintSat, &[F32X4], &[I64X2]),986(Opcode::FcvtToSintSat, &[F32X4], &[I8]),987(Opcode::FcvtToSintSat, &[F64X2], &[I8]),988(Opcode::FcvtToSintSat, &[F32X4], &[I16]),989(Opcode::FcvtToSintSat, &[F64X2], &[I16]),990(Opcode::FcvtToSintSat, &[F32X4], &[I32]),991(Opcode::FcvtToSintSat, &[F64X2], &[I32]),992(Opcode::FcvtToSintSat, &[F32X4], &[I64]),993(Opcode::FcvtToSintSat, &[F64X2], &[I64]),994(Opcode::FcvtToSintSat, &[F32X4], &[I128]),995(Opcode::FcvtToSintSat, &[F64X2], &[I128]),996(Opcode::FcvtToSintSat, &[F32], &[I8X16]),997(Opcode::FcvtToSintSat, &[F64], &[I8X16]),998(Opcode::FcvtToSintSat, &[F32X4], &[I8X16]),999(Opcode::FcvtToSintSat, &[F64X2], &[I8X16]),1000(Opcode::FcvtToSintSat, &[F32], &[I16X8]),1001(Opcode::FcvtToSintSat, &[F64], &[I16X8]),1002(Opcode::FcvtToSintSat, &[F32X4], &[I16X8]),1003(Opcode::FcvtToSintSat, &[F64X2], &[I16X8]),1004(Opcode::FcvtToSintSat, &[F32], &[I32X4]),1005(Opcode::FcvtToSintSat, &[F64], &[I32X4]),1006(Opcode::FcvtToSintSat, &[F64X2], &[I32X4]),1007(Opcode::FcvtToSintSat, &[F32], &[I64X2]),1008(Opcode::FcvtToSintSat, &[F64], &[I64X2]),1009(Opcode::FcvtToSintSat, &[F32X4], &[I64X2]),1010(Opcode::FcvtFromUint, &[I8X16], &[F32]),1011(Opcode::FcvtFromUint, &[I16X8], &[F32]),1012(Opcode::FcvtFromUint, &[I32X4], &[F32]),1013(Opcode::FcvtFromUint, &[I64X2], &[F32]),1014(Opcode::FcvtFromUint, &[I8X16], &[F64]),1015(Opcode::FcvtFromUint, &[I16X8], &[F64]),1016(Opcode::FcvtFromUint, &[I32X4], &[F64]),1017(Opcode::FcvtFromUint, &[I64X2], &[F64]),1018(Opcode::FcvtFromUint, &[I8], &[F32X4]),1019(Opcode::FcvtFromUint, &[I16], &[F32X4]),1020(Opcode::FcvtFromUint, &[I32], &[F32X4]),1021(Opcode::FcvtFromUint, &[I64], &[F32X4]),1022(Opcode::FcvtFromUint, &[I128], &[F32X4]),1023(Opcode::FcvtFromUint, &[I8X16], &[F32X4]),1024(Opcode::FcvtFromUint, &[I16X8], &[F32X4]),1025(Opcode::FcvtFromUint, &[I64X2], &[F32X4]),1026(Opcode::FcvtFromUint, &[I8], &[F64X2]),1027(Opcode::FcvtFromUint, &[I16], &[F64X2]),1028(Opcode::FcvtFromUint, &[I32], &[F64X2]),1029(Opcode::FcvtFromUint, &[I64], &[F64X2]),1030(Opcode::FcvtFromUint, &[I128], &[F64X2]),1031(Opcode::FcvtFromUint, &[I8X16], &[F64X2]),1032(Opcode::FcvtFromUint, &[I16X8], &[F64X2]),1033(Opcode::FcvtFromUint, &[I32X4], &[F64X2]),1034(Opcode::FcvtFromSint, &[I8X16], &[F32]),1035(Opcode::FcvtFromSint, &[I16X8], &[F32]),1036(Opcode::FcvtFromSint, &[I32X4], &[F32]),1037(Opcode::FcvtFromSint, &[I64X2], &[F32]),1038(Opcode::FcvtFromSint, &[I8X16], &[F64]),1039(Opcode::FcvtFromSint, &[I16X8], &[F64]),1040(Opcode::FcvtFromSint, &[I32X4], &[F64]),1041(Opcode::FcvtFromSint, &[I64X2], &[F64]),1042(Opcode::FcvtFromSint, &[I8], &[F32X4]),1043(Opcode::FcvtFromSint, &[I16], &[F32X4]),1044(Opcode::FcvtFromSint, &[I32], &[F32X4]),1045(Opcode::FcvtFromSint, &[I64], &[F32X4]),1046(Opcode::FcvtFromSint, &[I128], &[F32X4]),1047(Opcode::FcvtFromSint, &[I8X16], &[F32X4]),1048(Opcode::FcvtFromSint, &[I16X8], &[F32X4]),1049(Opcode::FcvtFromSint, &[I64X2], &[F32X4]),1050(Opcode::FcvtFromSint, &[I8], &[F64X2]),1051(Opcode::FcvtFromSint, &[I16], &[F64X2]),1052(Opcode::FcvtFromSint, &[I32], &[F64X2]),1053(Opcode::FcvtFromSint, &[I64], &[F64X2]),1054(Opcode::FcvtFromSint, &[I128], &[F64X2]),1055(Opcode::FcvtFromSint, &[I8X16], &[F64X2]),1056(Opcode::FcvtFromSint, &[I16X8], &[F64X2]),1057(Opcode::FcvtFromSint, &[I32X4], &[F64X2]),1058// Only supported on x64 with a feature at this time, so 128-bit1059// atomics are not suitable to fuzz yet.1060(Opcode::AtomicRmw, _, &[I128]),1061(Opcode::AtomicCas, _, &[I128]),1062(Opcode::AtomicLoad, _, &[I128]),1063(Opcode::AtomicStore, &[I128, _], _),1064(Opcode::SequencePoint),1065)1066})1067.filter(|(op, ..)| {1068allowed_opcodes1069.as_ref()1070.map_or(true, |opcodes| opcodes.contains(op))1071})1072.collect()1073});10741075fn inserter_for_format(fmt: InstructionFormat) -> OpcodeInserter {1076match fmt {1077InstructionFormat::AtomicCas => insert_atomic_cas,1078InstructionFormat::AtomicRmw => insert_atomic_rmw,1079InstructionFormat::Binary => insert_opcode,1080InstructionFormat::BinaryImm64 => todo!(),1081InstructionFormat::BinaryImm8 => insert_ins_ext_lane,1082InstructionFormat::Call => insert_call,1083InstructionFormat::CallIndirect => insert_call,1084InstructionFormat::CondTrap => todo!(),1085InstructionFormat::DynamicStackLoad => todo!(),1086InstructionFormat::DynamicStackStore => todo!(),1087InstructionFormat::FloatCompare => insert_cmp,1088InstructionFormat::FuncAddr => todo!(),1089InstructionFormat::IntAddTrap => todo!(),1090InstructionFormat::IntCompare => insert_cmp,1091InstructionFormat::IntCompareImm => todo!(),1092InstructionFormat::Load => insert_load_store,1093InstructionFormat::LoadNoOffset => insert_load_store,1094InstructionFormat::NullAry => insert_opcode,1095InstructionFormat::Shuffle => insert_shuffle,1096InstructionFormat::StackLoad => insert_stack_load,1097InstructionFormat::StackStore => insert_stack_store,1098InstructionFormat::Store => insert_load_store,1099InstructionFormat::StoreNoOffset => insert_load_store,1100InstructionFormat::Ternary => insert_opcode,1101InstructionFormat::TernaryImm8 => insert_ins_ext_lane,1102InstructionFormat::Trap => todo!(),1103InstructionFormat::Unary => insert_opcode,1104InstructionFormat::UnaryConst => insert_const,1105InstructionFormat::UnaryGlobalValue => todo!(),1106InstructionFormat::UnaryIeee16 => insert_const,1107InstructionFormat::UnaryIeee32 => insert_const,1108InstructionFormat::UnaryIeee64 => insert_const,1109InstructionFormat::UnaryImm => insert_const,1110InstructionFormat::ExceptionHandlerAddress => insert_const,11111112InstructionFormat::BranchTable1113| InstructionFormat::Brif1114| InstructionFormat::Jump1115| InstructionFormat::MultiAry1116| InstructionFormat::TryCall1117| InstructionFormat::TryCallIndirect => {1118panic!("Control-flow instructions should be handled by 'insert_terminator': {fmt:?}")1119}1120}1121}11221123pub struct FunctionGenerator<'r, 'data>1124where1125'data: 'r,1126{1127u: &'r mut Unstructured<'data>,1128config: &'r Config,1129resources: Resources,1130isa: OwnedTargetIsa,1131name: UserFuncName,1132signature: Signature,1133}11341135#[derive(Debug, Clone)]1136enum BlockTerminator {1137Return,1138Jump(Block),1139Br(Block, Block),1140BrTable(Block, Vec<Block>),1141Switch(Type, Block, HashMap<u128, Block>),1142TailCall(FuncRef),1143TailCallIndirect(FuncRef),1144}11451146#[derive(Debug, Clone)]1147enum BlockTerminatorKind {1148Return,1149Jump,1150Br,1151BrTable,1152Switch,1153TailCall,1154TailCallIndirect,1155}11561157/// Alias Analysis Category1158///1159/// Our alias analysis pass supports 4 categories of accesses to distinguish1160/// different regions. The "Other" region is the general case, and is the default1161/// Although they have highly suggestive names there is no difference between any1162/// of the categories.1163///1164/// We assign each stack slot a category when we first generate them, and then1165/// ensure that all accesses to that stack slot are correctly tagged. We already1166/// ensure that memory accesses never cross stack slots, so there is no risk1167/// of a memory access being tagged with the wrong category.1168#[derive(Debug, PartialEq, Clone, Copy)]1169enum AACategory {1170Other,1171Heap,1172Table,1173VmCtx,1174}11751176impl AACategory {1177pub fn all() -> &'static [Self] {1178&[1179AACategory::Other,1180AACategory::Heap,1181AACategory::Table,1182AACategory::VmCtx,1183]1184}11851186pub fn update_memflags(&self, flags: &mut MemFlags) {1187flags.set_alias_region(match self {1188AACategory::Other => None,1189AACategory::Heap => Some(AliasRegion::Heap),1190AACategory::Table => Some(AliasRegion::Table),1191AACategory::VmCtx => Some(AliasRegion::Vmctx),1192})1193}1194}11951196pub type StackAlignment = StackSize;11971198#[derive(Default)]1199struct Resources {1200vars: HashMap<Type, Vec<Variable>>,1201blocks: Vec<(Block, BlockSignature)>,1202blocks_without_params: Vec<Block>,1203block_terminators: Vec<BlockTerminator>,1204func_refs: Vec<(Signature, SigRef, FuncRef)>,1205/// This field is required to be sorted by stack slot size at all times.1206/// We use this invariant when searching for stack slots with a given size.1207/// See [FunctionGenerator::stack_slot_with_size]1208stack_slots: Vec<(StackSlot, StackSize, StackAlignment, AACategory)>,1209usercalls: Vec<(UserExternalName, Signature)>,1210libcalls: Vec<LibCall>,1211}12121213impl Resources {1214/// Partitions blocks at `block`. Only blocks that can be targeted by branches are considered.1215///1216/// The first slice includes all blocks up to and including `block`.1217/// The second slice includes all remaining blocks.1218fn partition_target_blocks(1219&self,1220block: Block,1221) -> (&[(Block, BlockSignature)], &[(Block, BlockSignature)]) {1222// Blocks are stored in-order and have no gaps, this means that we can simply index them by1223// their number. We also need to exclude the entry block since it isn't a valid target.1224let target_blocks = &self.blocks[1..];1225target_blocks.split_at(block.as_u32() as usize)1226}12271228/// Returns blocks forward of `block`. Only blocks that can be targeted by branches are considered.1229fn forward_blocks(&self, block: Block) -> &[(Block, BlockSignature)] {1230let (_, forward_blocks) = self.partition_target_blocks(block);1231forward_blocks1232}12331234/// Generates a slice of `blocks_without_params` ahead of `block`1235fn forward_blocks_without_params(&self, block: Block) -> &[Block] {1236let partition_point = self.blocks_without_params.partition_point(|b| *b <= block);1237&self.blocks_without_params[partition_point..]1238}12391240/// Generates an iterator of all valid tail call targets. This includes all functions with both1241/// the `tail` calling convention and the same return values as the caller.1242fn tail_call_targets<'a>(1243&'a self,1244caller_sig: &'a Signature,1245) -> impl Iterator<Item = &'a (Signature, SigRef, FuncRef)> {1246self.func_refs.iter().filter(|(sig, _, _)| {1247sig.call_conv == CallConv::Tail && sig.returns == caller_sig.returns1248})1249}1250}12511252impl<'r, 'data> FunctionGenerator<'r, 'data>1253where1254'data: 'r,1255{1256pub fn new(1257u: &'r mut Unstructured<'data>,1258config: &'r Config,1259isa: OwnedTargetIsa,1260name: UserFuncName,1261signature: Signature,1262usercalls: Vec<(UserExternalName, Signature)>,1263libcalls: Vec<LibCall>,1264) -> Self {1265Self {1266u,1267config,1268resources: Resources {1269usercalls,1270libcalls,1271..Resources::default()1272},1273isa,1274name,1275signature,1276}1277}12781279/// Generates a random value for config `param`1280fn param(&mut self, param: &RangeInclusive<usize>) -> Result<usize> {1281Ok(self.u.int_in_range(param.clone())?)1282}12831284fn system_callconv(&mut self) -> CallConv {1285// TODO: This currently only runs on linux, so this is the only choice1286// We should improve this once we generate flags and targets1287CallConv::SystemV1288}12891290/// Finds a stack slot with size of at least n bytes1291fn stack_slot_with_size(1292&mut self,1293n: u32,1294) -> Result<(StackSlot, StackSize, StackAlignment, AACategory)> {1295let first = self1296.resources1297.stack_slots1298.partition_point(|&(_slot, size, _align, _category)| size < n);1299Ok(*self.u.choose(&self.resources.stack_slots[first..])?)1300}13011302/// Generates an address that should allow for a store or a load.1303///1304/// Addresses aren't generated like other values. They are never stored in variables so that1305/// we don't run the risk of returning them from a function, which would make the fuzzer1306/// complain since they are different from the interpreter to the backend.1307///1308/// `min_size`: Controls the amount of space that the address should have.1309///1310/// `aligned`: When passed as true, the resulting address is guaranteed to be aligned1311/// on an 8 byte boundary.1312///1313/// Returns a valid address and the maximum possible offset that still respects `min_size`.1314fn generate_load_store_address(1315&mut self,1316builder: &mut FunctionBuilder,1317min_size: u32,1318aligned: bool,1319) -> Result<(Value, u32, AACategory)> {1320// TODO: Currently our only source of addresses is stack_addr, but we1321// should add global_value, symbol_value eventually1322let (addr, available_size, category) = {1323let (ss, slot_size, _align, category) = self.stack_slot_with_size(min_size)?;13241325// stack_slot_with_size guarantees that slot_size >= min_size1326let max_offset = slot_size - min_size;1327let offset = if aligned {1328self.u.int_in_range(0..=max_offset / min_size)? * min_size1329} else {1330self.u.int_in_range(0..=max_offset)?1331};13321333let base_addr = builder.ins().stack_addr(I64, ss, offset as i32);1334let available_size = slot_size.saturating_sub(offset);1335(base_addr, available_size, category)1336};13371338// TODO: Insert a bunch of amode opcodes here to modify the address!13391340// Now that we have an address and a size, we just choose a random offset to return to the1341// caller. Preserving min_size bytes.1342let max_offset = available_size.saturating_sub(min_size);1343Ok((addr, max_offset, category))1344}13451346// Generates an address and memflags for a load or store.1347fn generate_address_and_memflags(1348&mut self,1349builder: &mut FunctionBuilder,1350min_size: u32,1351is_atomic: bool,1352) -> Result<(Value, MemFlags, Offset32)> {1353// Should we generate an aligned address1354// Some backends have issues with unaligned atomics.1355// AArch64: https://github.com/bytecodealliance/wasmtime/issues/54831356// RISCV: https://github.com/bytecodealliance/wasmtime/issues/58821357let requires_aligned_atomics = matches!(1358self.isa.triple().architecture,1359Architecture::Aarch64(_) | Architecture::Riscv64(_)1360);1361let aligned = if is_atomic && requires_aligned_atomics {1362true1363} else if min_size > 8 {1364// TODO: We currently can't guarantee that a stack_slot will be aligned on a 16 byte1365// boundary. We don't have a way to specify alignment when creating stack slots, and1366// cranelift only guarantees 8 byte alignment between stack slots.1367// See: https://github.com/bytecodealliance/wasmtime/issues/5922#issuecomment-14579266241368false1369} else {1370bool::arbitrary(self.u)?1371};13721373let mut flags = MemFlags::new();1374// Even if we picked an aligned address, we can always generate unaligned memflags1375if aligned && bool::arbitrary(self.u)? {1376flags.set_aligned();1377}1378// If the address is aligned, then we know it won't trap1379if aligned && bool::arbitrary(self.u)? {1380flags.set_notrap();1381}13821383let (address, max_offset, category) =1384self.generate_load_store_address(builder, min_size, aligned)?;13851386// Set the Alias Analysis bits on the memflags1387category.update_memflags(&mut flags);13881389// Pick an offset to pass into the load/store.1390let offset = if aligned {139101392} else {1393self.u.int_in_range(0..=max_offset)? as i321394}1395.into();13961397Ok((address, flags, offset))1398}13991400/// Get a variable of type `ty` from the current function1401fn get_variable_of_type(&mut self, ty: Type) -> Result<Variable> {1402let opts = self.resources.vars.get(&ty).map_or(&[][..], Vec::as_slice);1403let var = self.u.choose(opts)?;1404Ok(*var)1405}14061407/// Generates an instruction(`iconst`/`fconst`/etc...) to introduce a constant value1408fn generate_const(&mut self, builder: &mut FunctionBuilder, ty: Type) -> Result<Value> {1409Ok(match self.u.datavalue(ty)? {1410DataValue::I8(i) => builder.ins().iconst(ty, i as u8 as i64),1411DataValue::I16(i) => builder.ins().iconst(ty, i as u16 as i64),1412DataValue::I32(i) => builder.ins().iconst(ty, i as u32 as i64),1413DataValue::I64(i) => builder.ins().iconst(ty, i),1414DataValue::I128(i) => {1415let hi = builder.ins().iconst(I64, (i >> 64) as i64);1416let lo = builder.ins().iconst(I64, i as i64);1417builder.ins().iconcat(lo, hi)1418}1419DataValue::F16(f) => builder.ins().f16const(f),1420DataValue::F32(f) => builder.ins().f32const(f),1421DataValue::F64(f) => builder.ins().f64const(f),1422DataValue::F128(f) => {1423let handle = builder.func.dfg.constants.insert(f.into());1424builder.ins().f128const(handle)1425}1426DataValue::V128(bytes) => {1427let data = bytes.to_vec().into();1428let handle = builder.func.dfg.constants.insert(data);1429builder.ins().vconst(ty, handle)1430}1431_ => unimplemented!(),1432})1433}14341435/// Chooses a random block which can be targeted by a jump / branch.1436/// This means any block that is not the first block.1437fn generate_target_block(&mut self, source_block: Block) -> Result<Block> {1438// We try to mostly generate forward branches to avoid generating an excessive amount of1439// infinite loops. But they are still important, so give them a small chance of existing.1440let (backwards_blocks, forward_blocks) =1441self.resources.partition_target_blocks(source_block);1442let ratio = self.config.backwards_branch_ratio;1443let block_targets = if !backwards_blocks.is_empty() && self.u.ratio(ratio.0, ratio.1)? {1444backwards_blocks1445} else {1446forward_blocks1447};1448assert!(!block_targets.is_empty());14491450let (block, _) = self.u.choose(block_targets)?.clone();1451Ok(block)1452}14531454fn generate_values_for_block(1455&mut self,1456builder: &mut FunctionBuilder,1457block: Block,1458) -> Result<Vec<BlockArg>> {1459let (_, sig) = self.resources.blocks[block.as_u32() as usize].clone();1460Ok(self1461.generate_values_for_signature(builder, sig.iter().copied())?1462.into_iter()1463.map(|val| BlockArg::Value(val))1464.collect::<Vec<_>>())1465}14661467fn generate_values_for_signature<I: Iterator<Item = Type>>(1468&mut self,1469builder: &mut FunctionBuilder,1470signature: I,1471) -> Result<Vec<Value>> {1472signature1473.map(|ty| {1474let var = self.get_variable_of_type(ty)?;1475let val = builder.use_var(var);1476Ok(val)1477})1478.collect()1479}14801481/// The terminator that we need to insert has already been picked ahead of time1482/// we just need to build the instructions for it1483fn insert_terminator(1484&mut self,1485builder: &mut FunctionBuilder,1486source_block: Block,1487) -> Result<()> {1488let terminator = self.resources.block_terminators[source_block.as_u32() as usize].clone();14891490match terminator {1491BlockTerminator::Return => {1492let types: Vec<Type> = {1493let rets = &builder.func.signature.returns;1494rets.iter().map(|p| p.value_type).collect()1495};1496let vals = self.generate_values_for_signature(builder, types.into_iter())?;14971498builder.ins().return_(&vals[..]);1499}1500BlockTerminator::Jump(target) => {1501let args = self.generate_values_for_block(builder, target)?;1502builder.ins().jump(target, &args[..]);1503}1504BlockTerminator::Br(left, right) => {1505let left_args = self.generate_values_for_block(builder, left)?;1506let right_args = self.generate_values_for_block(builder, right)?;15071508let condbr_types = [I8, I16, I32, I64, I128];1509let _type = *self.u.choose(&condbr_types[..])?;1510let val = builder.use_var(self.get_variable_of_type(_type)?);1511builder1512.ins()1513.brif(val, left, &left_args[..], right, &right_args[..]);1514}1515BlockTerminator::BrTable(default, targets) => {1516// Create jump tables on demand1517let mut jt = Vec::with_capacity(targets.len());1518for block in targets {1519let args = self.generate_values_for_block(builder, block)?;1520jt.push(builder.func.dfg.block_call(block, &args))1521}15221523let args = self.generate_values_for_block(builder, default)?;1524let jt_data = JumpTableData::new(builder.func.dfg.block_call(default, &args), &jt);1525let jt = builder.create_jump_table(jt_data);15261527// br_table only supports I321528let val = builder.use_var(self.get_variable_of_type(I32)?);15291530builder.ins().br_table(val, jt);1531}1532BlockTerminator::Switch(_type, default, entries) => {1533let mut switch = Switch::new();1534for (&entry, &block) in entries.iter() {1535switch.set_entry(entry, block);1536}15371538let switch_val = builder.use_var(self.get_variable_of_type(_type)?);15391540switch.emit(builder, switch_val, default);1541}1542BlockTerminator::TailCall(target) | BlockTerminator::TailCallIndirect(target) => {1543let (sig, sig_ref, func_ref) = self1544.resources1545.func_refs1546.iter()1547.find(|(_, _, f)| *f == target)1548.expect("Failed to find previously selected function")1549.clone();15501551let opcode = match terminator {1552BlockTerminator::TailCall(_) => Opcode::ReturnCall,1553BlockTerminator::TailCallIndirect(_) => Opcode::ReturnCallIndirect,1554_ => unreachable!(),1555};15561557insert_call_to_function(self, builder, opcode, &sig, sig_ref, func_ref)?;1558}1559}15601561Ok(())1562}15631564/// Fills the current block with random instructions1565fn generate_instructions(&mut self, builder: &mut FunctionBuilder) -> Result<()> {1566for _ in 0..self.param(&self.config.instructions_per_block)? {1567let (op, args, rets) = self.u.choose(&OPCODE_SIGNATURES)?;15681569// We filter out instructions that aren't supported by the target at this point instead1570// of building a single vector of valid instructions at the beginning of function1571// generation, to avoid invalidating the corpus when instructions are enabled/disabled.1572if !valid_for_target(&self.isa.triple(), *op, &args, &rets) {1573return Err(arbitrary::Error::IncorrectFormat.into());1574}15751576let inserter = inserter_for_format(op.format());1577inserter(self, builder, *op, &args, &rets)?;1578}15791580Ok(())1581}15821583fn generate_funcrefs(&mut self, builder: &mut FunctionBuilder) -> Result<()> {1584let usercalls: Vec<_> = self1585.resources1586.usercalls1587.iter()1588.map(|(name, signature)| {1589let user_func_ref = builder.func.declare_imported_user_function(name.clone());1590let name = ExternalName::User(user_func_ref);1591(name, signature.clone())1592})1593.collect();15941595let lib_callconv = self.system_callconv();1596let libcalls: Vec<_> = self1597.resources1598.libcalls1599.iter()1600.map(|libcall| {1601let pointer_type = Type::int_with_byte_size(1602self.isa.triple().pointer_width().unwrap().bytes().into(),1603)1604.unwrap();1605let signature = libcall.signature(lib_callconv, pointer_type);1606let name = ExternalName::LibCall(*libcall);1607(name, signature)1608})1609.collect();16101611for (name, signature) in usercalls.into_iter().chain(libcalls) {1612let sig_ref = builder.import_signature(signature.clone());1613let func_ref = builder.import_function(ExtFuncData {1614name,1615signature: sig_ref,16161617// Libcalls can't be colocated because they can be very far away1618// from allocated memory at runtime, and additionally at this1619// time cranelift-jit puts all functions in their own mmap so1620// they also cannot be colocated.1621colocated: false,1622patchable: false,1623});16241625self.resources1626.func_refs1627.push((signature, sig_ref, func_ref));1628}16291630Ok(())1631}16321633fn generate_stack_slots(&mut self, builder: &mut FunctionBuilder) -> Result<()> {1634for _ in 0..self.param(&self.config.static_stack_slots_per_function)? {1635let bytes = self.param(&self.config.static_stack_slot_size)? as u32;1636let alignment = self.param(&self.config.stack_slot_alignment_log2)? as u8;1637let alignment_bytes = 1 << alignment;16381639let ss_data = StackSlotData::new(StackSlotKind::ExplicitSlot, bytes, alignment);1640let slot = builder.create_sized_stack_slot(ss_data);16411642// Generate one Alias Analysis Category for each slot1643let category = *self.u.choose(AACategory::all())?;16441645self.resources1646.stack_slots1647.push((slot, bytes, alignment_bytes, category));1648}16491650self.resources1651.stack_slots1652.sort_unstable_by_key(|&(_slot, bytes, _align, _category)| bytes);16531654Ok(())1655}16561657/// Zero initializes the stack slot by inserting `stack_store`'s.1658fn initialize_stack_slots(&mut self, builder: &mut FunctionBuilder) -> Result<()> {1659let i8_zero = builder.ins().iconst(I8, 0);1660let i16_zero = builder.ins().iconst(I16, 0);1661let i32_zero = builder.ins().iconst(I32, 0);1662let i64_zero = builder.ins().iconst(I64, 0);1663let i128_zero = builder.ins().uextend(I128, i64_zero);16641665for &(slot, init_size, _align, category) in self.resources.stack_slots.iter() {1666let mut size = init_size;16671668// Insert the largest available store for the remaining size.1669while size != 0 {1670let offset = (init_size - size) as i32;1671let (val, filled) = match size {1672sz if sz / 16 > 0 => (i128_zero, 16),1673sz if sz / 8 > 0 => (i64_zero, 8),1674sz if sz / 4 > 0 => (i32_zero, 4),1675sz if sz / 2 > 0 => (i16_zero, 2),1676_ => (i8_zero, 1),1677};1678let addr = builder.ins().stack_addr(I64, slot, offset);16791680// Each stack slot has an associated category, that means we have to set the1681// correct memflags for it. So we can't use `stack_store` directly.1682let mut flags = MemFlags::new();1683flags.set_notrap();1684category.update_memflags(&mut flags);16851686builder.ins().store(flags, val, addr, 0);16871688size -= filled;1689}1690}1691Ok(())1692}16931694/// Creates a random amount of blocks in this function1695fn generate_blocks(&mut self, builder: &mut FunctionBuilder) -> Result<()> {1696let extra_block_count = self.param(&self.config.blocks_per_function)?;16971698// We must always have at least one block, so we generate the "extra" blocks and add 1 for1699// the entry block.1700let block_count = 1 + extra_block_count;17011702// Blocks need to be sorted in ascending order1703self.resources.blocks = (0..block_count)1704.map(|i| {1705let is_entry = i == 0;1706let block = builder.create_block();17071708// Optionally mark blocks that are not the entry block as cold1709if !is_entry {1710if bool::arbitrary(self.u)? {1711builder.set_cold_block(block);1712}1713}17141715// The first block has to have the function signature, but for the rest of them we generate1716// a random signature;1717if is_entry {1718builder.append_block_params_for_function_params(block);1719Ok((1720block,1721self.signature.params.iter().map(|a| a.value_type).collect(),1722))1723} else {1724let sig = self.generate_block_signature()?;1725sig.iter().for_each(|ty| {1726builder.append_block_param(block, *ty);1727});1728Ok((block, sig))1729}1730})1731.collect::<Result<Vec<_>>>()?;17321733// Valid blocks for jump tables have to have no parameters in the signature, and must also1734// not be the first block.1735self.resources.blocks_without_params = self.resources.blocks[1..]1736.iter()1737.filter(|(_, sig)| sig.len() == 0)1738.map(|(b, _)| *b)1739.collect();17401741// Compute the block CFG1742//1743// cranelift-frontend requires us to never generate unreachable blocks1744// To ensure this property we start by constructing a main "spine" of blocks. So block1 can1745// always jump to block2, and block2 can always jump to block3, etc...1746//1747// That is not a very interesting CFG, so we introduce variations on that, but always1748// ensuring that the property of pointing to the next block is maintained whatever the1749// branching mechanism we use.1750let blocks = self.resources.blocks.clone();1751self.resources.block_terminators = blocks1752.iter()1753.map(|&(block, _)| {1754let next_block = Block::with_number(block.as_u32() + 1).unwrap();1755let forward_blocks = self.resources.forward_blocks(block);1756let paramless_targets = self.resources.forward_blocks_without_params(block);1757let has_paramless_targets = !paramless_targets.is_empty();1758let next_block_is_paramless = paramless_targets.contains(&next_block);17591760let mut valid_terminators = vec![];17611762if forward_blocks.is_empty() {1763// Return is only valid on the last block.1764valid_terminators.push(BlockTerminatorKind::Return);1765} else {1766// If we have more than one block we can allow terminators that target blocks.1767// TODO: We could add some kind of BrReturn here, to explore edges where we1768// exit in the middle of the function1769valid_terminators.extend_from_slice(&[1770BlockTerminatorKind::Jump,1771BlockTerminatorKind::Br,1772BlockTerminatorKind::BrTable,1773]);1774}17751776// As the Switch interface only allows targeting blocks without params we need1777// to ensure that the next block has no params, since that one is guaranteed to be1778// picked in either case.1779if has_paramless_targets && next_block_is_paramless {1780valid_terminators.push(BlockTerminatorKind::Switch);1781}17821783// Tail Calls are a block terminator, so we should insert them as any other block1784// terminator. We should ensure that we can select at least one target before considering1785// them as candidate instructions.1786let has_tail_callees = self1787.resources1788.tail_call_targets(&self.signature)1789.next()1790.is_some();1791let is_tail_caller = self.signature.call_conv == CallConv::Tail;17921793let supports_tail_calls = match self.isa.triple().architecture {1794Architecture::Aarch64(_) | Architecture::Riscv64(_) => true,1795// TODO: x64 currently requires frame pointers for tail calls.1796Architecture::X86_64 => self.isa.flags().preserve_frame_pointers(),1797// TODO: Other platforms do not support tail calls yet.1798_ => false,1799};18001801if is_tail_caller && has_tail_callees && supports_tail_calls {1802valid_terminators.extend([1803BlockTerminatorKind::TailCall,1804BlockTerminatorKind::TailCallIndirect,1805]);1806}18071808let terminator = self.u.choose(&valid_terminators)?;18091810// Choose block targets for the terminators that we picked above1811Ok(match terminator {1812BlockTerminatorKind::Return => BlockTerminator::Return,1813BlockTerminatorKind::Jump => BlockTerminator::Jump(next_block),1814BlockTerminatorKind::Br => {1815BlockTerminator::Br(next_block, self.generate_target_block(block)?)1816}1817// TODO: Allow generating backwards branches here1818BlockTerminatorKind::BrTable => {1819// Make the default the next block, and then we don't have to worry1820// that we can reach it via the targets1821let default = next_block;18221823let target_count = self.param(&self.config.jump_table_entries)?;1824let targets = Result::from_iter(1825(0..target_count).map(|_| self.generate_target_block(block)),1826)?;18271828BlockTerminator::BrTable(default, targets)1829}1830BlockTerminatorKind::Switch => {1831// Make the default the next block, and then we don't have to worry1832// that we can reach it via the entries below1833let default_block = next_block;18341835let _type = *self.u.choose(&[I8, I16, I32, I64, I128][..])?;18361837// Build this into a HashMap since we cannot have duplicate entries.1838let mut entries = HashMap::new();1839for _ in 0..self.param(&self.config.switch_cases)? {1840// The Switch API only allows for entries that are addressable by the index type1841// so we need to limit the range of values that we generate.1842let (ty_min, ty_max) = _type.bounds(false);1843let range_start = self.u.int_in_range(ty_min..=ty_max)?;18441845// We can either insert a contiguous range of blocks or a individual block1846// This is done because the Switch API specializes contiguous ranges.1847let range_size = if bool::arbitrary(self.u)? {184811849} else {1850self.param(&self.config.switch_max_range_size)?1851} as u128;18521853// Build the switch entries1854for i in 0..range_size {1855let index = range_start.wrapping_add(i) % ty_max;1856let block = *self1857.u1858.choose(self.resources.forward_blocks_without_params(block))?;18591860entries.insert(index, block);1861}1862}18631864BlockTerminator::Switch(_type, default_block, entries)1865}1866BlockTerminatorKind::TailCall => {1867let targets = self1868.resources1869.tail_call_targets(&self.signature)1870.collect::<Vec<_>>();1871let (_, _, funcref) = *self.u.choose(&targets[..])?;1872BlockTerminator::TailCall(*funcref)1873}1874BlockTerminatorKind::TailCallIndirect => {1875let targets = self1876.resources1877.tail_call_targets(&self.signature)1878.collect::<Vec<_>>();1879let (_, _, funcref) = *self.u.choose(&targets[..])?;1880BlockTerminator::TailCallIndirect(*funcref)1881}1882})1883})1884.collect::<Result<_>>()?;18851886Ok(())1887}18881889fn generate_block_signature(&mut self) -> Result<BlockSignature> {1890let param_count = self.param(&self.config.block_signature_params)?;18911892let mut params = Vec::with_capacity(param_count);1893for _ in 0..param_count {1894params.push(self.u._type((&*self.isa).supports_simd())?);1895}1896Ok(params)1897}18981899fn build_variable_pool(&mut self, builder: &mut FunctionBuilder) -> Result<()> {1900let block = builder.current_block().unwrap();19011902// Define variables for the function signature1903let mut vars: Vec<_> = builder1904.func1905.signature1906.params1907.iter()1908.map(|param| param.value_type)1909.zip(builder.block_params(block).iter().copied())1910.collect();19111912// Create a pool of vars that are going to be used in this function1913for _ in 0..self.param(&self.config.vars_per_function)? {1914let ty = self.u._type((&*self.isa).supports_simd())?;1915let value = self.generate_const(builder, ty)?;1916vars.push((ty, value));1917}19181919for (ty, value) in vars.into_iter() {1920let var = builder.declare_var(ty);1921builder.def_var(var, value);19221923// Randomly declare variables as needing a stack map.1924// We limit these to only types that have fewer than 16 bytes1925// since the stack map mechanism does not support larger types.1926if ty.bytes() <= 16 && self.u.arbitrary()? {1927builder.declare_var_needs_stack_map(var);1928}19291930self.resources1931.vars1932.entry(ty)1933.or_insert_with(Vec::new)1934.push(var);1935}19361937Ok(())1938}19391940/// We generate a function in multiple stages:1941///1942/// * First we generate a random number of empty blocks1943/// * Then we generate a random pool of variables to be used throughout the function1944/// * We then visit each block and generate random instructions1945///1946/// Because we generate all blocks and variables up front we already know everything that1947/// we need when generating instructions (i.e. jump targets / variables)1948pub fn generate(mut self) -> Result<Function> {1949let mut fn_builder_ctx = FunctionBuilderContext::new();1950let mut func = Function::with_name_signature(self.name.clone(), self.signature.clone());19511952let mut builder = FunctionBuilder::new(&mut func, &mut fn_builder_ctx);19531954// Build the function references before generating the block CFG since we store1955// function references in the CFG.1956self.generate_funcrefs(&mut builder)?;1957self.generate_blocks(&mut builder)?;19581959// Function preamble1960self.generate_stack_slots(&mut builder)?;19611962// Main instruction generation loop1963for (block, block_sig) in self.resources.blocks.clone().into_iter() {1964let is_block0 = block.as_u32() == 0;1965builder.switch_to_block(block);19661967if is_block0 {1968// The first block is special because we must create variables both for the1969// block signature and for the variable pool. Additionally, we must also define1970// initial values for all variables that are not the function signature.1971self.build_variable_pool(&mut builder)?;19721973// Stack slots have random bytes at the beginning of the function1974// initialize them to a constant value so that execution stays predictable.1975self.initialize_stack_slots(&mut builder)?;1976} else {1977// Define variables for the block params1978for (i, ty) in block_sig.iter().enumerate() {1979let var = self.get_variable_of_type(*ty)?;1980let block_param = builder.block_params(block)[i];1981builder.def_var(var, block_param);1982}1983}19841985// Generate block instructions1986self.generate_instructions(&mut builder)?;19871988// Insert a terminator to safely exit the block1989self.insert_terminator(&mut builder, block)?;1990}19911992builder.seal_all_blocks();1993builder.finalize();19941995Ok(func)1996}1997}199819992000