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:
@@ -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(¤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<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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user