diff --git a/cranelift/fuzzgen/src/function_generator.rs b/cranelift/fuzzgen/src/function_generator.rs index d51f008f92..eb04256884 100644 --- a/cranelift/fuzzgen/src/function_generator.rs +++ b/cranelift/fuzzgen/src/function_generator.rs @@ -430,11 +430,11 @@ fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) - &[I128, I128] ), // https://github.com/bytecodealliance/wasmtime/issues/4870 + (Opcode::Bnot, &[F32 | F64]), ( Opcode::Band | Opcode::Bor | Opcode::Bxor - | Opcode::Bnot | Opcode::BandNot | Opcode::BorNot | Opcode::BxorNot, @@ -486,7 +486,6 @@ fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) - // https://github.com/bytecodealliance/wasmtime/issues/4900 (Opcode::FcvtFromSint, &[I128], &[F32 | F64]), (Opcode::FcvtFromSint, &[I64X2], &[F64X2]), - (Opcode::Bmask, &[I8X16 | I16X8 | I32X4 | I64X2]), ( Opcode::Umulhi | Opcode::Smulhi, &([I8X16, I8X16] | [I16X8, I16X8] | [I32X4, I32X4] | [I64X2, I64X2]) @@ -542,11 +541,11 @@ fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) - &[I128, I128] ), // https://github.com/bytecodealliance/wasmtime/issues/4870 + (Opcode::Bnot, &[F32 | F64]), ( Opcode::Band | Opcode::Bor | Opcode::Bxor - | Opcode::Bnot | Opcode::BandNot | Opcode::BorNot | Opcode::BxorNot, @@ -568,7 +567,6 @@ fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) - &[I128], &[F32 | F64] ), - (Opcode::Bmask, &[I8X16 | I16X8 | I32X4 | I64X2]), ( Opcode::Umulhi | Opcode::Smulhi, &([I8X16, I8X16] | [I16X8, I16X8] | [I32X4, I32X4] | [I64X2, I64X2]) @@ -587,11 +585,11 @@ fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) - Opcode::Udiv | Opcode::Sdiv | Opcode::Urem | Opcode::Srem, &[I128, I128] ), + (Opcode::Bnot, &[F32 | F64]), ( Opcode::Band | Opcode::Bor | Opcode::Bxor - | Opcode::Bnot | Opcode::BandNot | Opcode::BorNot | Opcode::BxorNot, @@ -610,7 +608,6 @@ fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) - &[I128], &[F32 | F64] ), - (Opcode::Bmask, &[I8X16 | I16X8 | I32X4 | I64X2]), (Opcode::SsubSat | Opcode::SaddSat, &[I64X2, I64X2]), ) } @@ -673,7 +670,7 @@ type OpcodeSignature = ( // TODO: Derive this from the `cranelift-meta` generator. #[rustfmt::skip] -const OPCODE_SIGNATURES: &[OpcodeSignature] = &[ +pub const OPCODE_SIGNATURES: &[OpcodeSignature] = &[ (Opcode::Nop, &[], &[]), // Iadd (Opcode::Iadd, &[I8, I8], &[I8]), @@ -774,21 +771,21 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[ (Opcode::Srem, &[I64, I64], &[I64]), (Opcode::Srem, &[I128, I128], &[I128]), // Ineg - (Opcode::Ineg, &[I8, I8], &[I8]), - (Opcode::Ineg, &[I16, I16], &[I16]), - (Opcode::Ineg, &[I32, I32], &[I32]), - (Opcode::Ineg, &[I64, I64], &[I64]), - (Opcode::Ineg, &[I128, I128], &[I128]), + (Opcode::Ineg, &[I8], &[I8]), + (Opcode::Ineg, &[I16], &[I16]), + (Opcode::Ineg, &[I32], &[I32]), + (Opcode::Ineg, &[I64], &[I64]), + (Opcode::Ineg, &[I128], &[I128]), // Iabs (Opcode::Iabs, &[I8], &[I8]), (Opcode::Iabs, &[I16], &[I16]), (Opcode::Iabs, &[I32], &[I32]), (Opcode::Iabs, &[I64], &[I64]), (Opcode::Iabs, &[I128], &[I128]), - (Opcode::Iabs, &[I8X16, I8X16], &[I8X16]), - (Opcode::Iabs, &[I16X8, I16X8], &[I16X8]), - (Opcode::Iabs, &[I32X4, I32X4], &[I32X4]), - (Opcode::Iabs, &[I64X2, I64X2], &[I64X2]), + (Opcode::Iabs, &[I8X16], &[I8X16]), + (Opcode::Iabs, &[I16X8], &[I16X8]), + (Opcode::Iabs, &[I32X4], &[I32X4]), + (Opcode::Iabs, &[I64X2], &[I64X2]), // Smin (Opcode::Smin, &[I8, I8], &[I8]), (Opcode::Smin, &[I16, I16], &[I16]), @@ -1154,10 +1151,6 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[ (Opcode::Bmask, &[I32], &[I128]), (Opcode::Bmask, &[I64], &[I128]), (Opcode::Bmask, &[I128], &[I128]), - (Opcode::Bmask, &[I8X16], &[I8X16]), - (Opcode::Bmask, &[I16X8], &[I16X8]), - (Opcode::Bmask, &[I32X4], &[I32X4]), - (Opcode::Bmask, &[I64X2], &[I64X2]), // Bswap (Opcode::Bswap, &[I16], &[I16]), (Opcode::Bswap, &[I32], &[I32]), @@ -1266,7 +1259,7 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[ (Opcode::Fma, &[F32, F32, F32], &[F32]), (Opcode::Fma, &[F64, F64, F64], &[F64]), (Opcode::Fma, &[F32X4, F32X4, F32X4], &[F32X4]), - (Opcode::Fma, &[F64X2, F64X2, F64X4], &[F64X2]), + (Opcode::Fma, &[F64X2, F64X2, F64X2], &[F64X2]), // Fabs (Opcode::Fabs, &[F32], &[F32]), (Opcode::Fabs, &[F64], &[F64]), @@ -1511,7 +1504,7 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[ (Opcode::Bitcast, &[F64], &[I64]), (Opcode::Bitcast, &[I64], &[F64]), // Shuffle - (Opcode::Shuffle, &[I8X16, I8X16, I8X16], &[I8X16]), + (Opcode::Shuffle, &[I8X16, I8X16], &[I8X16]), // Swizzle (Opcode::Swizzle, &[I8X16, I8X16], &[I8X16]), // Splat @@ -1553,10 +1546,10 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[ (Opcode::VhighBits, &[I32X4], &[I8]), (Opcode::VhighBits, &[I64X2], &[I8]), // VanyTrue - (Opcode::VanyTrue, &[I8X16, I8X16, I8X16], &[I8]), - (Opcode::VanyTrue, &[I16X8, I16X8, I16X8], &[I8]), - (Opcode::VanyTrue, &[I32X4, I32X4, I32X4], &[I8]), - (Opcode::VanyTrue, &[I64X2, I64X2, I64X2], &[I8]), + (Opcode::VanyTrue, &[I8X16], &[I8]), + (Opcode::VanyTrue, &[I16X8], &[I8]), + (Opcode::VanyTrue, &[I32X4], &[I8]), + (Opcode::VanyTrue, &[I64X2], &[I8]), // SwidenLow (Opcode::SwidenLow, &[I8X16], &[I16X8]), (Opcode::SwidenLow, &[I16X8], &[I32X4]), @@ -2347,3 +2340,191 @@ where Ok(func) } } + +#[cfg(test)] +mod tests { + + use super::OPCODE_SIGNATURES; + use cranelift::codegen::ir::{instructions::ResolvedConstraint, types::*, Opcode, Type}; + use std::collections::HashMap; + + /// The list of opcodes that we exclude from the analysis. This should only be instructions + /// that affect control flow, as everything else should be handled by the general machinery in + /// function_generator. + /// + /// NOTE: This list should not grow, and ultimately should just be the set of instructions that + /// need special handling outside of `generate_instructions`. + const SKIPPED_OPCODES: &[Opcode] = &[ + // These opcodes have special handling in cranelift-fuzzgen + Opcode::Call, + Opcode::Return, + Opcode::Jump, + Opcode::Brif, + Opcode::BrTable, + // TODO: ExtractVector produces dynamic vectors, and those cause a panic in + // `constraint.result_type`. + Opcode::ExtractVector, + // These opcodes are known to have invalid signatures, but the specific types chosen are + // not used by the insertion strategy for that format. They can be removed when their + // entries in OPCODE_SIGNATURES have been fixed + Opcode::Load, + Opcode::Store, + Opcode::AtomicLoad, + Opcode::AtomicStore, + Opcode::AtomicCas, + Opcode::AtomicRmw, + Opcode::Sload8, + Opcode::Sload16, + Opcode::Sload32, + Opcode::Uload8, + Opcode::Uload16, + Opcode::Uload32, + Opcode::Istore8, + Opcode::Istore16, + Opcode::Istore32, + ]; + + /// This is the set of types that we know how to fuzz in cranelift. It's not specialized by + /// targets, as we expect any target-specific support for things like SIMD to be expressed in + /// the `function_generator::valid_for_target` predicate instead. + const TYPES: &[Type] = &[ + I8, I16, I32, I64, I128, // Scalar Integers + F32, F64, // Scalar Floats + I8X16, I16X8, I32X4, I64X2, // SIMD Integers + F32X4, F64X2, // SIMD Floats + ]; + + /// Generate all instantiations of all opcodes in cranelift, minus those named in + /// [SKIPPED_OPCODES]. + fn instruction_instantiations<'a>() -> Vec<(Opcode, Vec, Vec)> { + let mut insts = vec![]; + + for op in Opcode::all() { + if SKIPPED_OPCODES.contains(op) { + continue; + } + + let constraints = op.constraints(); + + let ctrl_types = if let Some(ctrls) = constraints.ctrl_typeset() { + Vec::from_iter(TYPES.iter().copied().filter(|ty| ctrls.contains(*ty))) + } else { + vec![INVALID] + }; + + for ctrl_type in ctrl_types { + let rets = Vec::from_iter( + (0..constraints.num_fixed_results()) + .map(|i| constraints.result_type(i, ctrl_type)), + ); + + // Cols is a vector whose length will match `num_fixed_value_arguments`, and whose + // elements will be vectors of types that are valid for that fixed argument + // position. + let mut cols = vec![]; + + for i in 0..constraints.num_fixed_value_arguments() { + match constraints.value_argument_constraint(i, ctrl_type) { + ResolvedConstraint::Bound(ty) => cols.push(Vec::from([ty])), + ResolvedConstraint::Free(tys) => cols.push(Vec::from_iter( + TYPES.iter().copied().filter(|ty| tys.contains(*ty)), + )), + } + } + + // Generate the cartesian product of cols to produce a vector of argument lists, + // argss. The argss vector is seeded with the empty argument list, so there's an + // initial value to be extended in the loop below. + let mut argss = vec![vec![]]; + let mut cols = cols.as_slice(); + while let Some((col, rest)) = cols.split_last() { + cols = rest; + + let mut next = vec![]; + for current in argss.iter() { + // Extend the front of each argument candidate with every type in `col`. + for ty in col { + let mut args = vec![*ty]; + args.extend_from_slice(¤t); + next.push(args); + } + } + + let _ = std::mem::replace(&mut argss, next); + } + + for args in argss { + insts.push((*op, args, rets.clone())); + } + } + } + + insts + } + + #[derive(Eq, PartialEq, Debug)] + struct Inst<'a> { + args: &'a [Type], + rets: &'a [Type], + } + + fn build_sig_map<'a, T>(sigs: &'a [(Opcode, T, T)]) -> HashMap>> + where + T: AsRef<[Type]>, + { + let mut insts = HashMap::>>::default(); + + for (op, args, rets) in sigs { + insts.entry(*op).or_default().push(Inst { + args: args.as_ref(), + rets: rets.as_ref(), + }); + } + + insts + } + + #[test] + fn validate_opcode_signatures() { + // We could instead use the instruction constraints to validate the entries in + // OPCODE_SIGNATURES directly, but generating all instantiations gives us the ability to + // also generate the complement of signatures that we don't currently handle in the future. + let all_ops = instruction_instantiations(); + let everything = build_sig_map(&all_ops); + let fuzzed = build_sig_map(OPCODE_SIGNATURES); + + let mut found_errs = false; + let mut unknown = vec![]; + for (op, insts) in fuzzed.iter() { + if let Some(known_insts) = everything.get(op) { + let invalid = + Vec::from_iter(insts.iter().filter(|inst| !known_insts.contains(inst))); + if !invalid.is_empty() { + found_errs = true; + println!("# Invalid instantiations for Opcode::{:?}", op); + for inst in invalid { + println!("- args: `{:?}`, rets: `{:?}`", inst.args, inst.rets); + } + } + } else { + if !SKIPPED_OPCODES.contains(op) { + unknown.push(*op); + } + } + } + + if !unknown.is_empty() { + found_errs = true; + println!(); + println!("# Instructions without known instantiations"); + for op in unknown { + println!("- Opcode::{:?}", op); + } + } + + assert!( + !found_errs, + "Discovered inconsistent entries in OPCODE_SIGNATURES" + ); + } +}