Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion crates/intrinsic-test/src/arm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,19 @@ impl SupportedArchitectureTest for ArmArchitectureTest {

let intrinsics = intrinsics
.into_iter()
// Not sure how we would compare intrinsic that returns void.
// Skip intrinsics that don't return a value.
.filter(|i| i.results.kind() != TypeKind::Void)
// Skip bfloat intrinsics - not currently supported
.filter(|i| i.results.kind() != TypeKind::BFloat)
.filter(|i| !i.arguments.iter().any(|a| a.ty.kind() == TypeKind::BFloat))
// Skip pointers for now, we would probably need to look at the return
// type to work out how many elements we need to point to.
.filter(|i| !i.arguments.iter().any(|a| a.is_ptr()))
// Skip intrinsics with 128-bit elements (e.g. `p128`)
.filter(|i| !i.arguments.iter().any(|a| a.ty.inner_size() == 128))
// Skip intrinsics from `--skip`
.filter(|i| !cli_options.skip.contains(&i.name))
// Skip A64-specific intrinsics on A32
.filter(|i| !(a32 && i.arch_tags == vec!["A64".to_string()]))
.take(sample_size)
.collect::<Vec<_>>();
Expand Down
69 changes: 62 additions & 7 deletions crates/intrinsic-test/src/common/argument.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ where
self.constraint.is_some()
}

/// The name (e.g. "A_VALS" or "a_vals") for the array of possible test inputs.
/// Returns a string with the name of the static variable containing test values for intrinsic
/// arguments of this type.
pub(crate) fn rust_vals_array_name(&self) -> impl std::fmt::Display {
let loads = crate::common::gen_rust::PASSES;
format!(
Expand All @@ -62,12 +63,15 @@ where
)
}

/// Should this argument be passed by reference in C wrapper function declarations?
///
/// SIMD types and `f16` are currently passed by reference.
pub(crate) fn pass_by_ref(&self) -> bool {
// pass SIMD types and `f16` by reference
self.is_simd() || (self.ty.kind() == TypeKind::Float && self.ty.inner_size() == 16)
}
}

/// Arguments of an intrinsic - including parameters that end up being const generics.
#[derive(Debug, PartialEq, Clone)]
pub struct ArgumentList<T: IntrinsicTypeDefinition> {
pub args: Vec<Argument<T>>,
Expand All @@ -77,6 +81,11 @@ impl<T> ArgumentList<T>
where
T: IntrinsicTypeDefinition,
{
/// Returns a string with the arguments in `self` as a parameter list for a wrapper fn
/// definition in C (e.g. `$ty1 $arg1, $ty2 $arg2`).
///
/// Skips arguments with constraints - which correspond to arguments that must take immediates -
/// as a different C definition will be generated for each value of these being tested.
pub fn as_non_imm_arglist_c(&self) -> String {
self.iter()
.filter(|arg| !arg.has_constraint())
Expand All @@ -90,6 +99,11 @@ where
.to_string()
}

/// Returns a string with the arguments in `self` as a parameter list for a Rust declaration of
/// a C wrapper fn (e.g. `$arg1: $ty1, $arg2: $ty2`).
///
/// Skips arguments with constraints - which correspond to arguments that must take immediates -
/// as a different C definition will be generated for each value of these being tested.
pub fn as_non_imm_arglist_rust(&self) -> String {
self.iter()
.filter(|arg| !arg.has_constraint())
Expand All @@ -107,6 +121,8 @@ where
.to_string()
}

/// Returns a string with the arguments in `self` being passed to an intrinsic call in C
/// (e.g. `$arg1, 2 /* imm_args[0] */, $arg3` where `$arg2` has a constraint).
pub fn as_call_params_c(&self, imm_args: &[i64]) -> String {
let mut imm_args = imm_args.iter();
self.iter()
Expand All @@ -123,15 +139,19 @@ where
.to_string()
}

/// Converts the argument list into the call parameters for a Rust function.
/// e.g. this would generate something like `a, b, c`
/// Returns a string with the arguments in `self` being passed to an intrinsic call in Rust.
/// (e.g. `$arg1, $arg3` where `$arg2` has a constraint and so corresponds to a const generic
/// parameter).
pub fn as_call_param_rust(&self) -> String {
self.iter()
.filter(|a| !a.has_constraint())
.map(|arg| arg.generate_name())
.join(", ")
}

/// Returns a string with the arguments in `self` being passed to the declaration of a C wrapper
/// fn from Rust (e.g. `$arg1, $arg3` (where `$arg2` has a constraint and so corresponds to a
/// const generic parameter).
pub fn as_c_call_param_rust(&self) -> String {
self.iter()
.filter(|a| !a.has_constraint())
Expand All @@ -145,6 +165,26 @@ where
.join("")
}

/// Returns a string defining a static variable with test values used for all intrinsics with
/// arguments of `arg`'s type.
///
/// e.g.
/// ```rust,ignore
/// static U8_20: [u8; 20] = [
/// 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xf0,
/// 0x80, 0x3b, 0xff,
/// ];
/// ```
///
/// `num_lanes * num_vectors + loads - 1` elements are present in the array, which is sufficient
/// for a `loads` number of `num_lanes * num_vectors` windows into the array to be loaded:
///
/// ```text
/// [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xf0, 0x80, 0x3b, 0xff]
/// ^^^^^^^^^^^^^^^^^^^ first window of `num_lanes * num_vectors` elements (e.g. four elements)
/// ^^^^^^^^^^^^^^^^^^ second window
/// `loads`th window ^^^^^^^^^^^^^^^^^^^^^^
/// ```
pub fn gen_arg_rust(
arg: &Argument<T>,
w: &mut impl std::io::Write,
Expand All @@ -160,9 +200,23 @@ where
)
}

/// Creates a line for each argument that initializes the argument from array `[ARG]_VALS` at
/// an offset `i` using a load intrinsic, in Rust.
/// e.g `let a = vld1_u8(A_VALS.as_ptr().offset(i));`
/// Returns a string defining a local variable for each argument and loading a value into each
/// using a load intrinsic.
///
/// e.g.
/// ```rust,ignore
/// let a = vld1_u8(I16_23.as_ptr().offset((i + 0 /* idx */) % 20 /* PASSES */));
/// ````
///
/// The generator will have already generated arrays of appropriate length with values that can
/// be used for testing (see the `gen_args_rust` function).
///
/// Each load is assumed to have a variable `i` in scope which comes from a loop which repeats
/// the testing of the intrinsic for different values - each subsequent `i` shifts the window
/// of values being loaded along the pre-prepared array.
///
/// Each subsequent argument's first window is started one element further into the array
/// then the previous.
pub fn load_values_rust(&self) -> String {
self.iter()
.filter(|&arg| !arg.has_constraint())
Expand All @@ -186,6 +240,7 @@ where
.collect()
}

/// Returns an iterator over the contained arguments
pub fn iter(&self) -> std::slice::Iter<'_, Argument<T>> {
self.args.iter()
}
Expand Down
1 change: 1 addition & 0 deletions crates/intrinsic-test/src/common/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub struct Cli {
#[arg(long)]
pub target: String,

/// Percentage of intrinsics to test (used to limit testing to keep CI times manageable)
#[arg(long, default_value_t = 100u8)]
pub sample_percentage: u8,
}
Expand Down
6 changes: 3 additions & 3 deletions crates/intrinsic-test/src/common/constraint.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use serde::Deserialize;
use std::ops::Range;

