blob: 3f3bfed132c762ada341d913514f6842503509b6 [file] [log] [blame]
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::LazyLock;
use crate::format_code;
use crate::input::InputType;
use crate::intrinsic::Intrinsic;
use crate::typekinds::BaseType;
use crate::typekinds::{ToRepr, TypeKind};
use itertools::Itertools;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
// Number of vectors in our buffers - the maximum tuple size, 4, plus 1 as we set the vnum
// argument to 1.
const NUM_VECS: usize = 5;
// The maximum vector length (in bits)
const VL_MAX_BITS: usize = 2048;
// The maximum vector length (in bytes)
const VL_MAX_BYTES: usize = VL_MAX_BITS / 8;
// The maximum number of elements in each vector type
const LEN_F32: usize = VL_MAX_BYTES / core::mem::size_of::<f32>();
const LEN_F64: usize = VL_MAX_BYTES / core::mem::size_of::<f64>();
const LEN_I8: usize = VL_MAX_BYTES / core::mem::size_of::<i8>();
const LEN_I16: usize = VL_MAX_BYTES / core::mem::size_of::<i16>();
const LEN_I32: usize = VL_MAX_BYTES / core::mem::size_of::<i32>();
const LEN_I64: usize = VL_MAX_BYTES / core::mem::size_of::<i64>();
const LEN_U8: usize = VL_MAX_BYTES / core::mem::size_of::<u8>();
const LEN_U16: usize = VL_MAX_BYTES / core::mem::size_of::<u16>();
const LEN_U32: usize = VL_MAX_BYTES / core::mem::size_of::<u32>();
const LEN_U64: usize = VL_MAX_BYTES / core::mem::size_of::<u64>();
/// `load_intrinsics` and `store_intrinsics` is a vector of intrinsics
/// variants, while `out_path` is a file to write to.
pub fn generate_load_store_tests(
load_intrinsics: Vec<Intrinsic>,
store_intrinsics: Vec<Intrinsic>,
out_path: Option<&PathBuf>,
) -> Result<(), String> {
let output = match out_path {
Some(out) => {
Box::new(File::create(out).map_err(|e| format!("couldn't create tests file: {e}"))?)
as Box<dyn Write>
}
None => Box::new(std::io::stdout()) as Box<dyn Write>,
};
let mut used_stores = vec![false; store_intrinsics.len()];
let tests: Vec<_> = load_intrinsics
.iter()
.map(|load| {
let store_candidate = load
.signature
.fn_name()
.to_string()
.replace("svld1s", "svst1")
.replace("svld1u", "svst1")
.replace("svldnt1s", "svstnt1")
.replace("svldnt1u", "svstnt1")
.replace("svld", "svst")
.replace("gather", "scatter");
let store_index = store_intrinsics
.iter()
.position(|i| i.signature.fn_name().to_string() == store_candidate);
if let Some(i) = store_index {
used_stores[i] = true;
}
generate_single_test(
load.clone(),
store_index.map(|i| store_intrinsics[i].clone()),
)
})
.try_collect()?;
assert!(
used_stores.into_iter().all(|b| b),
"Not all store tests have been paired with a load. Consider generating specifc store-only tests"
);
let preamble =
TokenStream::from_str(&PREAMBLE).map_err(|e| format!("Preamble is invalid: {e}"))?;
// Only output manual tests for the SVE set
let manual_tests = match &load_intrinsics[0].target_features[..] {
[s] if s == "sve" => TokenStream::from_str(&MANUAL_TESTS)
.map_err(|e| format!("Manual tests are invalid: {e}"))?,
_ => quote!(),
};
format_code(
output,
format!(
"// This code is automatically generated. DO NOT MODIFY.
//
// Instead, modify `crates/stdarch-gen-arm/spec/sve` and run the following command to re-generate
// this file:
//
// ```
// cargo run --bin=stdarch-gen-arm -- crates/stdarch-gen-arm/spec
// ```
{}",
quote! { #preamble #(#tests)* #manual_tests }
),
)
.map_err(|e| format!("couldn't write tests: {e}"))
}
/// A test looks like this:
/// ```
/// let data = [scalable vector];
///
/// let mut storage = [0; N];
///
/// store_intrinsic([true_predicate], storage.as_mut_ptr(), data);
/// [test contents of storage]
///
/// let loaded == load_intrinsic([true_predicate], storage.as_ptr())
/// assert!(loaded == data);
/// ```
/// We intialise our data such that the value stored matches the index it's stored to.
/// By doing this we can validate scatters by checking that each value in the storage
/// array is either 0 or the same as its index.
fn generate_single_test(
load: Intrinsic,
store: Option<Intrinsic>,
) -> Result<proc_macro2::TokenStream, String> {
let chars = LdIntrCharacteristics::new(&load)?;
let fn_name = load.signature.fn_name().to_string();
#[allow(clippy::collapsible_if)]
if let Some(ty) = &chars.gather_bases_type {
if ty.base_type().unwrap().get_size() == Ok(32)
&& chars.gather_index_type.is_none()
&& chars.gather_offset_type.is_none()
{
// We lack a way to ensure data is in the bottom 32 bits of the address space
println!("Skipping test for {fn_name}");
return Ok(quote!());
}
}
if fn_name.starts_with("svldff1") && fn_name.contains("gather") {
// TODO: We can remove this check when first-faulting gathers are fixed in CI's QEMU
// https://gitlab.com/qemu-project/qemu/-/issues/1612
println!("Skipping test for {fn_name}");
return Ok(quote!());
}
let fn_ident = format_ident!("{fn_name}");
let test_name = format_ident!(
"test_{fn_name}{}",
if let Some(ref store) = store {
format!("_with_{}", store.signature.fn_name())
} else {
String::new()
}
);
let load_type = &chars.load_type;
let acle_type = load_type.acle_notation_repr();
// If there's no return type, fallback to the load type for things that depend on it
let ret_type = &load
.signature
.return_type
.as_ref()
.and_then(TypeKind::base_type)
.unwrap_or(load_type);
let pred_fn = format_ident!("svptrue_b{}", load_type.size());
let load_type_caps = load_type.rust_repr().to_uppercase();
let data_array = format_ident!("{load_type_caps}_DATA");
let size_fn = format_ident!("svcnt{}", ret_type.size_literal());
let rust_ret_type = ret_type.rust_repr();
let assert_fn = format_ident!("assert_vector_matches_{rust_ret_type}");
// Use vnum=1, so adjust all values by one vector length
let (length_call, vnum_arg) = if chars.vnum {
if chars.is_prf {
(quote!(), quote!(, 1))
} else {
(quote!(let len = #size_fn() as usize;), quote!(, 1))
}
} else {
(quote!(), quote!())
};
let (bases_load, bases_arg) = if let Some(ty) = &chars.gather_bases_type {
// Bases is a vector of (sometimes 32-bit) pointers
// When we combine bases with an offset/index argument, we load from the data arrays
// starting at 1
let base_ty = ty.base_type().unwrap();
let rust_type = format_ident!("{}", base_ty.rust_repr());
let index_fn = format_ident!("svindex_{}", base_ty.acle_notation_repr());
let size_in_bytes = chars.load_type.get_size().unwrap() / 8;
if base_ty.get_size().unwrap() == 32 {
// Treat bases as a vector of offsets here - we don't test this without an offset or
// index argument
(
Some(quote!(
let bases = #index_fn(0, #size_in_bytes.try_into().unwrap());
)),
quote!(, bases),
)
} else {
// Treat bases as a vector of pointers
let base_fn = format_ident!("svdup_n_{}", base_ty.acle_notation_repr());
let data_array = if store.is_some() {
format_ident!("storage")
} else {
format_ident!("{}_DATA", chars.load_type.rust_repr().to_uppercase())
};
let add_fn = format_ident!("svadd_{}_x", base_ty.acle_notation_repr());
(
Some(quote! {
let bases = #base_fn(#data_array.as_ptr() as #rust_type);
let offsets = #index_fn(0, #size_in_bytes.try_into().unwrap());
let bases = #add_fn(#pred_fn(), bases, offsets);
}),
quote!(, bases),
)
}
} else {
(None, quote!())
};
let index_arg = if let Some(ty) = &chars.gather_index_type {
let rust_type = format_ident!("{}", ty.rust_repr());
if chars
.gather_bases_type
.as_ref()
.and_then(TypeKind::base_type)
.map_or(Err(String::new()), BaseType::get_size)
.unwrap()
== 32
{
// Let index be the base of the data array
let data_array = if store.is_some() {
format_ident!("storage")
} else {
format_ident!("{}_DATA", chars.load_type.rust_repr().to_uppercase())
};
let size_in_bytes = chars.load_type.get_size().unwrap() / 8;
quote!(, #data_array.as_ptr() as #rust_type / (#size_in_bytes as #rust_type) + 1)
} else {
quote!(, 1.try_into().unwrap())
}
} else {
quote!()
};
let offset_arg = if let Some(ty) = &chars.gather_offset_type {
let size_in_bytes = chars.load_type.get_size().unwrap() / 8;
if chars
.gather_bases_type
.as_ref()
.and_then(TypeKind::base_type)
.map_or(Err(String::new()), BaseType::get_size)
.unwrap()
== 32
{
// Let offset be the base of the data array
let rust_type = format_ident!("{}", ty.rust_repr());
let data_array = if store.is_some() {
format_ident!("storage")
} else {
format_ident!("{}_DATA", chars.load_type.rust_repr().to_uppercase())
};
quote!(, #data_array.as_ptr() as #rust_type + #size_in_bytes as #rust_type)
} else {
quote!(, #size_in_bytes.try_into().unwrap())
}
} else {
quote!()
};
let (offsets_load, offsets_arg) = if let Some(ty) = &chars.gather_offsets_type {
// Offsets is a scalable vector of per-element offsets in bytes. We re-use the contiguous
// data for this, then multiply to get indices
let offsets_fn = format_ident!("svindex_{}", ty.base_type().unwrap().acle_notation_repr());
let size_in_bytes = chars.load_type.get_size().unwrap() / 8;
(
Some(quote! {
let offsets = #offsets_fn(0, #size_in_bytes.try_into().unwrap());
}),
quote!(, offsets),
)
} else {
(None, quote!())
};
let (indices_load, indices_arg) = if let Some(ty) = &chars.gather_indices_type {
// There's no need to multiply indices by the load type width
let base_ty = ty.base_type().unwrap();
let indices_fn = format_ident!("svindex_{}", base_ty.acle_notation_repr());
(
Some(quote! {
let indices = #indices_fn(0, 1);
}),
quote! {, indices},
)
} else {
(None, quote!())
};
let ptr = if chars.gather_bases_type.is_some() {
quote!()
} else if chars.is_prf {
quote!(, I64_DATA.as_ptr())
} else {
quote!(, #data_array.as_ptr())
};
let tuple_len = &chars.tuple_len;
let expecteds = if chars.is_prf {
// No return value for prefetches
vec![]
} else {
(0..*tuple_len)
.map(|i| get_expected_range(i, &chars))
.collect()
};
let asserts: Vec<_> =
if *tuple_len > 1 {
let svget = format_ident!("svget{tuple_len}_{acle_type}");
expecteds.iter().enumerate().map(|(i, expected)| {
quote! (#assert_fn(#svget::<{ #i as i32 }>(loaded), #expected);)
}).collect()
} else {
expecteds
.iter()
.map(|expected| quote! (#assert_fn(loaded, #expected);))
.collect()
};
let function = if chars.is_prf {
if fn_name.contains("gather") && fn_name.contains("base") && !fn_name.starts_with("svprf_")
{
// svprf(b|h|w|d)_gather base intrinsics do not have a generic type parameter
quote!(#fn_ident::<{ svprfop::SV_PLDL1KEEP }>)
} else {
quote!(#fn_ident::<{ svprfop::SV_PLDL1KEEP }, i64>)
}
} else {
quote!(#fn_ident)
};
let octaword_guard = if chars.replicate_width == Some(256) {
let msg = format!("Skipping {test_name} due to SVE vector length");
quote! {
if svcntb() < 32 {
println!(#msg);
return;
}
}
} else {
quote!()
};
let feats = load.target_features.join(",");
if let Some(store) = store {
let data_init = if *tuple_len == 1 {
quote!(#(#expecteds)*)
} else {
let create = format_ident!("svcreate{tuple_len}_{acle_type}");
quote!(#create(#(#expecteds),*))
};
let input = store.input.types.first().unwrap().get(0).unwrap();
let store_type = input
.get(store.test.get_typeset_index().unwrap())
.and_then(InputType::typekind)
.and_then(TypeKind::base_type)
.unwrap();
let store_type = format_ident!("{}", store_type.rust_repr());
let storage_len = NUM_VECS * VL_MAX_BITS / chars.load_type.get_size()? as usize;
let store_fn = format_ident!("{}", store.signature.fn_name().to_string());
let load_type = format_ident!("{}", chars.load_type.rust_repr());
let (store_ptr, store_mut_ptr) = if chars.gather_bases_type.is_none() {
(
quote!(, storage.as_ptr() as *const #load_type),
quote!(, storage.as_mut_ptr()),
)
} else {
(quote!(), quote!())
};
let args = quote!(#pred_fn() #store_ptr #vnum_arg #bases_arg #offset_arg #index_arg #offsets_arg #indices_arg);
let call = if chars.uses_ffr {
// Doing a normal load first maximises the number of elements our ff/nf test loads
let non_ffr_fn_name = format_ident!(
"{}",
fn_name
.replace("svldff1", "svld1")
.replace("svldnf1", "svld1")
);
quote! {
svsetffr();
let _ = #non_ffr_fn_name(#args);
let loaded = #function(#args);
}
} else {
// Note that the FFR must be set for all tests as the assert functions mask against it
quote! {
svsetffr();
let loaded = #function(#args);
}
};
Ok(quote! {
#[simd_test(enable = #feats)]
unsafe fn #test_name() {
#octaword_guard
#length_call
let mut storage = [0 as #store_type; #storage_len];
let data = #data_init;
#bases_load
#offsets_load
#indices_load
#store_fn(#pred_fn() #store_mut_ptr #vnum_arg #bases_arg #offset_arg #index_arg #offsets_arg #indices_arg, data);
for (i, &val) in storage.iter().enumerate() {
assert!(val == 0 as #store_type || val == i as #store_type);
}
#call
#(#asserts)*
}
})
} else {
let args = quote!(#pred_fn() #ptr #vnum_arg #bases_arg #offset_arg #index_arg #offsets_arg #indices_arg);
let call = if chars.uses_ffr {
// Doing a normal load first maximises the number of elements our ff/nf test loads
let non_ffr_fn_name = format_ident!(
"{}",
fn_name
.replace("svldff1", "svld1")
.replace("svldnf1", "svld1")
);
quote! {
svsetffr();
let _ = #non_ffr_fn_name(#args);
let loaded = #function(#args);
}
} else {
// Note that the FFR must be set for all tests as the assert functions mask against it
quote! {
svsetffr();
let loaded = #function(#args);
}
};
Ok(quote! {
#[simd_test(enable = #feats)]
unsafe fn #test_name() {
#octaword_guard
#bases_load
#offsets_load
#indices_load
#call
#length_call
#(#asserts)*
}
})
}
}
/// Assumes chars.ret_type is not None
fn get_expected_range(tuple_idx: usize, chars: &LdIntrCharacteristics) -> proc_macro2::TokenStream {
// vnum=1
let vnum_adjust = if chars.vnum { quote!(len+) } else { quote!() };
let bases_adjust =
(chars.gather_index_type.is_some() || chars.gather_offset_type.is_some()) as usize;
let tuple_len = chars.tuple_len;
let size = chars
.ret_type
.as_ref()
.and_then(TypeKind::base_type)
.unwrap_or(&chars.load_type)
.get_size()
.unwrap() as usize;
if chars.replicate_width == Some(128) {
// svld1rq
let ty_rust = format_ident!(
"{}",
chars
.ret_type
.as_ref()
.unwrap()
.base_type()
.unwrap()
.rust_repr()
);
let args: Vec<_> = (0..(128 / size)).map(|i| quote!(#i as #ty_rust)).collect();
let dup = format_ident!(
"svdupq_n_{}",
chars.ret_type.as_ref().unwrap().acle_notation_repr()
);
quote!(#dup(#(#args,)*))
} else if chars.replicate_width == Some(256) {
// svld1ro - we use two interleaved svdups to create a repeating 256-bit pattern
let ty_rust = format_ident!(
"{}",
chars
.ret_type
.as_ref()
.unwrap()
.base_type()
.unwrap()
.rust_repr()
);
let ret_acle = chars.ret_type.as_ref().unwrap().acle_notation_repr();
let args: Vec<_> = (0..(128 / size)).map(|i| quote!(#i as #ty_rust)).collect();
let args2: Vec<_> = ((128 / size)..(256 / size))
.map(|i| quote!(#i as #ty_rust))
.collect();
let dup = format_ident!("svdupq_n_{ret_acle}");
let interleave = format_ident!("svtrn1q_{ret_acle}");
quote!(#interleave(#dup(#(#args,)*), #dup(#(#args2,)*)))
} else {
let start = bases_adjust + tuple_idx;
if chars
.ret_type
.as_ref()
.unwrap()
.base_type()
.unwrap()
.is_float()
{
// Use svcvt to create a linear sequence of floats
let cvt_fn = format_ident!("svcvt_f{size}_s{size}_x");
let pred_fn = format_ident!("svptrue_b{size}");
let svindex_fn = format_ident!("svindex_s{size}");
quote! { #cvt_fn(#pred_fn(), #svindex_fn((#vnum_adjust #start).try_into().unwrap(), #tuple_len.try_into().unwrap()))}
} else {
let ret_acle = chars.ret_type.as_ref().unwrap().acle_notation_repr();
let svindex = format_ident!("svindex_{ret_acle}");
quote!(#svindex((#vnum_adjust #start).try_into().unwrap(), #tuple_len.try_into().unwrap()))
}
}
}
struct LdIntrCharacteristics {
// The data type to load from (not necessarily the data type returned)
load_type: BaseType,
// The data type to return (None for unit)
ret_type: Option<TypeKind>,
// The size of tuple to load/store
tuple_len: usize,
// Whether a vnum argument is present
vnum: bool,
// Is the intrinsic first/non-faulting?
uses_ffr: bool,
// Is it a prefetch?
is_prf: bool,
// The size of data loaded with svld1ro/q intrinsics
replicate_width: Option<usize>,
// Scalable vector of pointers to load from
gather_bases_type: Option<TypeKind>,
// Scalar offset, paired with bases
gather_offset_type: Option<TypeKind>,
// Scalar index, paired with bases
gather_index_type: Option<TypeKind>,
// Scalable vector of offsets
gather_offsets_type: Option<TypeKind>,
// Scalable vector of indices
gather_indices_type: Option<TypeKind>,
}
impl LdIntrCharacteristics {
fn new(intr: &Intrinsic) -> Result<LdIntrCharacteristics, String> {
let input = intr.input.types.first().unwrap().get(0).unwrap();
let load_type = input
.get(intr.test.get_typeset_index().unwrap())
.and_then(InputType::typekind)
.and_then(TypeKind::base_type)
.unwrap();
let ret_type = intr.signature.return_type.clone();
let name = intr.signature.fn_name().to_string();
let tuple_len = name
.chars()
.find(|c| c.is_numeric())
.and_then(|c| c.to_digit(10))
.unwrap_or(1) as usize;
let uses_ffr = name.starts_with("svldff") || name.starts_with("svldnf");
let is_prf = name.starts_with("svprf");
let replicate_width = if name.starts_with("svld1ro") {
Some(256)
} else if name.starts_with("svld1rq") {
Some(128)
} else {
None
};
let get_ty_of_arg = |name: &str| {
intr.signature
.arguments
.iter()
.find(|a| a.name.to_string() == name)
.map(|a| a.kind.clone())
};
let gather_bases_type = get_ty_of_arg("bases");
let gather_offset_type = get_ty_of_arg("offset");
let gather_index_type = get_ty_of_arg("index");
let gather_offsets_type = get_ty_of_arg("offsets");
let gather_indices_type = get_ty_of_arg("indices");
Ok(LdIntrCharacteristics {
load_type: *load_type,
ret_type,
tuple_len,
vnum: name.contains("vnum"),
uses_ffr,
is_prf,
replicate_width,
gather_bases_type,
gather_offset_type,
gather_index_type,
gather_offsets_type,
gather_indices_type,
})
}
}
static PREAMBLE: LazyLock<String> = LazyLock::new(|| {
format!(
r#"#![allow(unused)]
use super::*;
use std::boxed::Box;
use std::convert::{{TryFrom, TryInto}};
use std::sync::LazyLock;
use std::vec::Vec;
use stdarch_test::simd_test;
static F32_DATA: LazyLock<[f32; {LEN_F32} * {NUM_VECS}]> = LazyLock::new(|| {{
(0..{LEN_F32} * {NUM_VECS})
.map(|i| i as f32)
.collect::<Vec<_>>()
.try_into()
.expect("f32 data incorrectly initialised")
}});
static F64_DATA: LazyLock<[f64; {LEN_F64} * {NUM_VECS}]> = LazyLock::new(|| {{
(0..{LEN_F64} * {NUM_VECS})
.map(|i| i as f64)
.collect::<Vec<_>>()
.try_into()
.expect("f64 data incorrectly initialised")
}});
static I8_DATA: LazyLock<[i8; {LEN_I8} * {NUM_VECS}]> = LazyLock::new(|| {{
(0..{LEN_I8} * {NUM_VECS})
.map(|i| ((i + 128) % 256 - 128) as i8)
.collect::<Vec<_>>()
.try_into()
.expect("i8 data incorrectly initialised")
}});
static I16_DATA: LazyLock<[i16; {LEN_I16} * {NUM_VECS}]> = LazyLock::new(|| {{
(0..{LEN_I16} * {NUM_VECS})
.map(|i| i as i16)
.collect::<Vec<_>>()
.try_into()
.expect("i16 data incorrectly initialised")
}});
static I32_DATA: LazyLock<[i32; {LEN_I32} * {NUM_VECS}]> = LazyLock::new(|| {{
(0..{LEN_I32} * {NUM_VECS})
.map(|i| i as i32)
.collect::<Vec<_>>()
.try_into()
.expect("i32 data incorrectly initialised")
}});
static I64_DATA: LazyLock<[i64; {LEN_I64} * {NUM_VECS}]> = LazyLock::new(|| {{
(0..{LEN_I64} * {NUM_VECS})
.map(|i| i as i64)
.collect::<Vec<_>>()
.try_into()
.expect("i64 data incorrectly initialised")
}});
static U8_DATA: LazyLock<[u8; {LEN_U8} * {NUM_VECS}]> = LazyLock::new(|| {{
(0..{LEN_U8} * {NUM_VECS})
.map(|i| i as u8)
.collect::<Vec<_>>()
.try_into()
.expect("u8 data incorrectly initialised")
}});
static U16_DATA: LazyLock<[u16; {LEN_U16} * {NUM_VECS}]> = LazyLock::new(|| {{
(0..{LEN_U16} * {NUM_VECS})
.map(|i| i as u16)
.collect::<Vec<_>>()
.try_into()
.expect("u16 data incorrectly initialised")
}});
static U32_DATA: LazyLock<[u32; {LEN_U32} * {NUM_VECS}]> = LazyLock::new(|| {{
(0..{LEN_U32} * {NUM_VECS})
.map(|i| i as u32)
.collect::<Vec<_>>()
.try_into()
.expect("u32 data incorrectly initialised")
}});
static U64_DATA: LazyLock<[u64; {LEN_U64} * {NUM_VECS}]> = LazyLock::new(|| {{
(0..{LEN_U64} * {NUM_VECS})
.map(|i| i as u64)
.collect::<Vec<_>>()
.try_into()
.expect("u64 data incorrectly initialised")
}});
#[target_feature(enable = "sve")]
fn assert_vector_matches_f32(vector: svfloat32_t, expected: svfloat32_t) {{
let defined = svrdffr();
assert!(svptest_first(svptrue_b32(), defined));
let cmp = svcmpne_f32(defined, vector, expected);
assert!(!svptest_any(defined, cmp))
}}
#[target_feature(enable = "sve")]
fn assert_vector_matches_f64(vector: svfloat64_t, expected: svfloat64_t) {{
let defined = svrdffr();
assert!(svptest_first(svptrue_b64(), defined));
let cmp = svcmpne_f64(defined, vector, expected);
assert!(!svptest_any(defined, cmp))
}}
#[target_feature(enable = "sve")]
fn assert_vector_matches_i8(vector: svint8_t, expected: svint8_t) {{
let defined = svrdffr();
assert!(svptest_first(svptrue_b8(), defined));
let cmp = svcmpne_s8(defined, vector, expected);
assert!(!svptest_any(defined, cmp))
}}
#[target_feature(enable = "sve")]
fn assert_vector_matches_i16(vector: svint16_t, expected: svint16_t) {{
let defined = svrdffr();
assert!(svptest_first(svptrue_b16(), defined));
let cmp = svcmpne_s16(defined, vector, expected);
assert!(!svptest_any(defined, cmp))
}}
#[target_feature(enable = "sve")]
fn assert_vector_matches_i32(vector: svint32_t, expected: svint32_t) {{
let defined = svrdffr();
assert!(svptest_first(svptrue_b32(), defined));
let cmp = svcmpne_s32(defined, vector, expected);
assert!(!svptest_any(defined, cmp))
}}
#[target_feature(enable = "sve")]
fn assert_vector_matches_i64(vector: svint64_t, expected: svint64_t) {{
let defined = svrdffr();
assert!(svptest_first(svptrue_b64(), defined));
let cmp = svcmpne_s64(defined, vector, expected);
assert!(!svptest_any(defined, cmp))
}}
#[target_feature(enable = "sve")]
fn assert_vector_matches_u8(vector: svuint8_t, expected: svuint8_t) {{
let defined = svrdffr();
assert!(svptest_first(svptrue_b8(), defined));
let cmp = svcmpne_u8(defined, vector, expected);
assert!(!svptest_any(defined, cmp))
}}
#[target_feature(enable = "sve")]
fn assert_vector_matches_u16(vector: svuint16_t, expected: svuint16_t) {{
let defined = svrdffr();
assert!(svptest_first(svptrue_b16(), defined));
let cmp = svcmpne_u16(defined, vector, expected);
assert!(!svptest_any(defined, cmp))
}}
#[target_feature(enable = "sve")]
fn assert_vector_matches_u32(vector: svuint32_t, expected: svuint32_t) {{
let defined = svrdffr();
assert!(svptest_first(svptrue_b32(), defined));
let cmp = svcmpne_u32(defined, vector, expected);
assert!(!svptest_any(defined, cmp))
}}
#[target_feature(enable = "sve")]
fn assert_vector_matches_u64(vector: svuint64_t, expected: svuint64_t) {{
let defined = svrdffr();
assert!(svptest_first(svptrue_b64(), defined));
let cmp = svcmpne_u64(defined, vector, expected);
assert!(!svptest_any(defined, cmp))
}}
"#
)
});
const MANUAL_TESTS: &str = "#[simd_test(enable = \"sve\")]
unsafe fn test_ffr() {
svsetffr();
let ffr = svrdffr();
assert_vector_matches_u8(svdup_n_u8_z(ffr, 1), svindex_u8(1, 0));
let pred = svdupq_n_b8(true, false, true, false, true, false, true, false,
true, false, true, false, true, false, true, false);
svwrffr(pred);
let ffr = svrdffr_z(svptrue_b8());
assert_vector_matches_u8(svdup_n_u8_z(ffr, 1), svdup_n_u8_z(pred, 1));
}
";