Validate the OPCODE_SIGNATURES table (#6047)

* Add a program for checking the function_generator opcode signatures

* Rework as a test in function_generator instead

* Fix some invalid opcode signatures in the function generator

* Fix bnot exclusions
This commit is contained in:
Trevor Elliott
2023-03-18 11:27:35 -07:00
committed by GitHub
parent 02089b9b3c
commit d72010b749

View File

@@ -430,11 +430,11 @@ fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) -
&[I128, I128] &[I128, I128]
), ),
// https://github.com/bytecodealliance/wasmtime/issues/4870 // https://github.com/bytecodealliance/wasmtime/issues/4870
(Opcode::Bnot, &[F32 | F64]),
( (
Opcode::Band Opcode::Band
| Opcode::Bor | Opcode::Bor
| Opcode::Bxor | Opcode::Bxor
| Opcode::Bnot
| Opcode::BandNot | Opcode::BandNot
| Opcode::BorNot | Opcode::BorNot
| Opcode::BxorNot, | 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 // https://github.com/bytecodealliance/wasmtime/issues/4900
(Opcode::FcvtFromSint, &[I128], &[F32 | F64]), (Opcode::FcvtFromSint, &[I128], &[F32 | F64]),
(Opcode::FcvtFromSint, &[I64X2], &[F64X2]), (Opcode::FcvtFromSint, &[I64X2], &[F64X2]),
(Opcode::Bmask, &[I8X16 | I16X8 | I32X4 | I64X2]),
( (
Opcode::Umulhi | Opcode::Smulhi, Opcode::Umulhi | Opcode::Smulhi,
&([I8X16, I8X16] | [I16X8, I16X8] | [I32X4, I32X4] | [I64X2, I64X2]) &([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] &[I128, I128]
), ),
// https://github.com/bytecodealliance/wasmtime/issues/4870 // https://github.com/bytecodealliance/wasmtime/issues/4870
(Opcode::Bnot, &[F32 | F64]),
( (
Opcode::Band Opcode::Band
| Opcode::Bor | Opcode::Bor
| Opcode::Bxor | Opcode::Bxor
| Opcode::Bnot
| Opcode::BandNot | Opcode::BandNot
| Opcode::BorNot | Opcode::BorNot
| Opcode::BxorNot, | Opcode::BxorNot,
@@ -568,7 +567,6 @@ fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) -
&[I128], &[I128],
&[F32 | F64] &[F32 | F64]
), ),
(Opcode::Bmask, &[I8X16 | I16X8 | I32X4 | I64X2]),
( (
Opcode::Umulhi | Opcode::Smulhi, Opcode::Umulhi | Opcode::Smulhi,
&([I8X16, I8X16] | [I16X8, I16X8] | [I32X4, I32X4] | [I64X2, I64X2]) &([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, Opcode::Udiv | Opcode::Sdiv | Opcode::Urem | Opcode::Srem,
&[I128, I128] &[I128, I128]
), ),
(Opcode::Bnot, &[F32 | F64]),
( (
Opcode::Band Opcode::Band
| Opcode::Bor | Opcode::Bor
| Opcode::Bxor | Opcode::Bxor
| Opcode::Bnot
| Opcode::BandNot | Opcode::BandNot
| Opcode::BorNot | Opcode::BorNot
| Opcode::BxorNot, | Opcode::BxorNot,
@@ -610,7 +608,6 @@ fn valid_for_target(triple: &Triple, op: Opcode, args: &[Type], rets: &[Type]) -
&[I128], &[I128],
&[F32 | F64] &[F32 | F64]
), ),
(Opcode::Bmask, &[I8X16 | I16X8 | I32X4 | I64X2]),
(Opcode::SsubSat | Opcode::SaddSat, &[I64X2, I64X2]), (Opcode::SsubSat | Opcode::SaddSat, &[I64X2, I64X2]),
) )
} }
@@ -673,7 +670,7 @@ type OpcodeSignature = (
// TODO: Derive this from the `cranelift-meta` generator. // TODO: Derive this from the `cranelift-meta` generator.
#[rustfmt::skip] #[rustfmt::skip]
const OPCODE_SIGNATURES: &[OpcodeSignature] = &[ pub const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
(Opcode::Nop, &[], &[]), (Opcode::Nop, &[], &[]),
// Iadd // Iadd
(Opcode::Iadd, &[I8, I8], &[I8]), (Opcode::Iadd, &[I8, I8], &[I8]),
@@ -774,21 +771,21 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
(Opcode::Srem, &[I64, I64], &[I64]), (Opcode::Srem, &[I64, I64], &[I64]),
(Opcode::Srem, &[I128, I128], &[I128]), (Opcode::Srem, &[I128, I128], &[I128]),
// Ineg // Ineg
(Opcode::Ineg, &[I8, I8], &[I8]), (Opcode::Ineg, &[I8], &[I8]),
(Opcode::Ineg, &[I16, I16], &[I16]), (Opcode::Ineg, &[I16], &[I16]),
(Opcode::Ineg, &[I32, I32], &[I32]), (Opcode::Ineg, &[I32], &[I32]),
(Opcode::Ineg, &[I64, I64], &[I64]), (Opcode::Ineg, &[I64], &[I64]),
(Opcode::Ineg, &[I128, I128], &[I128]), (Opcode::Ineg, &[I128], &[I128]),
// Iabs // Iabs
(Opcode::Iabs, &[I8], &[I8]), (Opcode::Iabs, &[I8], &[I8]),
(Opcode::Iabs, &[I16], &[I16]), (Opcode::Iabs, &[I16], &[I16]),
(Opcode::Iabs, &[I32], &[I32]), (Opcode::Iabs, &[I32], &[I32]),
(Opcode::Iabs, &[I64], &[I64]), (Opcode::Iabs, &[I64], &[I64]),
(Opcode::Iabs, &[I128], &[I128]), (Opcode::Iabs, &[I128], &[I128]),
(Opcode::Iabs, &[I8X16, I8X16], &[I8X16]), (Opcode::Iabs, &[I8X16], &[I8X16]),
(Opcode::Iabs, &[I16X8, I16X8], &[I16X8]), (Opcode::Iabs, &[I16X8], &[I16X8]),
(Opcode::Iabs, &[I32X4, I32X4], &[I32X4]), (Opcode::Iabs, &[I32X4], &[I32X4]),
(Opcode::Iabs, &[I64X2, I64X2], &[I64X2]), (Opcode::Iabs, &[I64X2], &[I64X2]),
// Smin // Smin
(Opcode::Smin, &[I8, I8], &[I8]), (Opcode::Smin, &[I8, I8], &[I8]),
(Opcode::Smin, &[I16, I16], &[I16]), (Opcode::Smin, &[I16, I16], &[I16]),
@@ -1154,10 +1151,6 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
(Opcode::Bmask, &[I32], &[I128]), (Opcode::Bmask, &[I32], &[I128]),
(Opcode::Bmask, &[I64], &[I128]), (Opcode::Bmask, &[I64], &[I128]),
(Opcode::Bmask, &[I128], &[I128]), (Opcode::Bmask, &[I128], &[I128]),
(Opcode::Bmask, &[I8X16], &[I8X16]),
(Opcode::Bmask, &[I16X8], &[I16X8]),
(Opcode::Bmask, &[I32X4], &[I32X4]),
(Opcode::Bmask, &[I64X2], &[I64X2]),
// Bswap // Bswap
(Opcode::Bswap, &[I16], &[I16]), (Opcode::Bswap, &[I16], &[I16]),
(Opcode::Bswap, &[I32], &[I32]), (Opcode::Bswap, &[I32], &[I32]),
@@ -1266,7 +1259,7 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
(Opcode::Fma, &[F32, F32, F32], &[F32]), (Opcode::Fma, &[F32, F32, F32], &[F32]),
(Opcode::Fma, &[F64, F64, F64], &[F64]), (Opcode::Fma, &[F64, F64, F64], &[F64]),
(Opcode::Fma, &[F32X4, F32X4, F32X4], &[F32X4]), (Opcode::Fma, &[F32X4, F32X4, F32X4], &[F32X4]),
(Opcode::Fma, &[F64X2, F64X2, F64X4], &[F64X2]), (Opcode::Fma, &[F64X2, F64X2, F64X2], &[F64X2]),
// Fabs // Fabs
(Opcode::Fabs, &[F32], &[F32]), (Opcode::Fabs, &[F32], &[F32]),
(Opcode::Fabs, &[F64], &[F64]), (Opcode::Fabs, &[F64], &[F64]),
@@ -1511,7 +1504,7 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
(Opcode::Bitcast, &[F64], &[I64]), (Opcode::Bitcast, &[F64], &[I64]),
(Opcode::Bitcast, &[I64], &[F64]), (Opcode::Bitcast, &[I64], &[F64]),
// Shuffle // Shuffle
(Opcode::Shuffle, &[I8X16, I8X16, I8X16], &[I8X16]), (Opcode::Shuffle, &[I8X16, I8X16], &[I8X16]),
// Swizzle // Swizzle
(Opcode::Swizzle, &[I8X16, I8X16], &[I8X16]), (Opcode::Swizzle, &[I8X16, I8X16], &[I8X16]),
// Splat // Splat
@@ -1553,10 +1546,10 @@ const OPCODE_SIGNATURES: &[OpcodeSignature] = &[
(Opcode::VhighBits, &[I32X4], &[I8]), (Opcode::VhighBits, &[I32X4], &[I8]),
(Opcode::VhighBits, &[I64X2], &[I8]), (Opcode::VhighBits, &[I64X2], &[I8]),
// VanyTrue // VanyTrue
(Opcode::VanyTrue, &[I8X16, I8X16, I8X16], &[I8]), (Opcode::VanyTrue, &[I8X16], &[I8]),
(Opcode::VanyTrue, &[I16X8, I16X8, I16X8], &[I8]), (Opcode::VanyTrue, &[I16X8], &[I8]),
(Opcode::VanyTrue, &[I32X4, I32X4, I32X4], &[I8]), (Opcode::VanyTrue, &[I32X4], &[I8]),
(Opcode::VanyTrue, &[I64X2, I64X2, I64X2], &[I8]), (Opcode::VanyTrue, &[I64X2], &[I8]),
// SwidenLow // SwidenLow
(Opcode::SwidenLow, &[I8X16], &[I16X8]), (Opcode::SwidenLow, &[I8X16], &[I16X8]),
(Opcode::SwidenLow, &[I16X8], &[I32X4]), (Opcode::SwidenLow, &[I16X8], &[I32X4]),
@@ -2347,3 +2340,191 @@ where
Ok(func) 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<Type>, Vec<Type>)> {
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(&current);
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<Opcode, Vec<Inst<'a>>>
where
T: AsRef<[Type]>,
{
let mut insts = HashMap::<Opcode, Vec<Inst<'a>>>::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"
);
}
}