Path: blob/main/cranelift/fuzzgen/src/function_generator.rs
1692 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();121122insert_call_to_function(fgen, builder, opcode, &sig, sig_ref, func_ref)123}124125fn insert_stack_load(126fgen: &mut FunctionGenerator,127builder: &mut FunctionBuilder,128_opcode: Opcode,129_args: &[Type],130rets: &[Type],131) -> Result<()> {132let typevar = rets[0];133let type_size = typevar.bytes();134let (slot, slot_size, _align, category) = fgen.stack_slot_with_size(type_size)?;135136// `stack_load` doesn't support setting MemFlags, and it does not set any137// alias analysis bits, so we can only emit it for `Other` slots.138if category != AACategory::Other {139return Err(arbitrary::Error::IncorrectFormat.into());140}141142let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;143144let val = builder.ins().stack_load(typevar, slot, offset);145let var = fgen.get_variable_of_type(typevar)?;146builder.def_var(var, val);147148Ok(())149}150151fn insert_stack_store(152fgen: &mut FunctionGenerator,153builder: &mut FunctionBuilder,154_opcode: Opcode,155args: &[Type],156_rets: &[Type],157) -> Result<()> {158let typevar = args[0];159let type_size = typevar.bytes();160161let (slot, slot_size, _align, category) = fgen.stack_slot_with_size(type_size)?;162163// `stack_store` doesn't support setting MemFlags, and it does not set any164// alias analysis bits, so we can only emit it for `Other` slots.165if category != AACategory::Other {166return Err(arbitrary::Error::IncorrectFormat.into());167}168169let offset = fgen.u.int_in_range(0..=(slot_size - type_size))? as i32;170171let arg0 = fgen.get_variable_of_type(typevar)?;172let arg0 = builder.use_var(arg0);173174builder.ins().stack_store(arg0, slot, offset);175Ok(())176}177178fn insert_cmp(179fgen: &mut FunctionGenerator,180builder: &mut FunctionBuilder,181opcode: Opcode,182args: &[Type],183rets: &[Type],184) -> Result<()> {185let lhs = fgen.get_variable_of_type(args[0])?;186let lhs = builder.use_var(lhs);187188let rhs = fgen.get_variable_of_type(args[1])?;189let rhs = builder.use_var(rhs);190191let res = if opcode == Opcode::Fcmp {192let cc = *fgen.u.choose(FloatCC::all())?;193194// We filter out condition codes that aren't supported by the target at195// this point after randomly choosing one, instead of randomly choosing a196// supported one, to avoid invalidating the corpus when these get implemented.197let unimplemented_cc = match (fgen.isa.triple().architecture, cc) {198// Some FloatCC's are not implemented on AArch64, see:199// https://github.com/bytecodealliance/wasmtime/issues/4850200(Architecture::Aarch64(_), FloatCC::OrderedNotEqual) => true,201(Architecture::Aarch64(_), FloatCC::UnorderedOrEqual) => true,202(Architecture::Aarch64(_), FloatCC::UnorderedOrLessThan) => true,203(Architecture::Aarch64(_), FloatCC::UnorderedOrLessThanOrEqual) => true,204(Architecture::Aarch64(_), FloatCC::UnorderedOrGreaterThan) => true,205(Architecture::Aarch64(_), FloatCC::UnorderedOrGreaterThanOrEqual) => true,206207// These are not implemented on x86_64, for vectors.208(Architecture::X86_64, FloatCC::UnorderedOrEqual | FloatCC::OrderedNotEqual) => {209args[0].is_vector()210}211_ => false,212};213if unimplemented_cc {214return Err(arbitrary::Error::IncorrectFormat.into());215}216217builder.ins().fcmp(cc, lhs, rhs)218} else {219let cc = *fgen.u.choose(IntCC::all())?;220builder.ins().icmp(cc, lhs, rhs)221};222223let var = fgen.get_variable_of_type(rets[0])?;224builder.def_var(var, res);225226Ok(())227}228229fn insert_const(230fgen: &mut FunctionGenerator,231builder: &mut FunctionBuilder,232_opcode: Opcode,233_args: &[Type],234rets: &[Type],235) -> Result<()> {236let typevar = rets[0];237let var = fgen.get_variable_of_type(typevar)?;238let val = fgen.generate_const(builder, typevar)?;239builder.def_var(var, val);240Ok(())241}242243fn insert_bitcast(244fgen: &mut FunctionGenerator,245builder: &mut FunctionBuilder,246args: &[Type],247rets: &[Type],248) -> Result<()> {249let from_var = fgen.get_variable_of_type(args[0])?;250let from_val = builder.use_var(from_var);251252let to_var = fgen.get_variable_of_type(rets[0])?;253254// TODO: We can generate little/big endian flags here.255let mut memflags = MemFlags::new();256257// When bitcasting between vectors of different lane counts, we need to258// specify the endianness.259if args[0].lane_count() != rets[0].lane_count() {260memflags.set_endianness(Endianness::Little);261}262263let res = builder.ins().bitcast(rets[0], memflags, from_val);264builder.def_var(to_var, res);265Ok(())266}267268fn insert_load_store(269fgen: &mut FunctionGenerator,270builder: &mut FunctionBuilder,271opcode: Opcode,272args: &[Type],273rets: &[Type],274) -> Result<()> {275if opcode == Opcode::Bitcast {276return insert_bitcast(fgen, builder, args, rets);277}278279let ctrl_type = *rets.first().or(args.first()).unwrap();280let type_size = ctrl_type.bytes();281282let is_atomic = [Opcode::AtomicLoad, Opcode::AtomicStore].contains(&opcode);283let (address, flags, offset) =284fgen.generate_address_and_memflags(builder, type_size, is_atomic)?;285286// The variable being loaded or stored into287let var = fgen.get_variable_of_type(ctrl_type)?;288289match opcode.format() {290InstructionFormat::LoadNoOffset => {291let (inst, dfg) = builder292.ins()293.LoadNoOffset(opcode, ctrl_type, flags, address);294295let new_val = dfg.first_result(inst);296builder.def_var(var, new_val);297}298InstructionFormat::StoreNoOffset => {299let val = builder.use_var(var);300301builder302.ins()303.StoreNoOffset(opcode, ctrl_type, flags, val, address);304}305InstructionFormat::Store => {306let val = builder.use_var(var);307308builder309.ins()310.Store(opcode, ctrl_type, flags, offset, val, address);311}312InstructionFormat::Load => {313let (inst, dfg) = builder314.ins()315.Load(opcode, ctrl_type, flags, offset, address);316317let new_val = dfg.first_result(inst);318builder.def_var(var, new_val);319}320_ => unimplemented!(),321}322323Ok(())324}325326fn insert_atomic_rmw(327fgen: &mut FunctionGenerator,328builder: &mut FunctionBuilder,329_: Opcode,330_: &[Type],331rets: &[Type],332) -> Result<()> {333let ctrl_type = *rets.first().unwrap();334let type_size = ctrl_type.bytes();335336let rmw_op = *fgen.u.choose(AtomicRmwOp::all())?;337338let (address, flags, offset) = fgen.generate_address_and_memflags(builder, type_size, true)?;339340// AtomicRMW does not directly support offsets, so add the offset to the address separately.341let address = builder.ins().iadd_imm(address, i64::from(offset));342343// Load and store target variables344let source_var = fgen.get_variable_of_type(ctrl_type)?;345let target_var = fgen.get_variable_of_type(ctrl_type)?;346347let source_val = builder.use_var(source_var);348let new_val = builder349.ins()350.atomic_rmw(ctrl_type, flags, rmw_op, address, source_val);351352builder.def_var(target_var, new_val);353Ok(())354}355356fn insert_atomic_cas(357fgen: &mut FunctionGenerator,358builder: &mut FunctionBuilder,359_: Opcode,360_: &[Type],361rets: &[Type],362) -> Result<()> {363let ctrl_type = *rets.first().unwrap();364let type_size = ctrl_type.bytes();365366let (address, flags, offset) = fgen.generate_address_and_memflags(builder, type_size, true)?;367368// AtomicCas does not directly support offsets, so add the offset to the address separately.369let address = builder.ins().iadd_imm(address, i64::from(offset));370371// Source and Target variables372let expected_var = fgen.get_variable_of_type(ctrl_type)?;373let store_var = fgen.get_variable_of_type(ctrl_type)?;374let loaded_var = fgen.get_variable_of_type(ctrl_type)?;375376let expected_val = builder.use_var(expected_var);377let store_val = builder.use_var(store_var);378let new_val = builder379.ins()380.atomic_cas(flags, address, expected_val, store_val);381382builder.def_var(loaded_var, new_val);383Ok(())384}385386fn insert_shuffle(387fgen: &mut FunctionGenerator,388builder: &mut FunctionBuilder,389opcode: Opcode,390_: &[Type],391rets: &[Type],392) -> Result<()> {393let ctrl_type = *rets.first().unwrap();394395let lhs = builder.use_var(fgen.get_variable_of_type(ctrl_type)?);396let rhs = builder.use_var(fgen.get_variable_of_type(ctrl_type)?);397398let mask = {399let mut lanes = [0u8; 16];400for lane in lanes.iter_mut() {401*lane = fgen.u.int_in_range(0..=31)?;402}403let lanes = ConstantData::from(lanes.as_ref());404builder.func.dfg.immediates.push(lanes)405};406407// This function is called for any `InstructionFormat::Shuffle`. Which today is just408// `shuffle`, but lets assert that, just to be sure we don't accidentally insert409// something else.410assert_eq!(opcode, Opcode::Shuffle);411let res = builder.ins().shuffle(lhs, rhs, mask);412413let target_var = fgen.get_variable_of_type(ctrl_type)?;414builder.def_var(target_var, res);415416Ok(())417}418419fn insert_ins_ext_lane(420fgen: &mut FunctionGenerator,421builder: &mut FunctionBuilder,422opcode: Opcode,423args: &[Type],424rets: &[Type],425) -> Result<()> {426let vector_type = *args.first().unwrap();427let ret_type = *rets.first().unwrap();428429let lhs = builder.use_var(fgen.get_variable_of_type(vector_type)?);430let max_lane = (vector_type.lane_count() as u8) - 1;431let lane = fgen.u.int_in_range(0..=max_lane)?;432433let res = match opcode {434Opcode::Insertlane => {435let rhs = builder.use_var(fgen.get_variable_of_type(args[1])?);436builder.ins().insertlane(lhs, rhs, lane)437}438Opcode::Extractlane => builder.ins().extractlane(lhs, lane),439_ => todo!(),440};441442let target_var = fgen.get_variable_of_type(ret_type)?;443builder.def_var(target_var, res);444445Ok(())446}447448type OpcodeInserter = fn(449fgen: &mut FunctionGenerator,450builder: &mut FunctionBuilder,451Opcode,452&[Type],453&[Type],454) -> Result<()>;455456macro_rules! exceptions {457($op:expr, $args:expr, $rets:expr, $(($($cases:pat),*)),* $(,)?) => {458match ($op, $args, $rets) {459$( ($($cases,)* ..) => return false, )*460_ => true,461}462}463}464465/// Returns true if we believe this `OpcodeSignature` should compile correctly466/// for the given target triple. We currently have a range of known issues467/// with specific lowerings on specific backends, and we don't want to get468/// fuzz bug reports for those. Over time our goal is to eliminate all of these469/// exceptions.470fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) -> bool {471// Rule out invalid combinations that we don't yet have a good way of rejecting with the472// instruction DSL type constraints.473match op {474Opcode::FcvtToUintSat | Opcode::FcvtToSintSat => {475assert_eq!(args.len(), 1);476assert_eq!(rets.len(), 1);477478let arg = args[0];479let ret = rets[0];480481// Vector arguments must produce vector results, and scalar arguments must produce482// scalar results.483if arg.is_vector() != ret.is_vector() {484return false;485}486487if arg.is_vector() && ret.is_vector() {488// Vector conversions must have the same number of lanes, and the lanes must be the489// same bit-width.490if arg.lane_count() != ret.lane_count() {491return false;492}493494if arg.lane_of().bits() != ret.lane_of().bits() {495return false;496}497}498}499500Opcode::Bitcast => {501assert_eq!(args.len(), 1);502assert_eq!(rets.len(), 1);503504let arg = args[0];505let ret = rets[0];506507// The opcode generator still allows bitcasts between different sized types, but these508// are rejected in the verifier.509if arg.bits() != ret.bits() {510return false;511}512}513514// This requires precise runtime integration so it's not supported at515// all in fuzzgen just yet.516Opcode::StackSwitch => return false,517518_ => {}519}520521match triple.architecture {522Architecture::X86_64 => {523exceptions!(524op,525args,526rets,527(Opcode::UmulOverflow | Opcode::SmulOverflow, &[I128, I128]),528(Opcode::Imul, &[I8X16, I8X16]),529// https://github.com/bytecodealliance/wasmtime/issues/4756530(Opcode::Udiv | Opcode::Sdiv, &[I128, I128]),531// https://github.com/bytecodealliance/wasmtime/issues/5474532(Opcode::Urem | Opcode::Srem, &[I128, I128]),533// https://github.com/bytecodealliance/wasmtime/issues/3370534(535Opcode::Smin | Opcode::Umin | Opcode::Smax | Opcode::Umax,536&[I128, I128]537),538// https://github.com/bytecodealliance/wasmtime/issues/5107539(Opcode::Cls, &[I8], &[I8]),540(Opcode::Cls, &[I16], &[I16]),541(Opcode::Cls, &[I32], &[I32]),542(Opcode::Cls, &[I64], &[I64]),543(Opcode::Cls, &[I128], &[I128]),544// TODO545(Opcode::Bitselect, &[_, _, _], &[F32 | F64]),546// https://github.com/bytecodealliance/wasmtime/issues/4897547// https://github.com/bytecodealliance/wasmtime/issues/4899548(549Opcode::FcvtToUint550| Opcode::FcvtToUintSat551| Opcode::FcvtToSint552| Opcode::FcvtToSintSat,553&[F32 | F64],554&[I8 | I16 | I128]555),556(Opcode::FcvtToUint | Opcode::FcvtToSint, &[F32X4], &[I32X4]),557(558Opcode::FcvtToUint559| Opcode::FcvtToUintSat560| Opcode::FcvtToSint561| Opcode::FcvtToSintSat,562&[F64X2],563&[I64X2]564),565// https://github.com/bytecodealliance/wasmtime/issues/4900566(Opcode::FcvtFromUint, &[I128], &[F32 | F64]),567// This has a lowering, but only when preceded by `uwiden_low`.568(Opcode::FcvtFromUint, &[I64X2], &[F64X2]),569// https://github.com/bytecodealliance/wasmtime/issues/4900570(Opcode::FcvtFromSint, &[I128], &[F32 | F64]),571(Opcode::FcvtFromSint, &[I64X2], &[F64X2]),572(573Opcode::Umulhi | Opcode::Smulhi,574&([I8X16, I8X16] | [I16X8, I16X8] | [I32X4, I32X4] | [I64X2, I64X2])575),576(577Opcode::UaddSat | Opcode::SaddSat | Opcode::UsubSat | Opcode::SsubSat,578&([I32X4, I32X4] | [I64X2, I64X2])579),580(Opcode::Fcopysign, &([F32X4, F32X4] | [F64X2, F64X2])),581(Opcode::Popcnt, &([I8X16] | [I16X8] | [I32X4] | [I64X2])),582(583Opcode::Umax | Opcode::Smax | Opcode::Umin | Opcode::Smin,584&[I64X2, I64X2]585),586// https://github.com/bytecodealliance/wasmtime/issues/6104587(Opcode::Bitcast, &[I128], &[_]),588(Opcode::Bitcast, &[_], &[I128]),589(Opcode::Uunarrow),590(Opcode::Snarrow | Opcode::Unarrow, &[I64X2, I64X2]),591(Opcode::SqmulRoundSat, &[I32X4, I32X4]),592// This Icmp is not implemented: #5529593(Opcode::Icmp, &[I64X2, I64X2]),594// IaddPairwise is implemented, but only for some types, and with some preceding ops.595(Opcode::IaddPairwise),596// Nothing wrong with this select. But we have an isle rule that can optimize it597// into a `min`/`max` instructions, which we don't have implemented yet.598(Opcode::Select, &[_, I128, I128]),599// These stack accesses can cause segfaults if they are merged into an SSE instruction.600// See: #5922601(602Opcode::StackStore,603&[I8X16 | I16X8 | I32X4 | I64X2 | F32X4 | F64X2]604),605(606Opcode::StackLoad,607&[],608&[I8X16 | I16X8 | I32X4 | I64X2 | F32X4 | F64X2]609),610// TODO611(612Opcode::Sshr | Opcode::Ushr | Opcode::Ishl,613&[I8X16 | I16X8 | I32X4 | I64X2, I128]614),615(616Opcode::Rotr | Opcode::Rotl,617&[I8X16 | I16X8 | I32X4 | I64X2, _]618),619)620}621622Architecture::Aarch64(_) => {623exceptions!(624op,625args,626rets,627(Opcode::UmulOverflow | Opcode::SmulOverflow, &[I128, I128]),628// https://github.com/bytecodealliance/wasmtime/issues/4864629(Opcode::Udiv | Opcode::Sdiv, &[I128, I128]),630// https://github.com/bytecodealliance/wasmtime/issues/5472631(Opcode::Urem | Opcode::Srem, &[I128, I128]),632// https://github.com/bytecodealliance/wasmtime/issues/4313633(634Opcode::Smin | Opcode::Umin | Opcode::Smax | Opcode::Umax,635&[I128, I128]636),637// https://github.com/bytecodealliance/wasmtime/issues/4870638(Opcode::Bnot, &[F32 | F64]),639(640Opcode::Band641| Opcode::Bor642| Opcode::Bxor643| Opcode::BandNot644| Opcode::BorNot645| Opcode::BxorNot,646&([F32, F32] | [F64, F64])647),648// https://github.com/bytecodealliance/wasmtime/issues/5198649(Opcode::Bitselect, &[I128, I128, I128]),650// https://github.com/bytecodealliance/wasmtime/issues/4934651(652Opcode::FcvtToUint653| Opcode::FcvtToUintSat654| Opcode::FcvtToSint655| Opcode::FcvtToSintSat,656&[F32 | F64],657&[I128]658),659// https://github.com/bytecodealliance/wasmtime/issues/4933660(661Opcode::FcvtFromUint | Opcode::FcvtFromSint,662&[I128],663&[F32 | F64]664),665(666Opcode::Umulhi | Opcode::Smulhi,667&([I8X16, I8X16] | [I16X8, I16X8] | [I32X4, I32X4] | [I64X2, I64X2])668),669(Opcode::Popcnt, &[I16X8 | I32X4 | I64X2]),670// Nothing wrong with this select. But we have an isle rule that can optimize it671// into a `min`/`max` instructions, which we don't have implemented yet.672(Opcode::Select, &[I8, I128, I128]),673// https://github.com/bytecodealliance/wasmtime/issues/6104674(Opcode::Bitcast, &[I128], &[_]),675(Opcode::Bitcast, &[_], &[I128]),676// TODO677(678Opcode::Sshr | Opcode::Ushr | Opcode::Ishl,679&[I8X16 | I16X8 | I32X4 | I64X2, I128]680),681(682Opcode::Rotr | Opcode::Rotl,683&[I8X16 | I16X8 | I32X4 | I64X2, _]684),685// TODO686(Opcode::Bitselect, &[_, _, _], &[F32 | F64]),687(Opcode::VhighBits, &[F32X4 | F64X2]),688)689}690691Architecture::S390x => {692exceptions!(693op,694args,695rets,696(Opcode::UaddOverflow | Opcode::SaddOverflow),697(Opcode::UsubOverflow | Opcode::SsubOverflow),698(Opcode::UmulOverflow | Opcode::SmulOverflow),699(700Opcode::Udiv | Opcode::Sdiv | Opcode::Urem | Opcode::Srem,701&[I128, I128]702),703(Opcode::Bnot, &[F32 | F64]),704(705Opcode::Band706| Opcode::Bor707| Opcode::Bxor708| Opcode::BandNot709| Opcode::BorNot710| Opcode::BxorNot,711&([F32, F32] | [F64, F64])712),713(714Opcode::FcvtToUint715| Opcode::FcvtToUintSat716| Opcode::FcvtToSint717| Opcode::FcvtToSintSat,718&[F32 | F64],719&[I128]720),721(722Opcode::FcvtFromUint | Opcode::FcvtFromSint,723&[I128],724&[F32 | F64]725),726(Opcode::SsubSat | Opcode::SaddSat, &[I64X2, I64X2]),727// https://github.com/bytecodealliance/wasmtime/issues/6104728(Opcode::Bitcast, &[I128], &[_]),729(Opcode::Bitcast, &[_], &[I128]),730// TODO731(Opcode::Bitselect, &[_, _, _], &[F32 | F64]),732)733}734735Architecture::Riscv64(_) => {736exceptions!(737op,738args,739rets,740// TODO741(Opcode::UaddOverflow | Opcode::SaddOverflow),742(Opcode::UsubOverflow | Opcode::SsubOverflow),743(Opcode::UmulOverflow | Opcode::SmulOverflow),744// TODO745(746Opcode::Udiv | Opcode::Sdiv | Opcode::Urem | Opcode::Srem,747&[I128, I128]748),749// TODO750(Opcode::Iabs, &[I128]),751// TODO752(Opcode::Bitselect, &[I128, I128, I128]),753// https://github.com/bytecodealliance/wasmtime/issues/5528754(755Opcode::FcvtToUint | Opcode::FcvtToSint,756[F32 | F64],757&[I128]758),759(760Opcode::FcvtToUintSat | Opcode::FcvtToSintSat,761&[F32 | F64],762&[I128]763),764// https://github.com/bytecodealliance/wasmtime/issues/5528765(766Opcode::FcvtFromUint | Opcode::FcvtFromSint,767&[I128],768&[F32 | F64]769),770// TODO771(772Opcode::SelectSpectreGuard,773&[_, _, _],774&[F32 | F64 | I8X16 | I16X8 | I32X4 | I64X2 | F64X2 | F32X4]775),776// TODO777(Opcode::Bitselect, &[_, _, _], &[F32 | F64]),778(779Opcode::Rotr | Opcode::Rotl,780&[I8X16 | I16X8 | I32X4 | I64X2, _]781),782)783}784785_ => true,786}787}788789type OpcodeSignature = (Opcode, Vec<Type>, Vec<Type>);790791static OPCODE_SIGNATURES: LazyLock<Vec<OpcodeSignature>> = LazyLock::new(|| {792let types = &[793I8, I16, I32, I64, I128, // Scalar Integers794F32, F64, // Scalar Floats795I8X16, I16X8, I32X4, I64X2, // SIMD Integers796F32X4, F64X2, // SIMD Floats797];798799// When this env variable is passed, we only generate instructions for the opcodes listed in800// the comma-separated list. This is useful for debugging, as it allows us to focus on a few801// specific opcodes.802let allowed_opcodes = std::env::var("FUZZGEN_ALLOWED_OPS").ok().map(|s| {803s.split(',')804.map(|s| s.trim())805.filter(|s| !s.is_empty())806.map(|s| Opcode::from_str(s).expect("Unrecoginzed opcode"))807.collect::<Vec<_>>()808});809810Opcode::all()811.iter()812.filter(|op| {813match op {814// Control flow opcodes should not be generated through `generate_instructions`.815Opcode::BrTable816| Opcode::Brif817| Opcode::Jump818| Opcode::Return819| Opcode::ReturnCall820| Opcode::ReturnCallIndirect821| Opcode::TryCall822| Opcode::TryCallIndirect => false,823824// Constants are generated outside of `generate_instructions`825Opcode::Iconst => false,826827// TODO: extract_vector raises exceptions during return type generation because it828// uses dynamic vectors.829Opcode::ExtractVector => false,830831_ => true,832}833})834.flat_map(|op| {835let constraints = op.constraints();836837let ctrl_types = if let Some(ctrls) = constraints.ctrl_typeset() {838Vec::from_iter(types.iter().copied().filter(|ty| ctrls.contains(*ty)))839} else {840vec![INVALID]841};842843ctrl_types.into_iter().flat_map(move |ctrl_type| {844let rets = Vec::from_iter(845(0..constraints.num_fixed_results())846.map(|i| constraints.result_type(i, ctrl_type)),847);848849// Cols is a vector whose length will match `num_fixed_value_arguments`, and whose850// elements will be vectors of types that are valid for that fixed argument851// position.852let mut cols = vec![];853854for i in 0..constraints.num_fixed_value_arguments() {855match constraints.value_argument_constraint(i, ctrl_type) {856ResolvedConstraint::Bound(ty) => cols.push(Vec::from([ty])),857ResolvedConstraint::Free(tys) => cols.push(Vec::from_iter(858types.iter().copied().filter(|ty| tys.contains(*ty)),859)),860}861}862863// Generate the cartesian product of cols to produce a vector of argument lists,864// argss. The argss vector is seeded with the empty argument list, so there's an865// initial value to be extended in the loop below.866let mut argss = vec![vec![]];867let mut cols = cols.as_slice();868while let Some((col, rest)) = cols.split_last() {869cols = rest;870871let mut next = vec![];872for current in argss.iter() {873// Extend the front of each argument candidate with every type in `col`.874for ty in col {875let mut args = vec![*ty];876args.extend_from_slice(¤t);877next.push(args);878}879}880881let _ = std::mem::replace(&mut argss, next);882}883884argss.into_iter().map(move |args| (*op, args, rets.clone()))885})886})887.filter(|(op, args, rets)| {888// These op/signature combinations need to be vetted889exceptions!(890op,891args.as_slice(),892rets.as_slice(),893(Opcode::Debugtrap),894(Opcode::Trap),895(Opcode::Trapz),896(Opcode::Trapnz),897(Opcode::CallIndirect, &[I32]),898(Opcode::FuncAddr),899(Opcode::X86Pshufb),900(Opcode::AvgRound),901(Opcode::Uload8x8),902(Opcode::Sload8x8),903(Opcode::Uload16x4),904(Opcode::Sload16x4),905(Opcode::Uload32x2),906(Opcode::Sload32x2),907(Opcode::StackAddr),908(Opcode::DynamicStackLoad),909(Opcode::DynamicStackStore),910(Opcode::DynamicStackAddr),911(Opcode::GlobalValue),912(Opcode::SymbolValue),913(Opcode::TlsValue),914(Opcode::GetPinnedReg),915(Opcode::SetPinnedReg),916(Opcode::GetFramePointer),917(Opcode::GetStackPointer),918(Opcode::GetReturnAddress),919(Opcode::X86Blendv),920(Opcode::IcmpImm),921(Opcode::X86Pmulhrsw),922(Opcode::IaddImm),923(Opcode::ImulImm),924(Opcode::UdivImm),925(Opcode::SdivImm),926(Opcode::UremImm),927(Opcode::SremImm),928(Opcode::IrsubImm),929(Opcode::UaddOverflowCin),930(Opcode::SaddOverflowCin),931(Opcode::UaddOverflowTrap),932(Opcode::UsubOverflowBin),933(Opcode::SsubOverflowBin),934(Opcode::BandImm),935(Opcode::BorImm),936(Opcode::BxorImm),937(Opcode::RotlImm),938(Opcode::RotrImm),939(Opcode::IshlImm),940(Opcode::UshrImm),941(Opcode::SshrImm),942(Opcode::ScalarToVector),943(Opcode::X86Pmaddubsw),944(Opcode::X86Cvtt2dq),945(Opcode::Umulhi, &[I128, I128], &[I128]),946(Opcode::Smulhi, &[I128, I128], &[I128]),947// https://github.com/bytecodealliance/wasmtime/issues/6073948(Opcode::Iconcat, &[I32, I32], &[I64]),949(Opcode::Iconcat, &[I16, I16], &[I32]),950(Opcode::Iconcat, &[I8, I8], &[I16]),951// https://github.com/bytecodealliance/wasmtime/issues/6073952(Opcode::Isplit, &[I64], &[I32, I32]),953(Opcode::Isplit, &[I32], &[I16, I16]),954(Opcode::Isplit, &[I16], &[I8, I8]),955(Opcode::Fmin, &[F32X4, F32X4], &[F32X4]),956(Opcode::Fmin, &[F64X2, F64X2], &[F64X2]),957(Opcode::Fmax, &[F32X4, F32X4], &[F32X4]),958(Opcode::Fmax, &[F64X2, F64X2], &[F64X2]),959(Opcode::FcvtToUintSat, &[F32X4], &[I8]),960(Opcode::FcvtToUintSat, &[F64X2], &[I8]),961(Opcode::FcvtToUintSat, &[F32X4], &[I16]),962(Opcode::FcvtToUintSat, &[F64X2], &[I16]),963(Opcode::FcvtToUintSat, &[F32X4], &[I32]),964(Opcode::FcvtToUintSat, &[F64X2], &[I32]),965(Opcode::FcvtToUintSat, &[F32X4], &[I64]),966(Opcode::FcvtToUintSat, &[F64X2], &[I64]),967(Opcode::FcvtToUintSat, &[F32X4], &[I128]),968(Opcode::FcvtToUintSat, &[F64X2], &[I128]),969(Opcode::FcvtToUintSat, &[F32], &[I8X16]),970(Opcode::FcvtToUintSat, &[F64], &[I8X16]),971(Opcode::FcvtToUintSat, &[F32X4], &[I8X16]),972(Opcode::FcvtToUintSat, &[F64X2], &[I8X16]),973(Opcode::FcvtToUintSat, &[F32], &[I16X8]),974(Opcode::FcvtToUintSat, &[F64], &[I16X8]),975(Opcode::FcvtToUintSat, &[F32X4], &[I16X8]),976(Opcode::FcvtToUintSat, &[F64X2], &[I16X8]),977(Opcode::FcvtToUintSat, &[F32], &[I32X4]),978(Opcode::FcvtToUintSat, &[F64], &[I32X4]),979(Opcode::FcvtToUintSat, &[F64X2], &[I32X4]),980(Opcode::FcvtToUintSat, &[F32], &[I64X2]),981(Opcode::FcvtToUintSat, &[F64], &[I64X2]),982(Opcode::FcvtToUintSat, &[F32X4], &[I64X2]),983(Opcode::FcvtToSintSat, &[F32X4], &[I8]),984(Opcode::FcvtToSintSat, &[F64X2], &[I8]),985(Opcode::FcvtToSintSat, &[F32X4], &[I16]),986(Opcode::FcvtToSintSat, &[F64X2], &[I16]),987(Opcode::FcvtToSintSat, &[F32X4], &[I32]),988(Opcode::FcvtToSintSat, &[F64X2], &[I32]),989(Opcode::FcvtToSintSat, &[F32X4], &[I64]),990(Opcode::FcvtToSintSat, &[F64X2], &[I64]),991(Opcode::FcvtToSintSat, &[F32X4], &[I128]),992(Opcode::FcvtToSintSat, &[F64X2], &[I128]),993(Opcode::FcvtToSintSat, &[F32], &[I8X16]),994(Opcode::FcvtToSintSat, &[F64], &[I8X16]),995(Opcode::FcvtToSintSat, &[F32X4], &[I8X16]),996(Opcode::FcvtToSintSat, &[F64X2], &[I8X16]),997(Opcode::FcvtToSintSat, &[F32], &[I16X8]),998(Opcode::FcvtToSintSat, &[F64], &[I16X8]),999(Opcode::FcvtToSintSat, &[F32X4], &[I16X8]),1000(Opcode::FcvtToSintSat, &[F64X2], &[I16X8]),1001(Opcode::FcvtToSintSat, &[F32], &[I32X4]),1002(Opcode::FcvtToSintSat, &[F64], &[I32X4]),1003(Opcode::FcvtToSintSat, &[F64X2], &[I32X4]),1004(Opcode::FcvtToSintSat, &[F32], &[I64X2]),1005(Opcode::FcvtToSintSat, &[F64], &[I64X2]),1006(Opcode::FcvtToSintSat, &[F32X4], &[I64X2]),1007(Opcode::FcvtFromUint, &[I8X16], &[F32]),1008(Opcode::FcvtFromUint, &[I16X8], &[F32]),1009(Opcode::FcvtFromUint, &[I32X4], &[F32]),1010(Opcode::FcvtFromUint, &[I64X2], &[F32]),1011(Opcode::FcvtFromUint, &[I8X16], &[F64]),1012(Opcode::FcvtFromUint, &[I16X8], &[F64]),1013(Opcode::FcvtFromUint, &[I32X4], &[F64]),1014(Opcode::FcvtFromUint, &[I64X2], &[F64]),1015(Opcode::FcvtFromUint, &[I8], &[F32X4]),1016(Opcode::FcvtFromUint, &[I16], &[F32X4]),1017(Opcode::FcvtFromUint, &[I32], &[F32X4]),1018(Opcode::FcvtFromUint, &[I64], &[F32X4]),1019(Opcode::FcvtFromUint, &[I128], &[F32X4]),1020(Opcode::FcvtFromUint, &[I8X16], &[F32X4]),1021(Opcode::FcvtFromUint, &[I16X8], &[F32X4]),1022(Opcode::FcvtFromUint, &[I64X2], &[F32X4]),1023(Opcode::FcvtFromUint, &[I8], &[F64X2]),1024(Opcode::FcvtFromUint, &[I16], &[F64X2]),1025(Opcode::FcvtFromUint, &[I32], &[F64X2]),1026(Opcode::FcvtFromUint, &[I64], &[F64X2]),1027(Opcode::FcvtFromUint, &[I128], &[F64X2]),1028(Opcode::FcvtFromUint, &[I8X16], &[F64X2]),1029(Opcode::FcvtFromUint, &[I16X8], &[F64X2]),1030(Opcode::FcvtFromUint, &[I32X4], &[F64X2]),1031(Opcode::FcvtFromSint, &[I8X16], &[F32]),1032(Opcode::FcvtFromSint, &[I16X8], &[F32]),1033(Opcode::FcvtFromSint, &[I32X4], &[F32]),1034(Opcode::FcvtFromSint, &[I64X2], &[F32]),1035(Opcode::FcvtFromSint, &[I8X16], &[F64]),1036(Opcode::FcvtFromSint, &[I16X8], &[F64]),1037(Opcode::FcvtFromSint, &[I32X4], &[F64]),1038(Opcode::FcvtFromSint, &[I64X2], &[F64]),1039(Opcode::FcvtFromSint, &[I8], &[F32X4]),1040(Opcode::FcvtFromSint, &[I16], &[F32X4]),1041(Opcode::FcvtFromSint, &[I32], &[F32X4]),1042(Opcode::FcvtFromSint, &[I64], &[F32X4]),1043(Opcode::FcvtFromSint, &[I128], &[F32X4]),1044(Opcode::FcvtFromSint, &[I8X16], &[F32X4]),1045(Opcode::FcvtFromSint, &[I16X8], &[F32X4]),1046(Opcode::FcvtFromSint, &[I64X2], &[F32X4]),1047(Opcode::FcvtFromSint, &[I8], &[F64X2]),1048(Opcode::FcvtFromSint, &[I16], &[F64X2]),1049(Opcode::FcvtFromSint, &[I32], &[F64X2]),1050(Opcode::FcvtFromSint, &[I64], &[F64X2]),1051(Opcode::FcvtFromSint, &[I128], &[F64X2]),1052(Opcode::FcvtFromSint, &[I8X16], &[F64X2]),1053(Opcode::FcvtFromSint, &[I16X8], &[F64X2]),1054(Opcode::FcvtFromSint, &[I32X4], &[F64X2]),1055// Only supported on x64 with a feature at this time, so 128-bit1056// atomics are not suitable to fuzz yet.1057(Opcode::AtomicRmw, _, &[I128]),1058(Opcode::AtomicCas, _, &[I128]),1059(Opcode::AtomicLoad, _, &[I128]),1060(Opcode::AtomicStore, &[I128, _], _),1061)1062})1063.filter(|(op, ..)| {1064allowed_opcodes1065.as_ref()1066.map_or(true, |opcodes| opcodes.contains(op))1067})1068.collect()1069});10701071fn inserter_for_format(fmt: InstructionFormat) -> OpcodeInserter {1072match fmt {1073InstructionFormat::AtomicCas => insert_atomic_cas,1074InstructionFormat::AtomicRmw => insert_atomic_rmw,1075InstructionFormat::Binary => insert_opcode,1076InstructionFormat::BinaryImm64 => todo!(),1077InstructionFormat::BinaryImm8 => insert_ins_ext_lane,1078InstructionFormat::Call => insert_call,1079InstructionFormat::CallIndirect => insert_call,1080InstructionFormat::CondTrap => todo!(),1081InstructionFormat::DynamicStackLoad => todo!(),1082InstructionFormat::DynamicStackStore => todo!(),1083InstructionFormat::FloatCompare => insert_cmp,1084InstructionFormat::FuncAddr => todo!(),1085InstructionFormat::IntAddTrap => todo!(),1086InstructionFormat::IntCompare => insert_cmp,1087InstructionFormat::IntCompareImm => todo!(),1088InstructionFormat::Load => insert_load_store,1089InstructionFormat::LoadNoOffset => insert_load_store,1090InstructionFormat::NullAry => insert_opcode,1091InstructionFormat::Shuffle => insert_shuffle,1092InstructionFormat::StackLoad => insert_stack_load,1093InstructionFormat::StackStore => insert_stack_store,1094InstructionFormat::Store => insert_load_store,1095InstructionFormat::StoreNoOffset => insert_load_store,1096InstructionFormat::Ternary => insert_opcode,1097InstructionFormat::TernaryImm8 => insert_ins_ext_lane,1098InstructionFormat::Trap => todo!(),1099InstructionFormat::Unary => insert_opcode,1100InstructionFormat::UnaryConst => insert_const,1101InstructionFormat::UnaryGlobalValue => todo!(),1102InstructionFormat::UnaryIeee16 => insert_const,1103InstructionFormat::UnaryIeee32 => insert_const,1104InstructionFormat::UnaryIeee64 => insert_const,1105InstructionFormat::UnaryImm => insert_const,11061107InstructionFormat::BranchTable1108| InstructionFormat::Brif1109| InstructionFormat::Jump1110| InstructionFormat::MultiAry1111| InstructionFormat::TryCall1112| InstructionFormat::TryCallIndirect => {1113panic!("Control-flow instructions should be handled by 'insert_terminator': {fmt:?}")1114}1115}1116}11171118pub struct FunctionGenerator<'r, 'data>1119where1120'data: 'r,1121{1122u: &'r mut Unstructured<'data>,1123config: &'r Config,1124resources: Resources,1125isa: OwnedTargetIsa,1126name: UserFuncName,1127signature: Signature,1128}11291130#[derive(Debug, Clone)]1131enum BlockTerminator {1132Return,1133Jump(Block),1134Br(Block, Block),1135BrTable(Block, Vec<Block>),1136Switch(Type, Block, HashMap<u128, Block>),1137TailCall(FuncRef),1138TailCallIndirect(FuncRef),1139}11401141#[derive(Debug, Clone)]1142enum BlockTerminatorKind {1143Return,1144Jump,1145Br,1146BrTable,1147Switch,1148TailCall,1149TailCallIndirect,1150}11511152/// Alias Analysis Category1153///1154/// Our alias analysis pass supports 4 categories of accesses to distinguish1155/// different regions. The "Other" region is the general case, and is the default1156/// Although they have highly suggestive names there is no difference between any1157/// of the categories.1158///1159/// We assign each stack slot a category when we first generate them, and then1160/// ensure that all accesses to that stack slot are correctly tagged. We already1161/// ensure that memory accesses never cross stack slots, so there is no risk1162/// of a memory access being tagged with the wrong category.1163#[derive(Debug, PartialEq, Clone, Copy)]1164enum AACategory {1165Other,1166Heap,1167Table,1168VmCtx,1169}11701171impl AACategory {1172pub fn all() -> &'static [Self] {1173&[1174AACategory::Other,1175AACategory::Heap,1176AACategory::Table,1177AACategory::VmCtx,1178]1179}11801181pub fn update_memflags(&self, flags: &mut MemFlags) {1182flags.set_alias_region(match self {1183AACategory::Other => None,1184AACategory::Heap => Some(AliasRegion::Heap),1185AACategory::Table => Some(AliasRegion::Table),1186AACategory::VmCtx => Some(AliasRegion::Vmctx),1187})1188}1189}11901191pub type StackAlignment = StackSize;11921193#[derive(Default)]1194struct Resources {1195vars: HashMap<Type, Vec<Variable>>,1196blocks: Vec<(Block, BlockSignature)>,1197blocks_without_params: Vec<Block>,1198block_terminators: Vec<BlockTerminator>,1199func_refs: Vec<(Signature, SigRef, FuncRef)>,1200/// This field is required to be sorted by stack slot size at all times.1201/// We use this invariant when searching for stack slots with a given size.1202/// See [FunctionGenerator::stack_slot_with_size]1203stack_slots: Vec<(StackSlot, StackSize, StackAlignment, AACategory)>,1204usercalls: Vec<(UserExternalName, Signature)>,1205libcalls: Vec<LibCall>,1206}12071208impl Resources {1209/// Partitions blocks at `block`. Only blocks that can be targeted by branches are considered.1210///1211/// The first slice includes all blocks up to and including `block`.1212/// The second slice includes all remaining blocks.1213fn partition_target_blocks(1214&self,1215block: Block,1216) -> (&[(Block, BlockSignature)], &[(Block, BlockSignature)]) {1217// Blocks are stored in-order and have no gaps, this means that we can simply index them by1218// their number. We also need to exclude the entry block since it isn't a valid target.1219let target_blocks = &self.blocks[1..];1220target_blocks.split_at(block.as_u32() as usize)1221}12221223/// Returns blocks forward of `block`. Only blocks that can be targeted by branches are considered.1224fn forward_blocks(&self, block: Block) -> &[(Block, BlockSignature)] {1225let (_, forward_blocks) = self.partition_target_blocks(block);1226forward_blocks1227}12281229/// Generates a slice of `blocks_without_params` ahead of `block`1230fn forward_blocks_without_params(&self, block: Block) -> &[Block] {1231let partition_point = self.blocks_without_params.partition_point(|b| *b <= block);1232&self.blocks_without_params[partition_point..]1233}12341235/// Generates an iterator of all valid tail call targets. This includes all functions with both1236/// the `tail` calling convention and the same return values as the caller.1237fn tail_call_targets<'a>(1238&'a self,1239caller_sig: &'a Signature,1240) -> impl Iterator<Item = &'a (Signature, SigRef, FuncRef)> {1241self.func_refs.iter().filter(|(sig, _, _)| {1242sig.call_conv == CallConv::Tail && sig.returns == caller_sig.returns1243})1244}1245}12461247impl<'r, 'data> FunctionGenerator<'r, 'data>1248where1249'data: 'r,1250{1251pub fn new(1252u: &'r mut Unstructured<'data>,1253config: &'r Config,1254isa: OwnedTargetIsa,1255name: UserFuncName,1256signature: Signature,1257usercalls: Vec<(UserExternalName, Signature)>,1258libcalls: Vec<LibCall>,1259) -> Self {1260Self {1261u,1262config,1263resources: Resources {1264usercalls,1265libcalls,1266..Resources::default()1267},1268isa,1269name,1270signature,1271}1272}12731274/// Generates a random value for config `param`1275fn param(&mut self, param: &RangeInclusive<usize>) -> Result<usize> {1276Ok(self.u.int_in_range(param.clone())?)1277}12781279fn system_callconv(&mut self) -> CallConv {1280// TODO: This currently only runs on linux, so this is the only choice1281// We should improve this once we generate flags and targets1282CallConv::SystemV1283}12841285/// Finds a stack slot with size of at least n bytes1286fn stack_slot_with_size(1287&mut self,1288n: u32,1289) -> Result<(StackSlot, StackSize, StackAlignment, AACategory)> {1290let first = self1291.resources1292.stack_slots1293.partition_point(|&(_slot, size, _align, _category)| size < n);1294Ok(*self.u.choose(&self.resources.stack_slots[first..])?)1295}12961297/// Generates an address that should allow for a store or a load.1298///1299/// Addresses aren't generated like other values. They are never stored in variables so that1300/// we don't run the risk of returning them from a function, which would make the fuzzer1301/// complain since they are different from the interpreter to the backend.1302///1303/// `min_size`: Controls the amount of space that the address should have.1304///1305/// `aligned`: When passed as true, the resulting address is guaranteed to be aligned1306/// on an 8 byte boundary.1307///1308/// Returns a valid address and the maximum possible offset that still respects `min_size`.1309fn generate_load_store_address(1310&mut self,1311builder: &mut FunctionBuilder,1312min_size: u32,1313aligned: bool,1314) -> Result<(Value, u32, AACategory)> {1315// TODO: Currently our only source of addresses is stack_addr, but we1316// should add global_value, symbol_value eventually1317let (addr, available_size, category) = {1318let (ss, slot_size, _align, category) = self.stack_slot_with_size(min_size)?;13191320// stack_slot_with_size guarantees that slot_size >= min_size1321let max_offset = slot_size - min_size;1322let offset = if aligned {1323self.u.int_in_range(0..=max_offset / min_size)? * min_size1324} else {1325self.u.int_in_range(0..=max_offset)?1326};13271328let base_addr = builder.ins().stack_addr(I64, ss, offset as i32);1329let available_size = slot_size.saturating_sub(offset);1330(base_addr, available_size, category)1331};13321333// TODO: Insert a bunch of amode opcodes here to modify the address!13341335// Now that we have an address and a size, we just choose a random offset to return to the1336// caller. Preserving min_size bytes.1337let max_offset = available_size.saturating_sub(min_size);1338Ok((addr, max_offset, category))1339}13401341// Generates an address and memflags for a load or store.1342fn generate_address_and_memflags(1343&mut self,1344builder: &mut FunctionBuilder,1345min_size: u32,1346is_atomic: bool,1347) -> Result<(Value, MemFlags, Offset32)> {1348// Should we generate an aligned address1349// Some backends have issues with unaligned atomics.1350// AArch64: https://github.com/bytecodealliance/wasmtime/issues/54831351// RISCV: https://github.com/bytecodealliance/wasmtime/issues/58821352let requires_aligned_atomics = matches!(1353self.isa.triple().architecture,1354Architecture::Aarch64(_) | Architecture::Riscv64(_)1355);1356let aligned = if is_atomic && requires_aligned_atomics {1357true1358} else if min_size > 8 {1359// TODO: We currently can't guarantee that a stack_slot will be aligned on a 16 byte1360// boundary. We don't have a way to specify alignment when creating stack slots, and1361// cranelift only guarantees 8 byte alignment between stack slots.1362// See: https://github.com/bytecodealliance/wasmtime/issues/5922#issuecomment-14579266241363false1364} else {1365bool::arbitrary(self.u)?1366};13671368let mut flags = MemFlags::new();1369// Even if we picked an aligned address, we can always generate unaligned memflags1370if aligned && bool::arbitrary(self.u)? {1371flags.set_aligned();1372}1373// If the address is aligned, then we know it won't trap1374if aligned && bool::arbitrary(self.u)? {1375flags.set_notrap();1376}13771378let (address, max_offset, category) =1379self.generate_load_store_address(builder, min_size, aligned)?;13801381// Set the Alias Analysis bits on the memflags1382category.update_memflags(&mut flags);13831384// Pick an offset to pass into the load/store.1385let offset = if aligned {138601387} else {1388self.u.int_in_range(0..=max_offset)? as i321389}1390.into();13911392Ok((address, flags, offset))1393}13941395/// Get a variable of type `ty` from the current function1396fn get_variable_of_type(&mut self, ty: Type) -> Result<Variable> {1397let opts = self.resources.vars.get(&ty).map_or(&[][..], Vec::as_slice);1398let var = self.u.choose(opts)?;1399Ok(*var)1400}14011402/// Generates an instruction(`iconst`/`fconst`/etc...) to introduce a constant value1403fn generate_const(&mut self, builder: &mut FunctionBuilder, ty: Type) -> Result<Value> {1404Ok(match self.u.datavalue(ty)? {1405DataValue::I8(i) => builder.ins().iconst(ty, i as u8 as i64),1406DataValue::I16(i) => builder.ins().iconst(ty, i as u16 as i64),1407DataValue::I32(i) => builder.ins().iconst(ty, i as u32 as i64),1408DataValue::I64(i) => builder.ins().iconst(ty, i),1409DataValue::I128(i) => {1410let hi = builder.ins().iconst(I64, (i >> 64) as i64);1411let lo = builder.ins().iconst(I64, i as i64);1412builder.ins().iconcat(lo, hi)1413}1414DataValue::F16(f) => builder.ins().f16const(f),1415DataValue::F32(f) => builder.ins().f32const(f),1416DataValue::F64(f) => builder.ins().f64const(f),1417DataValue::F128(f) => {1418let handle = builder.func.dfg.constants.insert(f.into());1419builder.ins().f128const(handle)1420}1421DataValue::V128(bytes) => {1422let data = bytes.to_vec().into();1423let handle = builder.func.dfg.constants.insert(data);1424builder.ins().vconst(ty, handle)1425}1426_ => unimplemented!(),1427})1428}14291430/// Chooses a random block which can be targeted by a jump / branch.1431/// This means any block that is not the first block.1432fn generate_target_block(&mut self, source_block: Block) -> Result<Block> {1433// We try to mostly generate forward branches to avoid generating an excessive amount of1434// infinite loops. But they are still important, so give them a small chance of existing.1435let (backwards_blocks, forward_blocks) =1436self.resources.partition_target_blocks(source_block);1437let ratio = self.config.backwards_branch_ratio;1438let block_targets = if !backwards_blocks.is_empty() && self.u.ratio(ratio.0, ratio.1)? {1439backwards_blocks1440} else {1441forward_blocks1442};1443assert!(!block_targets.is_empty());14441445let (block, _) = self.u.choose(block_targets)?.clone();1446Ok(block)1447}14481449fn generate_values_for_block(1450&mut self,1451builder: &mut FunctionBuilder,1452block: Block,1453) -> Result<Vec<BlockArg>> {1454let (_, sig) = self.resources.blocks[block.as_u32() as usize].clone();1455Ok(self1456.generate_values_for_signature(builder, sig.iter().copied())?1457.into_iter()1458.map(|val| BlockArg::Value(val))1459.collect::<Vec<_>>())1460}14611462fn generate_values_for_signature<I: Iterator<Item = Type>>(1463&mut self,1464builder: &mut FunctionBuilder,1465signature: I,1466) -> Result<Vec<Value>> {1467signature1468.map(|ty| {1469let var = self.get_variable_of_type(ty)?;1470let val = builder.use_var(var);1471Ok(val)1472})1473.collect()1474}14751476/// The terminator that we need to insert has already been picked ahead of time1477/// we just need to build the instructions for it1478fn insert_terminator(1479&mut self,1480builder: &mut FunctionBuilder,1481source_block: Block,1482) -> Result<()> {1483let terminator = self.resources.block_terminators[source_block.as_u32() as usize].clone();14841485match terminator {1486BlockTerminator::Return => {1487let types: Vec<Type> = {1488let rets = &builder.func.signature.returns;1489rets.iter().map(|p| p.value_type).collect()1490};1491let vals = self.generate_values_for_signature(builder, types.into_iter())?;14921493builder.ins().return_(&vals[..]);1494}1495BlockTerminator::Jump(target) => {1496let args = self.generate_values_for_block(builder, target)?;1497builder.ins().jump(target, &args[..]);1498}1499BlockTerminator::Br(left, right) => {1500let left_args = self.generate_values_for_block(builder, left)?;1501let right_args = self.generate_values_for_block(builder, right)?;15021503let condbr_types = [I8, I16, I32, I64, I128];1504let _type = *self.u.choose(&condbr_types[..])?;1505let val = builder.use_var(self.get_variable_of_type(_type)?);1506builder1507.ins()1508.brif(val, left, &left_args[..], right, &right_args[..]);1509}1510BlockTerminator::BrTable(default, targets) => {1511// Create jump tables on demand1512let mut jt = Vec::with_capacity(targets.len());1513for block in targets {1514let args = self.generate_values_for_block(builder, block)?;1515jt.push(builder.func.dfg.block_call(block, &args))1516}15171518let args = self.generate_values_for_block(builder, default)?;1519let jt_data = JumpTableData::new(builder.func.dfg.block_call(default, &args), &jt);1520let jt = builder.create_jump_table(jt_data);15211522// br_table only supports I321523let val = builder.use_var(self.get_variable_of_type(I32)?);15241525builder.ins().br_table(val, jt);1526}1527BlockTerminator::Switch(_type, default, entries) => {1528let mut switch = Switch::new();1529for (&entry, &block) in entries.iter() {1530switch.set_entry(entry, block);1531}15321533let switch_val = builder.use_var(self.get_variable_of_type(_type)?);15341535switch.emit(builder, switch_val, default);1536}1537BlockTerminator::TailCall(target) | BlockTerminator::TailCallIndirect(target) => {1538let (sig, sig_ref, func_ref) = self1539.resources1540.func_refs1541.iter()1542.find(|(_, _, f)| *f == target)1543.expect("Failed to find previously selected function")1544.clone();15451546let opcode = match terminator {1547BlockTerminator::TailCall(_) => Opcode::ReturnCall,1548BlockTerminator::TailCallIndirect(_) => Opcode::ReturnCallIndirect,1549_ => unreachable!(),1550};15511552insert_call_to_function(self, builder, opcode, &sig, sig_ref, func_ref)?;1553}1554}15551556Ok(())1557}15581559/// Fills the current block with random instructions1560fn generate_instructions(&mut self, builder: &mut FunctionBuilder) -> Result<()> {1561for _ in 0..self.param(&self.config.instructions_per_block)? {1562let (op, args, rets) = self.u.choose(&OPCODE_SIGNATURES)?;15631564// We filter out instructions that aren't supported by the target at this point instead1565// of building a single vector of valid instructions at the beginning of function1566// generation, to avoid invalidating the corpus when instructions are enabled/disabled.1567if !valid_for_target(&self.isa.triple(), *op, &args, &rets) {1568return Err(arbitrary::Error::IncorrectFormat.into());1569}15701571let inserter = inserter_for_format(op.format());1572inserter(self, builder, *op, &args, &rets)?;1573}15741575Ok(())1576}15771578fn generate_funcrefs(&mut self, builder: &mut FunctionBuilder) -> Result<()> {1579let usercalls: Vec<_> = self1580.resources1581.usercalls1582.iter()1583.map(|(name, signature)| {1584let user_func_ref = builder.func.declare_imported_user_function(name.clone());1585let name = ExternalName::User(user_func_ref);1586let never_colocated = false;1587(name, signature.clone(), never_colocated)1588})1589.collect();15901591let lib_callconv = self.system_callconv();1592let libcalls: Vec<_> = self1593.resources1594.libcalls1595.iter()1596.map(|libcall| {1597let pointer_type = Type::int_with_byte_size(1598self.isa.triple().pointer_width().unwrap().bytes().into(),1599)1600.unwrap();1601let signature = libcall.signature(lib_callconv, pointer_type);1602let name = ExternalName::LibCall(*libcall);1603// libcalls can't be colocated to generated code because we1604// don't know where in the address space the function will go1605// relative to where the libcall is.1606let never_colocated = true;1607(name, signature, never_colocated)1608})1609.collect();16101611for (name, signature, never_colocated) 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,1616colocated: if never_colocated {1617false1618} else {1619self.u.arbitrary()?1620},1621});16221623self.resources1624.func_refs1625.push((signature, sig_ref, func_ref));1626}16271628Ok(())1629}16301631fn generate_stack_slots(&mut self, builder: &mut FunctionBuilder) -> Result<()> {1632for _ in 0..self.param(&self.config.static_stack_slots_per_function)? {1633let bytes = self.param(&self.config.static_stack_slot_size)? as u32;1634let alignment = self.param(&self.config.stack_slot_alignment_log2)? as u8;1635let alignment_bytes = 1 << alignment;16361637let ss_data = StackSlotData::new(StackSlotKind::ExplicitSlot, bytes, alignment);1638let slot = builder.create_sized_stack_slot(ss_data);16391640// Generate one Alias Analysis Category for each slot1641let category = *self.u.choose(AACategory::all())?;16421643self.resources1644.stack_slots1645.push((slot, bytes, alignment_bytes, category));1646}16471648self.resources1649.stack_slots1650.sort_unstable_by_key(|&(_slot, bytes, _align, _category)| bytes);16511652Ok(())1653}16541655/// Zero initializes the stack slot by inserting `stack_store`'s.1656fn initialize_stack_slots(&mut self, builder: &mut FunctionBuilder) -> Result<()> {1657let i8_zero = builder.ins().iconst(I8, 0);1658let i16_zero = builder.ins().iconst(I16, 0);1659let i32_zero = builder.ins().iconst(I32, 0);1660let i64_zero = builder.ins().iconst(I64, 0);1661let i128_zero = builder.ins().uextend(I128, i64_zero);16621663for &(slot, init_size, _align, category) in self.resources.stack_slots.iter() {1664let mut size = init_size;16651666// Insert the largest available store for the remaining size.1667while size != 0 {1668let offset = (init_size - size) as i32;1669let (val, filled) = match size {1670sz if sz / 16 > 0 => (i128_zero, 16),1671sz if sz / 8 > 0 => (i64_zero, 8),1672sz if sz / 4 > 0 => (i32_zero, 4),1673sz if sz / 2 > 0 => (i16_zero, 2),1674_ => (i8_zero, 1),1675};1676let addr = builder.ins().stack_addr(I64, slot, offset);16771678// Each stack slot has an associated category, that means we have to set the1679// correct memflags for it. So we can't use `stack_store` directly.1680let mut flags = MemFlags::new();1681flags.set_notrap();1682category.update_memflags(&mut flags);16831684builder.ins().store(flags, val, addr, 0);16851686size -= filled;1687}1688}1689Ok(())1690}16911692/// Creates a random amount of blocks in this function1693fn generate_blocks(&mut self, builder: &mut FunctionBuilder) -> Result<()> {1694let extra_block_count = self.param(&self.config.blocks_per_function)?;16951696// We must always have at least one block, so we generate the "extra" blocks and add 1 for1697// the entry block.1698let block_count = 1 + extra_block_count;16991700// Blocks need to be sorted in ascending order1701self.resources.blocks = (0..block_count)1702.map(|i| {1703let is_entry = i == 0;1704let block = builder.create_block();17051706// Optionally mark blocks that are not the entry block as cold1707if !is_entry {1708if bool::arbitrary(self.u)? {1709builder.set_cold_block(block);1710}1711}17121713// The first block has to have the function signature, but for the rest of them we generate1714// a random signature;1715if is_entry {1716builder.append_block_params_for_function_params(block);1717Ok((1718block,1719self.signature.params.iter().map(|a| a.value_type).collect(),1720))1721} else {1722let sig = self.generate_block_signature()?;1723sig.iter().for_each(|ty| {1724builder.append_block_param(block, *ty);1725});1726Ok((block, sig))1727}1728})1729.collect::<Result<Vec<_>>>()?;17301731// Valid blocks for jump tables have to have no parameters in the signature, and must also1732// not be the first block.1733self.resources.blocks_without_params = self.resources.blocks[1..]1734.iter()1735.filter(|(_, sig)| sig.len() == 0)1736.map(|(b, _)| *b)1737.collect();17381739// Compute the block CFG1740//1741// cranelift-frontend requires us to never generate unreachable blocks1742// To ensure this property we start by constructing a main "spine" of blocks. So block1 can1743// always jump to block2, and block2 can always jump to block3, etc...1744//1745// That is not a very interesting CFG, so we introduce variations on that, but always1746// ensuring that the property of pointing to the next block is maintained whatever the1747// branching mechanism we use.1748let blocks = self.resources.blocks.clone();1749self.resources.block_terminators = blocks1750.iter()1751.map(|&(block, _)| {1752let next_block = Block::with_number(block.as_u32() + 1).unwrap();1753let forward_blocks = self.resources.forward_blocks(block);1754let paramless_targets = self.resources.forward_blocks_without_params(block);1755let has_paramless_targets = !paramless_targets.is_empty();1756let next_block_is_paramless = paramless_targets.contains(&next_block);17571758let mut valid_terminators = vec![];17591760if forward_blocks.is_empty() {1761// Return is only valid on the last block.1762valid_terminators.push(BlockTerminatorKind::Return);1763} else {1764// If we have more than one block we can allow terminators that target blocks.1765// TODO: We could add some kind of BrReturn here, to explore edges where we1766// exit in the middle of the function1767valid_terminators.extend_from_slice(&[1768BlockTerminatorKind::Jump,1769BlockTerminatorKind::Br,1770BlockTerminatorKind::BrTable,1771]);1772}17731774// As the Switch interface only allows targeting blocks without params we need1775// to ensure that the next block has no params, since that one is guaranteed to be1776// picked in either case.1777if has_paramless_targets && next_block_is_paramless {1778valid_terminators.push(BlockTerminatorKind::Switch);1779}17801781// Tail Calls are a block terminator, so we should insert them as any other block1782// terminator. We should ensure that we can select at least one target before considering1783// them as candidate instructions.1784let has_tail_callees = self1785.resources1786.tail_call_targets(&self.signature)1787.next()1788.is_some();1789let is_tail_caller = self.signature.call_conv == CallConv::Tail;17901791let supports_tail_calls = match self.isa.triple().architecture {1792Architecture::Aarch64(_) | Architecture::Riscv64(_) => true,1793// TODO: x64 currently requires frame pointers for tail calls.1794Architecture::X86_64 => self.isa.flags().preserve_frame_pointers(),1795// TODO: Other platforms do not support tail calls yet.1796_ => false,1797};17981799if is_tail_caller && has_tail_callees && supports_tail_calls {1800valid_terminators.extend([1801BlockTerminatorKind::TailCall,1802BlockTerminatorKind::TailCallIndirect,1803]);1804}18051806let terminator = self.u.choose(&valid_terminators)?;18071808// Choose block targets for the terminators that we picked above1809Ok(match terminator {1810BlockTerminatorKind::Return => BlockTerminator::Return,1811BlockTerminatorKind::Jump => BlockTerminator::Jump(next_block),1812BlockTerminatorKind::Br => {1813BlockTerminator::Br(next_block, self.generate_target_block(block)?)1814}1815// TODO: Allow generating backwards branches here1816BlockTerminatorKind::BrTable => {1817// Make the default the next block, and then we don't have to worry1818// that we can reach it via the targets1819let default = next_block;18201821let target_count = self.param(&self.config.jump_table_entries)?;1822let targets = Result::from_iter(1823(0..target_count).map(|_| self.generate_target_block(block)),1824)?;18251826BlockTerminator::BrTable(default, targets)1827}1828BlockTerminatorKind::Switch => {1829// Make the default the next block, and then we don't have to worry1830// that we can reach it via the entries below1831let default_block = next_block;18321833let _type = *self.u.choose(&[I8, I16, I32, I64, I128][..])?;18341835// Build this into a HashMap since we cannot have duplicate entries.1836let mut entries = HashMap::new();1837for _ in 0..self.param(&self.config.switch_cases)? {1838// The Switch API only allows for entries that are addressable by the index type1839// so we need to limit the range of values that we generate.1840let (ty_min, ty_max) = _type.bounds(false);1841let range_start = self.u.int_in_range(ty_min..=ty_max)?;18421843// We can either insert a contiguous range of blocks or a individual block1844// This is done because the Switch API specializes contiguous ranges.1845let range_size = if bool::arbitrary(self.u)? {184611847} else {1848self.param(&self.config.switch_max_range_size)?1849} as u128;18501851// Build the switch entries1852for i in 0..range_size {1853let index = range_start.wrapping_add(i) % ty_max;1854let block = *self1855.u1856.choose(self.resources.forward_blocks_without_params(block))?;18571858entries.insert(index, block);1859}1860}18611862BlockTerminator::Switch(_type, default_block, entries)1863}1864BlockTerminatorKind::TailCall => {1865let targets = self1866.resources1867.tail_call_targets(&self.signature)1868.collect::<Vec<_>>();1869let (_, _, funcref) = *self.u.choose(&targets[..])?;1870BlockTerminator::TailCall(*funcref)1871}1872BlockTerminatorKind::TailCallIndirect => {1873let targets = self1874.resources1875.tail_call_targets(&self.signature)1876.collect::<Vec<_>>();1877let (_, _, funcref) = *self.u.choose(&targets[..])?;1878BlockTerminator::TailCallIndirect(*funcref)1879}1880})1881})1882.collect::<Result<_>>()?;18831884Ok(())1885}18861887fn generate_block_signature(&mut self) -> Result<BlockSignature> {1888let param_count = self.param(&self.config.block_signature_params)?;18891890let mut params = Vec::with_capacity(param_count);1891for _ in 0..param_count {1892params.push(self.u._type((&*self.isa).supports_simd())?);1893}1894Ok(params)1895}18961897fn build_variable_pool(&mut self, builder: &mut FunctionBuilder) -> Result<()> {1898let block = builder.current_block().unwrap();18991900// Define variables for the function signature1901let mut vars: Vec<_> = builder1902.func1903.signature1904.params1905.iter()1906.map(|param| param.value_type)1907.zip(builder.block_params(block).iter().copied())1908.collect();19091910// Create a pool of vars that are going to be used in this function1911for _ in 0..self.param(&self.config.vars_per_function)? {1912let ty = self.u._type((&*self.isa).supports_simd())?;1913let value = self.generate_const(builder, ty)?;1914vars.push((ty, value));1915}19161917for (ty, value) in vars.into_iter() {1918let var = builder.declare_var(ty);1919builder.def_var(var, value);19201921// Randomly declare variables as needing a stack map.1922// We limit these to only types that have fewer than 16 bytes1923// since the stack map mechanism does not support larger types.1924if ty.bytes() <= 16 && self.u.arbitrary()? {1925builder.declare_var_needs_stack_map(var);1926}19271928self.resources1929.vars1930.entry(ty)1931.or_insert_with(Vec::new)1932.push(var);1933}19341935Ok(())1936}19371938/// We generate a function in multiple stages:1939///1940/// * First we generate a random number of empty blocks1941/// * Then we generate a random pool of variables to be used throughout the function1942/// * We then visit each block and generate random instructions1943///1944/// Because we generate all blocks and variables up front we already know everything that1945/// we need when generating instructions (i.e. jump targets / variables)1946pub fn generate(mut self) -> Result<Function> {1947let mut fn_builder_ctx = FunctionBuilderContext::new();1948let mut func = Function::with_name_signature(self.name.clone(), self.signature.clone());19491950let mut builder = FunctionBuilder::new(&mut func, &mut fn_builder_ctx);19511952// Build the function references before generating the block CFG since we store1953// function references in the CFG.1954self.generate_funcrefs(&mut builder)?;1955self.generate_blocks(&mut builder)?;19561957// Function preamble1958self.generate_stack_slots(&mut builder)?;19591960// Main instruction generation loop1961for (block, block_sig) in self.resources.blocks.clone().into_iter() {1962let is_block0 = block.as_u32() == 0;1963builder.switch_to_block(block);19641965if is_block0 {1966// The first block is special because we must create variables both for the1967// block signature and for the variable pool. Additionally, we must also define1968// initial values for all variables that are not the function signature.1969self.build_variable_pool(&mut builder)?;19701971// Stack slots have random bytes at the beginning of the function1972// initialize them to a constant value so that execution stays predictable.1973self.initialize_stack_slots(&mut builder)?;1974} else {1975// Define variables for the block params1976for (i, ty) in block_sig.iter().enumerate() {1977let var = self.get_variable_of_type(*ty)?;1978let block_param = builder.block_params(block)[i];1979builder.def_var(var, block_param);1980}1981}19821983// Generate block instructions1984self.generate_instructions(&mut builder)?;19851986// Insert a terminator to safely exit the block1987self.insert_terminator(&mut builder, block)?;1988}19891990builder.seal_all_blocks();1991builder.finalize();19921993Ok(func)1994}1995}199619971998