/// Describes the values to test for a const generic parameter.
/// Describes the values to test for a const generic parameter
#[derive(Debug, PartialEq, Clone, Deserialize)]
pub enum Constraint {
/// Test a single value.
/// Test a single value
Equal(i64),
/// Test a range of values, e.g. `0..16`.
Range(Range<i64>),
Expand All @@ -13,7 +13,7 @@ pub enum Constraint {
}

impl Constraint {
/// Iterate over the values of this constraint.
/// Returns an iterator over the values of this constraint
pub fn iter<'a>(&'a self) -> impl Iterator<Item = i64> + 'a {
match self {
Constraint::Equal(i) => std::slice::Iter::default().copied().chain(*i..*i + 1),
Expand Down
10 changes: 10 additions & 0 deletions crates/intrinsic-test/src/common/gen_c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ use crate::common::intrinsic::Intrinsic;

use super::intrinsic_helpers::IntrinsicTypeDefinition;

/// Generates a C source file containing wrapper functions around each specialisation of each
/// intrinsic (that is, intrinsics with specific values for the the immediate arguments). Each
/// wrapper function is invoked via FFI from the Rust binary doing the testing.
///
/// e.g.
/// ```c
/// void __crc32cd_wrapper(uint32_t* __dst, uint32_t a, uint64_t b) {
/// *__dst = __crc32cd(a, b);
/// }
/// ```
pub fn write_wrapper_c<T: IntrinsicTypeDefinition>(
w: &mut impl std::io::Write,
notice: &str,
Expand Down
31 changes: 29 additions & 2 deletions crates/intrinsic-test/src/common/gen_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ use crate::common::argument::ArgumentList;
use crate::common::intrinsic::Intrinsic;
use crate::common::intrinsic_helpers::TypeKind;

// The number of times each intrinsic will be called.
// The number of times each intrinsic will be called - influences the generation of the
// test arrays to minimise repeated testing of the same test values.
pub(crate) const PASSES: u32 = 20;

// we need a reflexive equality relation, so treat NaNs as equal
/// Rust definitions that are included verbatim in the generated source. In particular, defines
/// a wrapper around float types that defines `NaN`s to be equal reflexively to enable
/// comparison of results that use floats types.
const COMMON_RUST_DEFINITIONS: &str = r#"
macro_rules! wrap_partialeq {
($($wrapper:ident ($inner:ty)),*) => {$(
Expand Down Expand Up @@ -53,6 +56,13 @@ pub fn run_rustfmt(source_path: &str) {
}
}

/// Writes a `Cargo.toml` containing a workspace with `module_count` members to `w`.
///
/// e.g.
/// ```toml
/// [workspace]
/// members = [ "mod_0", "mod_1" ]
/// ```
pub fn write_bin_cargo_toml(
w: &mut impl std::io::Write,
module_count: usize,
Expand All @@ -64,6 +74,8 @@ pub fn write_bin_cargo_toml(
writeln!(w, "]")
}

/// Writes a `Cargo.toml` for a crate with name `name` to `w` that will contain a single Rust source
/// file with a subset of the testing being generated.
pub fn write_lib_cargo_toml(w: &mut impl std::io::Write, name: &str) -> std::io::Result<()> {
write!(
w,
Expand All @@ -90,6 +102,8 @@ pub fn write_lib_cargo_toml(w: &mut impl std::io::Write, name: &str) -> std::io:
)
}

/// Writes a Rust source file into `w` with common definitions, static arrays with test values,
/// declarations of C wrapper functions for FFI and Rust test functions.
pub fn write_lib_rs<T: IntrinsicTypeDefinition>(
w: &mut impl std::io::Write,
notice: &str,
Expand Down Expand Up @@ -138,6 +152,13 @@ pub fn write_lib_rs<T: IntrinsicTypeDefinition>(
Ok(())
}

/// Writes the body of an intrinsic test to `w` for `intrinsic`.
///
/// Each specialisation of the intrinsic (i.e. specific instantiations of the immediate arguments
/// of the intrinsic) is added to an array of specialisations. Each specialisation is tested
/// (first loop) `PASSES` number of times (second loop). For a given iteration of a given
/// specialisation, test values are loaded for each argument and passed to the Rust intrinsic
/// and the C wrapper function, and the results are compared.
fn generate_rust_test_loop<T: IntrinsicTypeDefinition>(
w: &mut impl std::io::Write,
intrinsic: &Intrinsic<T>,
Expand Down Expand Up @@ -231,6 +252,8 @@ fn generate_rust_test_loop<T: IntrinsicTypeDefinition>(
)
}

/// Writes a test function for an given intrinsic to `w`, with a body generated by
/// `generate_rust_test_loop`.
fn create_rust_test<T: IntrinsicTypeDefinition>(
w: &mut impl std::io::Write,
intrinsic: &Intrinsic<T>,
Expand All @@ -250,6 +273,8 @@ fn create_rust_test<T: IntrinsicTypeDefinition>(
Ok(())
}

/// Writes an `extern "C"` block with function declarations for each of the C wrapper functions into
/// `w`.
pub fn write_bindings_rust<T: IntrinsicTypeDefinition>(
w: &mut impl std::io::Write,
i: usize,
Expand Down Expand Up @@ -283,6 +308,8 @@ pub fn write_bindings_rust<T: IntrinsicTypeDefinition>(
writeln!(w, "}}")
}

/// Writes a `build.rs` into `w` for each test crate that compiles the corresponding C source code
/// with wrapper functions.
pub fn write_build_rs(
w: &mut impl std::io::Write,
i: usize,
Expand Down
11 changes: 11 additions & 0 deletions crates/intrinsic-test/src/common/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ pub struct Intrinsic<T: IntrinsicTypeDefinition> {
pub arch_tags: Vec<String>,
}

/// Invokes `f` for each combination of the values in the constraint ranges.
///
/// For example, given `constraints=[Equal(0), Range(1..2), Set([3, 4])]` and `imm_values=[]`, this
/// produces the four calls to `f`: `f([0, 1, 3])`, `f([0, 1, 4])`, `f([0, 2, 3])`, `f([0, 2, 4])`.
fn recurse_specializations<'a, E>(
constraints: &mut (impl Iterator<Item = &'a Constraint> + Clone),
imm_values: &mut Vec<i64>,
Expand All @@ -37,6 +41,13 @@ fn recurse_specializations<'a, E>(
}

impl<T: IntrinsicTypeDefinition> Intrinsic<T> {
/// Invokes `f` for "specialisation" of the intrinsic - a specific instantiation of the
/// constant generics of the intrinsic. `f` takes a slice where the `i`th element corresponds
/// to the value of the `i`th const generic argument of the intrinsic.
///
/// For an intrinsic with three arguments with constraints `Equal(0)`, `Range(1..2)`,
/// `Set([3, 4])` respectively, this would produce four calls to `f`: `f(0, 1, 3)`,
/// `f(0, 1, 4)`, `f(0, 2, 3)`, `f(0, 2, 4)`.
pub fn iter_specializations<E>(
&self,
mut f: impl FnMut(&[i64]) -> Result<(), E>,
Expand Down
Loading
Loading