summaryrefslogtreecommitdiffstats
path: root/library/stdarch/crates/intrinsic-test/src
diff options
context:
space:
mode:
Diffstat (limited to 'library/stdarch/crates/intrinsic-test/src')
-rw-r--r--library/stdarch/crates/intrinsic-test/src/acle_csv_parser.rs319
-rw-r--r--library/stdarch/crates/intrinsic-test/src/argument.rs139
-rw-r--r--library/stdarch/crates/intrinsic-test/src/intrinsic.rs125
-rw-r--r--library/stdarch/crates/intrinsic-test/src/main.rs479
-rw-r--r--library/stdarch/crates/intrinsic-test/src/types.rs436
-rw-r--r--library/stdarch/crates/intrinsic-test/src/values.rs126
6 files changed, 1624 insertions, 0 deletions
diff --git a/library/stdarch/crates/intrinsic-test/src/acle_csv_parser.rs b/library/stdarch/crates/intrinsic-test/src/acle_csv_parser.rs
new file mode 100644
index 000000000..d7b066485
--- /dev/null
+++ b/library/stdarch/crates/intrinsic-test/src/acle_csv_parser.rs
@@ -0,0 +1,319 @@
+use itertools::Itertools;
+use regex::Regex;
+use serde::Deserialize;
+
+use crate::argument::{Argument, ArgumentList, Constraint};
+use crate::intrinsic::Intrinsic;
+use crate::types::{IntrinsicType, TypeKind};
+
+pub fn get_acle_intrinsics(filename: &str) -> Vec<Intrinsic> {
+ let data = std::fs::read_to_string(filename).expect("Failed to open ACLE intrinsics file");
+
+ let data = data
+ .lines()
+ .filter_map(|l| {
+ (!(l.starts_with("<COMMENT>") || l.is_empty() || l.starts_with("<SECTION>")))
+ .then(|| l.replace("<HEADER>\t", ""))
+ })
+ .join("\n");
+
+ let mut csv_reader = csv::ReaderBuilder::new()
+ .delimiter(b'\t')
+ .from_reader(data.as_bytes());
+
+ let mut intrinsics: Vec<Intrinsic> = csv_reader
+ .deserialize()
+ .filter_map(|x: Result<ACLEIntrinsicLine, _>| x.ok().map(|i| i.into()))
+ .collect::<Vec<_>>();
+
+ // Intrinsics such as vshll_n_s8 exist twice in the ACLE with different constraints.
+ intrinsics.sort_by(|a, b| a.name.cmp(&b.name));
+ let (intrinsics, duplicates) = intrinsics.partition_dedup_by(|a, b| a.name == b.name);
+ for duplicate in duplicates {
+ let name = &duplicate.name;
+ let constraints = duplicate
+ .arguments
+ .args
+ .drain(..)
+ .filter(|a| a.has_constraint());
+ let intrinsic = intrinsics.iter_mut().find(|i| &i.name == name).unwrap();
+
+ for mut constraint in constraints {
+ let real_constraint = intrinsic
+ .arguments
+ .args
+ .iter_mut()
+ .find(|a| a.name == constraint.name)
+ .unwrap();
+ real_constraint
+ .constraints
+ .push(constraint.constraints.pop().unwrap());
+ }
+ }
+
+ intrinsics.to_vec()
+}
+
+impl Into<Intrinsic> for ACLEIntrinsicLine {
+ fn into(self) -> Intrinsic {
+ let signature = self.intrinsic;
+ let (ret_ty, remaining) = signature.split_once(' ').unwrap();
+
+ let results = type_from_c(ret_ty)
+ .unwrap_or_else(|_| panic!("Failed to parse return type: {}", ret_ty));
+
+ let (name, args) = remaining.split_once('(').unwrap();
+ let args = args.trim_end_matches(')');
+
+ // Typo in ACLE data
+ let args = args.replace("int16x8q_t", "int16x8_t");
+
+ let arg_prep = self.argument_preparation.as_str();
+ let args = args
+ .split(',')
+ .enumerate()
+ .map(move |(idx, arg)| {
+ let arg = arg.trim();
+ if arg.starts_with("__builtin_constant_p") {
+ handle_constraint(idx, arg, arg_prep)
+ } else {
+ from_c(idx, arg)
+ }
+ })
+ .collect();
+ let arguments = ArgumentList { args };
+ let a64_only = match &*self.supported_architectures {
+ "A64" => true,
+ "v7/A32/A64" | "A32/A64" => false,
+ _ => panic!("Invalid supported architectures"),
+ };
+
+ Intrinsic {
+ name: name.to_string(),
+ arguments,
+ results,
+ a64_only,
+ }
+ }
+}
+
+fn handle_constraint(idx: usize, arg: &str, prep: &str) -> Argument {
+ let prep = prep.replace(' ', "");
+
+ let name = arg
+ .trim_start_matches("__builtin_constant_p")
+ .trim_start_matches(|ref c| c == &' ' || c == &'(')
+ .trim_end_matches(')')
+ .to_string();
+
+ let ty = IntrinsicType::Type {
+ constant: true,
+ kind: TypeKind::Int,
+ bit_len: Some(32),
+ simd_len: None,
+ vec_len: None,
+ };
+
+ let constraints = prep
+ .split(';')
+ .find_map(|p| handle_range_constraint(&name, p).or_else(|| handle_eq_constraint(&name, p)))
+ .map(|c| vec![c])
+ .unwrap_or_default();
+
+ Argument {
+ pos: idx,
+ name,
+ ty,
+ constraints,
+ }
+}
+
+fn handle_range_constraint(name: &str, data: &str) -> Option<Constraint> {
+ lazy_static! {
+ static ref RANGE_CONSTRAINT: Regex =
+ Regex::new(r#"([0-9]+)<=([[:alnum:]]+)<=([0-9]+)"#).unwrap();
+ }
+
+ let captures = RANGE_CONSTRAINT.captures(data)?;
+ if captures.get(2).map(|c| c.as_str() == name).unwrap_or(false) {
+ match (captures.get(1), captures.get(3)) {
+ (Some(start), Some(end)) => {
+ let start = start.as_str().parse::<i64>().unwrap();
+ let end = end.as_str().parse::<i64>().unwrap() + 1;
+ Some(Constraint::Range(start..end))
+ }
+ _ => panic!("Invalid constraint"),
+ }
+ } else {
+ None
+ }
+}
+
+fn handle_eq_constraint(name: &str, data: &str) -> Option<Constraint> {
+ lazy_static! {
+ static ref EQ_CONSTRAINT: Regex = Regex::new(r#"([[:alnum:]]+)==([0-9]+)"#).unwrap();
+ }
+ let captures = EQ_CONSTRAINT.captures(data)?;
+ if captures.get(1).map(|c| c.as_str() == name).unwrap_or(false) {
+ captures
+ .get(2)
+ .map(|c| Constraint::Equal(c.as_str().parse::<i64>().unwrap()))
+ } else {
+ None
+ }
+}
+
+fn from_c(pos: usize, s: &str) -> Argument {
+ let name_index = s
+ .chars()
+ .rev()
+ .take_while(|c| c != &'*' && c != &' ')
+ .count();
+
+ let name_start = s.len() - name_index;
+ let name = s[name_start..].to_string();
+ let s = s[..name_start].trim();
+
+ Argument {
+ pos,
+ name,
+ ty: type_from_c(s).unwrap_or_else(|_| panic!("Failed to parse type: {}", s)),
+ constraints: vec![],
+ }
+}
+
+fn type_from_c(s: &str) -> Result<IntrinsicType, String> {
+ const CONST_STR: &str = "const ";
+
+ if let Some(s) = s.strip_suffix('*') {
+ let (s, constant) = if s.ends_with(CONST_STR) {
+ (&s[..s.len() - (CONST_STR.len() + 1)], true)
+ } else {
+ (s, false)
+ };
+
+ let s = s.trim_end();
+
+ Ok(IntrinsicType::Ptr {
+ constant,
+ child: Box::new(type_from_c(s)?),
+ })
+ } else {
+ // [const ]TYPE[{bitlen}[x{simdlen}[x{vec_len}]]][_t]
+
+ let (mut s, constant) = if let Some(s) = s.strip_prefix(CONST_STR) {
+ (s, true)
+ } else {
+ (s, false)
+ };
+ s = s.strip_suffix("_t").unwrap_or(s);
+
+ let mut parts = s.split('x'); // [[{bitlen}], [{simdlen}], [{vec_len}] ]
+
+ let start = parts.next().ok_or("Impossible to parse type")?;
+
+ if let Some(digit_start) = start.find(|c: char| c.is_ascii_digit()) {
+ let (arg_kind, bit_len) = start.split_at(digit_start);
+
+ let arg_kind = arg_kind.parse::<TypeKind>()?;
+ let bit_len = bit_len.parse::<u32>().map_err(|err| err.to_string())?;
+
+ let simd_len = parts.next().map(|part| part.parse::<u32>().ok()).flatten();
+ let vec_len = parts.next().map(|part| part.parse::<u32>().ok()).flatten();
+
+ Ok(IntrinsicType::Type {
+ constant,
+ kind: arg_kind,
+ bit_len: Some(bit_len),
+ simd_len,
+ vec_len,
+ })
+ } else {
+ Ok(IntrinsicType::Type {
+ constant,
+ kind: start.parse::<TypeKind>()?,
+ bit_len: None,
+ simd_len: None,
+ vec_len: None,
+ })
+ }
+ }
+}
+
+#[derive(Deserialize, Debug, PartialEq, Clone)]
+struct ACLEIntrinsicLine {
+ #[serde(rename = "Intrinsic")]
+ intrinsic: String,
+ #[serde(rename = "Argument preparation")]
+ argument_preparation: String,
+ #[serde(rename = "AArch64 Instruction")]
+ aarch64_instruction: String,
+ #[serde(rename = "Result")]
+ result: String,
+ #[serde(rename = "Supported architectures")]
+ supported_architectures: String,
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::argument::Argument;
+ use crate::types::{IntrinsicType, TypeKind};
+
+ #[test]
+ fn parse_simd() {
+ let expected = Argument {
+ pos: 0,
+ name: "a".into(),
+ ty: IntrinsicType::Type {
+ constant: false,
+ kind: TypeKind::Int,
+ bit_len: Some(32),
+ simd_len: Some(4),
+ vec_len: None,
+ },
+ constraints: vec![],
+ };
+ let actual = from_c(0, "int32x4_t a");
+ assert_eq!(expected, actual);
+ }
+
+ #[test]
+ fn parse_simd_with_vec() {
+ let expected = Argument {
+ pos: 0,
+ name: "a".into(),
+ ty: IntrinsicType::Type {
+ constant: false,
+ kind: TypeKind::Int,
+ bit_len: Some(32),
+ simd_len: Some(4),
+ vec_len: Some(2),
+ },
+ constraints: vec![],
+ };
+ let actual = from_c(0, "int32x4x2_t a");
+ assert_eq!(expected, actual);
+ }
+
+ #[test]
+ fn test_ptr() {
+ let expected = Argument {
+ pos: 0,
+ name: "ptr".into(),
+ ty: crate::types::IntrinsicType::Ptr {
+ constant: true,
+ child: Box::new(IntrinsicType::Type {
+ constant: false,
+ kind: TypeKind::Int,
+ bit_len: Some(8),
+ simd_len: None,
+ vec_len: None,
+ }),
+ },
+ constraints: vec![],
+ };
+ let actual = from_c(0, "int8_t const *ptr");
+ assert_eq!(expected, actual);
+ }
+}
diff --git a/library/stdarch/crates/intrinsic-test/src/argument.rs b/library/stdarch/crates/intrinsic-test/src/argument.rs
new file mode 100644
index 000000000..f4cb77992
--- /dev/null
+++ b/library/stdarch/crates/intrinsic-test/src/argument.rs
@@ -0,0 +1,139 @@
+use std::ops::Range;
+
+use crate::types::IntrinsicType;
+use crate::Language;
+
+/// An argument for the intrinsic.
+#[derive(Debug, PartialEq, Clone)]
+pub struct Argument {
+ /// The argument's index in the intrinsic function call.
+ pub pos: usize,
+ /// The argument name.
+ pub name: String,
+ /// The type of the argument.
+ pub ty: IntrinsicType,
+ /// Any constraints that are on this argument
+ pub constraints: Vec<Constraint>,
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub enum Constraint {
+ Equal(i64),
+ Range(Range<i64>),
+}
+
+impl Constraint {
+ pub fn to_range(&self) -> Range<i64> {
+ match self {
+ Constraint::Equal(eq) => *eq..*eq + 1,
+ Constraint::Range(range) => range.clone(),
+ }
+ }
+}
+
+impl Argument {
+ fn to_c_type(&self) -> String {
+ self.ty.c_type()
+ }
+
+ fn is_simd(&self) -> bool {
+ self.ty.is_simd()
+ }
+
+ pub fn is_ptr(&self) -> bool {
+ self.ty.is_ptr()
+ }
+
+ pub fn has_constraint(&self) -> bool {
+ !self.constraints.is_empty()
+ }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct ArgumentList {
+ pub args: Vec<Argument>,
+}
+
+impl ArgumentList {
+ /// Converts the argument list into the call parameters for a C function call.
+ /// e.g. this would generate something like `a, &b, c`
+ pub fn as_call_param_c(&self) -> String {
+ self.args
+ .iter()
+ .map(|arg| match arg.ty {
+ IntrinsicType::Ptr { .. } => {
+ format!("&{}", arg.name)
+ }
+ IntrinsicType::Type { .. } => arg.name.clone(),
+ })
+ .collect::<Vec<String>>()
+ .join(", ")
+ }
+
+ /// Converts the argument list into the call parameters for a Rust function.
+ /// e.g. this would generate something like `a, b, c`
+ pub fn as_call_param_rust(&self) -> String {
+ self.args
+ .iter()
+ .filter(|a| !a.has_constraint())
+ .map(|arg| arg.name.clone())
+ .collect::<Vec<String>>()
+ .join(", ")
+ }
+
+ pub fn as_constraint_parameters_rust(&self) -> String {
+ self.args
+ .iter()
+ .filter(|a| a.has_constraint())
+ .map(|arg| arg.name.clone())
+ .collect::<Vec<String>>()
+ .join(", ")
+ }
+
+ /// Creates a line that initializes this argument for C code.
+ /// e.g. `int32x2_t a = { 0x1, 0x2 };`
+ pub fn init_random_values_c(&self, pass: usize) -> String {
+ self.iter()
+ .filter_map(|arg| {
+ (!arg.has_constraint()).then(|| {
+ format!(
+ "{ty} {name} = {{ {values} }};",
+ ty = arg.to_c_type(),
+ name = arg.name,
+ values = arg.ty.populate_random(pass, &Language::C)
+ )
+ })
+ })
+ .collect::<Vec<_>>()
+ .join("\n ")
+ }
+
+ /// Creates a line that initializes this argument for Rust code.
+ /// e.g. `let a = transmute([0x1, 0x2]);`
+ pub fn init_random_values_rust(&self, pass: usize) -> String {
+ self.iter()
+ .filter_map(|arg| {
+ (!arg.has_constraint()).then(|| {
+ if arg.is_simd() {
+ format!(
+ "let {name} = ::std::mem::transmute([{values}]);",
+ name = arg.name,
+ values = arg.ty.populate_random(pass, &Language::Rust),
+ )
+ } else {
+ format!(
+ "let {name} = {value};",
+ name = arg.name,
+ value = arg.ty.populate_random(pass, &Language::Rust)
+ )
+ }
+ })
+ })
+ .collect::<Vec<_>>()
+ .join("\n ")
+ }
+
+ pub fn iter(&self) -> std::slice::Iter<'_, Argument> {
+ self.args.iter()
+ }
+}
diff --git a/library/stdarch/crates/intrinsic-test/src/intrinsic.rs b/library/stdarch/crates/intrinsic-test/src/intrinsic.rs
new file mode 100644
index 000000000..2b7130440
--- /dev/null
+++ b/library/stdarch/crates/intrinsic-test/src/intrinsic.rs
@@ -0,0 +1,125 @@
+use crate::types::{IntrinsicType, TypeKind};
+
+use super::argument::ArgumentList;
+
+/// An intrinsic
+#[derive(Debug, PartialEq, Clone)]
+pub struct Intrinsic {
+ /// The function name of this intrinsic.
+ pub name: String,
+
+ /// Any arguments for this intrinsic.
+ pub arguments: ArgumentList,
+
+ /// The return type of this intrinsic.
+ pub results: IntrinsicType,
+
+ /// Whether this intrinsic is only available on A64.
+ pub a64_only: bool,
+}
+
+impl Intrinsic {
+ /// Generates a std::cout for the intrinsics results that will match the
+ /// rust debug output format for the return type.
+ pub fn print_result_c(&self, index: usize, additional: &str) -> String {
+ let lanes = if self.results.num_vectors() > 1 {
+ (0..self.results.num_vectors())
+ .map(|vector| {
+ format!(
+ r#""{ty}(" << {lanes} << ")""#,
+ ty = self.results.c_single_vector_type(),
+ lanes = (0..self.results.num_lanes())
+ .map(move |idx| -> std::string::String {
+ format!(
+ "{cast}{lane_fn}(__return_value.val[{vector}], {lane})",
+ cast = self.results.c_promotion(),
+ lane_fn = self.results.get_lane_function(),
+ lane = idx,
+ vector = vector,
+ )
+ })
+ .collect::<Vec<_>>()
+ .join(r#" << ", " << "#)
+ )
+ })
+ .collect::<Vec<_>>()
+ .join(r#" << ", " << "#)
+ } else if self.results.num_lanes() > 1 {
+ (0..self.results.num_lanes())
+ .map(|idx| -> std::string::String {
+ format!(
+ "{cast}{lane_fn}(__return_value, {lane})",
+ cast = self.results.c_promotion(),
+ lane_fn = self.results.get_lane_function(),
+ lane = idx
+ )
+ })
+ .collect::<Vec<_>>()
+ .join(r#" << ", " << "#)
+ } else {
+ format!(
+ "{promote}cast<{cast}>(__return_value)",
+ cast = match self.results.kind() {
+ TypeKind::Float if self.results.inner_size() == 32 => "float".to_string(),
+ TypeKind::Float if self.results.inner_size() == 64 => "double".to_string(),
+ TypeKind::Int => format!("int{}_t", self.results.inner_size()),
+ TypeKind::UInt => format!("uint{}_t", self.results.inner_size()),
+ TypeKind::Poly => format!("poly{}_t", self.results.inner_size()),
+ ty => todo!("print_result_c - Unknown type: {:#?}", ty),
+ },
+ promote = self.results.c_promotion(),
+ )
+ };
+
+ format!(
+ r#"std::cout << "Result {additional}-{idx}: {ty}" << std::fixed << std::setprecision(150) << {lanes} << "{close}" << std::endl;"#,
+ ty = if self.results.is_simd() {
+ format!("{}(", self.results.c_type())
+ } else {
+ String::from("")
+ },
+ close = if self.results.is_simd() { ")" } else { "" },
+ lanes = lanes,
+ additional = additional,
+ idx = index,
+ )
+ }
+
+ pub fn generate_pass_rust(&self, index: usize, additional: &str) -> String {
+ let constraints = self.arguments.as_constraint_parameters_rust();
+ let constraints = if !constraints.is_empty() {
+ format!("::<{}>", constraints)
+ } else {
+ constraints
+ };
+
+ format!(
+ r#"
+ unsafe {{
+ {initialized_args}
+ let res = {intrinsic_call}{const}({args});
+ println!("Result {additional}-{idx}: {{:.150?}}", res);
+ }}"#,
+ initialized_args = self.arguments.init_random_values_rust(index),
+ intrinsic_call = self.name,
+ args = self.arguments.as_call_param_rust(),
+ additional = additional,
+ idx = index,
+ const = constraints,
+ )
+ }
+
+ pub fn generate_pass_c(&self, index: usize, additional: &str) -> String {
+ format!(
+ r#" {{
+ {initialized_args}
+ auto __return_value = {intrinsic_call}({args});
+ {print_result}
+ }}"#,
+ initialized_args = self.arguments.init_random_values_c(index),
+ intrinsic_call = self.name,
+ args = self.arguments.as_call_param_c(),
+ print_result = self.print_result_c(index, additional)
+ )
+ }
+}
diff --git a/library/stdarch/crates/intrinsic-test/src/main.rs b/library/stdarch/crates/intrinsic-test/src/main.rs
new file mode 100644
index 000000000..1b58da2fd
--- /dev/null
+++ b/library/stdarch/crates/intrinsic-test/src/main.rs
@@ -0,0 +1,479 @@
+#![feature(slice_partition_dedup)]
+#[macro_use]
+extern crate lazy_static;
+#[macro_use]
+extern crate log;
+
+use std::fs::File;
+use std::io::Write;
+use std::process::Command;
+
+use clap::{App, Arg};
+use intrinsic::Intrinsic;
+use itertools::Itertools;
+use rayon::prelude::*;
+use types::TypeKind;
+
+use crate::acle_csv_parser::get_acle_intrinsics;
+use crate::argument::Argument;
+
+mod acle_csv_parser;
+mod argument;
+mod intrinsic;
+mod types;
+mod values;
+
+#[derive(Debug, PartialEq)]
+pub enum Language {
+ Rust,
+ C,
+}
+
+fn gen_code_c(intrinsic: &Intrinsic, constraints: &[&Argument], name: String) -> String {
+ if let Some((current, constraints)) = constraints.split_last() {
+ let range = current
+ .constraints
+ .iter()
+ .map(|c| c.to_range())
+ .flat_map(|r| r.into_iter());
+
+ range
+ .map(|i| {
+ format!(
+ r#" {{
+ {ty} {name} = {val};
+{pass}
+ }}"#,
+ name = current.name,
+ ty = current.ty.c_type(),
+ val = i,
+ pass = gen_code_c(intrinsic, constraints, format!("{}-{}", name, i))
+ )
+ })
+ .collect()
+ } else {
+ (1..20)
+ .map(|idx| intrinsic.generate_pass_c(idx, &name))
+ .collect::<Vec<_>>()
+ .join("\n")
+ }
+}
+
+fn generate_c_program(header_files: &[&str], intrinsic: &Intrinsic) -> String {
+ let constraints = intrinsic
+ .arguments
+ .iter()
+ .filter(|i| i.has_constraint())
+ .collect_vec();
+
+ format!(
+ r#"{header_files}
+#include <iostream>
+#include <cstring>
+#include <iomanip>
+#include <sstream>
+
+template<typename T1, typename T2> T1 cast(T2 x) {{
+ static_assert(sizeof(T1) == sizeof(T2), "sizeof T1 and T2 must be the same");
+ T1 ret = 0;
+ memcpy(&ret, &x, sizeof(T1));
+ return ret;
+}}
+
+#ifdef __aarch64__
+std::ostream& operator<<(std::ostream& os, poly128_t value) {{
+ std::stringstream temp;
+ do {{
+ int n = value % 10;
+ value /= 10;
+ temp << n;
+ }} while (value != 0);
+ std::string tempstr(temp.str());
+ std::string res(tempstr.rbegin(), tempstr.rend());
+ os << res;
+ return os;
+}}
+#endif
+
+int main(int argc, char **argv) {{
+{passes}
+ return 0;
+}}"#,
+ header_files = header_files
+ .iter()
+ .map(|header| format!("#include <{}>", header))
+ .collect::<Vec<_>>()
+ .join("\n"),
+ passes = gen_code_c(intrinsic, constraints.as_slice(), Default::default()),
+ )
+}
+
+fn gen_code_rust(intrinsic: &Intrinsic, constraints: &[&Argument], name: String) -> String {
+ if let Some((current, constraints)) = constraints.split_last() {
+ let range = current
+ .constraints
+ .iter()
+ .map(|c| c.to_range())
+ .flat_map(|r| r.into_iter());
+
+ range
+ .map(|i| {
+ format!(
+ r#" {{
+ const {name}: {ty} = {val};
+{pass}
+ }}"#,
+ name = current.name,
+ ty = current.ty.rust_type(),
+ val = i,
+ pass = gen_code_rust(intrinsic, constraints, format!("{}-{}", name, i))
+ )
+ })
+ .collect()
+ } else {
+ (1..20)
+ .map(|idx| intrinsic.generate_pass_rust(idx, &name))
+ .collect::<Vec<_>>()
+ .join("\n")
+ }
+}
+
+fn generate_rust_program(intrinsic: &Intrinsic, a32: bool) -> String {
+ let constraints = intrinsic
+ .arguments
+ .iter()
+ .filter(|i| i.has_constraint())
+ .collect_vec();
+
+ format!(
+ r#"#![feature(simd_ffi)]
+#![feature(link_llvm_intrinsics)]
+#![feature(stdsimd)]
+#![allow(overflowing_literals)]
+#![allow(non_upper_case_globals)]
+use core_arch::arch::{target_arch}::*;
+
+fn main() {{
+{passes}
+}}
+"#,
+ target_arch = if a32 { "arm" } else { "aarch64" },
+ passes = gen_code_rust(intrinsic, &constraints, Default::default())
+ )
+}
+
+fn compile_c(c_filename: &str, intrinsic: &Intrinsic, compiler: &str, a32: bool) -> bool {
+ let flags = std::env::var("CPPFLAGS").unwrap_or("".into());
+
+ let output = Command::new("sh")
+ .arg("-c")
+ .arg(format!(
+ "{cpp} {cppflags} {arch_flags} -Wno-narrowing -O2 -target {target} -o c_programs/{intrinsic} {filename}",
+ target = if a32 { "armv7-unknown-linux-gnueabihf" } else { "aarch64-unknown-linux-gnu" },
+ arch_flags = if a32 { "-march=armv8.6-a+crypto+crc+dotprod" } else { "-march=armv8.6-a+crypto+sha3+crc+dotprod" },
+ filename = c_filename,
+ intrinsic = intrinsic.name,
+ cpp = compiler,
+ cppflags = flags,
+ ))
+ .output();
+ if let Ok(output) = output {
+ if output.status.success() {
+ true
+ } else {
+ error!(
+ "Failed to compile code for intrinsic: {}\n\nstdout:\n{}\n\nstderr:\n{}",
+ intrinsic.name,
+ std::str::from_utf8(&output.stdout).unwrap_or(""),
+ std::str::from_utf8(&output.stderr).unwrap_or("")
+ );
+ false
+ }
+ } else {
+ error!("Command failed: {:#?}", output);
+ false
+ }
+}
+
+fn build_c(intrinsics: &Vec<Intrinsic>, compiler: &str, a32: bool) -> bool {
+ let _ = std::fs::create_dir("c_programs");
+ intrinsics
+ .par_iter()
+ .map(|i| {
+ let c_filename = format!(r#"c_programs/{}.cpp"#, i.name);
+ let mut file = File::create(&c_filename).unwrap();
+
+ let c_code = generate_c_program(&["arm_neon.h", "arm_acle.h"], &i);
+ file.write_all(c_code.into_bytes().as_slice()).unwrap();
+ compile_c(&c_filename, &i, compiler, a32)
+ })
+ .find_any(|x| !x)
+ .is_none()
+}
+
+fn build_rust(intrinsics: &Vec<Intrinsic>, toolchain: &str, a32: bool) -> bool {
+ intrinsics.iter().for_each(|i| {
+ let rust_dir = format!(r#"rust_programs/{}"#, i.name);
+ let _ = std::fs::create_dir_all(&rust_dir);
+ let rust_filename = format!(r#"{}/main.rs"#, rust_dir);
+ let mut file = File::create(&rust_filename).unwrap();
+
+ let c_code = generate_rust_program(&i, a32);
+ file.write_all(c_code.into_bytes().as_slice()).unwrap();
+ });
+
+ let mut cargo = File::create("rust_programs/Cargo.toml").unwrap();
+ cargo
+ .write_all(
+ format!(
+ r#"[package]
+name = "intrinsic-test"
+version = "{version}"
+authors = ["{authors}"]
+edition = "2018"
+[workspace]
+[dependencies]
+core_arch = {{ path = "../crates/core_arch" }}
+{binaries}"#,
+ version = env!("CARGO_PKG_VERSION"),
+ authors = env!("CARGO_PKG_AUTHORS"),
+ binaries = intrinsics
+ .iter()
+ .map(|i| {
+ format!(
+ r#"[[bin]]
+name = "{intrinsic}"
+path = "{intrinsic}/main.rs""#,
+ intrinsic = i.name
+ )
+ })
+ .collect::<Vec<_>>()
+ .join("\n")
+ )
+ .into_bytes()
+ .as_slice(),
+ )
+ .unwrap();
+
+ let output = Command::new("sh")
+ .current_dir("rust_programs")
+ .arg("-c")
+ .arg(format!(
+ "cargo {toolchain} build --target {target}",
+ toolchain = toolchain,
+ target = if a32 {
+ "armv7-unknown-linux-gnueabihf"
+ } else {
+ "aarch64-unknown-linux-gnu"
+ },
+ ))
+ .env("RUSTFLAGS", "-Cdebuginfo=0")
+ .output();
+ if let Ok(output) = output {
+ if output.status.success() {
+ true
+ } else {
+ error!(
+ "Failed to compile code for intrinsics\n\nstdout:\n{}\n\nstderr:\n{}",
+ std::str::from_utf8(&output.stdout).unwrap_or(""),
+ std::str::from_utf8(&output.stderr).unwrap_or("")
+ );
+ false
+ }
+ } else {
+ error!("Command failed: {:#?}", output);
+ false
+ }
+}
+
+fn main() {
+ pretty_env_logger::init();
+
+ let matches = App::new("Intrinsic test tool")
+ .about("Generates Rust and C programs for intrinsics and compares the output")
+ .arg(
+ Arg::with_name("INPUT")
+ .help("The input file containing the intrinsics")
+ .required(true)
+ .index(1),
+ )
+ .arg(
+ Arg::with_name("TOOLCHAIN")
+ .takes_value(true)
+ .long("toolchain")
+ .help("The rust toolchain to use for building the rust code"),
+ )
+ .arg(
+ Arg::with_name("CPPCOMPILER")
+ .takes_value(true)
+ .default_value("clang++")
+ .long("cppcompiler")
+ .help("The C++ compiler to use for compiling the c++ code"),
+ )
+ .arg(
+ Arg::with_name("RUNNER")
+ .takes_value(true)
+ .long("runner")
+ .help("Run the C programs under emulation with this command"),
+ )
+ .arg(
+ Arg::with_name("SKIP")
+ .takes_value(true)
+ .long("skip")
+ .help("Filename for a list of intrinsics to skip (one per line)"),
+ )
+ .arg(
+ Arg::with_name("A32")
+ .takes_value(false)
+ .long("a32")
+ .help("Run tests for A32 instrinsics instead of A64"),
+ )
+ .get_matches();
+
+ let filename = matches.value_of("INPUT").unwrap();
+ let toolchain = matches
+ .value_of("TOOLCHAIN")
+ .map_or("".into(), |t| format!("+{}", t));
+
+ let cpp_compiler = matches.value_of("CPPCOMPILER").unwrap();
+ let c_runner = matches.value_of("RUNNER").unwrap_or("");
+ let skip = if let Some(filename) = matches.value_of("SKIP") {
+ let data = std::fs::read_to_string(&filename).expect("Failed to open file");
+ data.lines()
+ .map(str::trim)
+ .filter(|s| !s.contains('#'))
+ .map(String::from)
+ .collect_vec()
+ } else {
+ Default::default()
+ };
+ let a32 = matches.is_present("A32");
+
+ let intrinsics = get_acle_intrinsics(filename);
+
+ let mut intrinsics = intrinsics
+ .into_iter()
+ // Not sure how we would compare intrinsic that returns void.
+ .filter(|i| i.results.kind() != TypeKind::Void)
+ .filter(|i| i.results.kind() != TypeKind::BFloat)
+ .filter(|i| !(i.results.kind() == TypeKind::Float && i.results.inner_size() == 16))
+ .filter(|i| !i.arguments.iter().any(|a| a.ty.kind() == TypeKind::BFloat))
+ .filter(|i| {
+ !i.arguments
+ .iter()
+ .any(|a| a.ty.kind() == TypeKind::Float && a.ty.inner_size() == 16)
+ })
+ // 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()))
+ .filter(|i| !i.arguments.iter().any(|a| a.ty.inner_size() == 128))
+ .filter(|i| !skip.contains(&i.name))
+ .filter(|i| !(a32 && i.a64_only))
+ .collect::<Vec<_>>();
+ intrinsics.dedup();
+
+ if !build_c(&intrinsics, cpp_compiler, a32) {
+ std::process::exit(2);
+ }
+
+ if !build_rust(&intrinsics, &toolchain, a32) {
+ std::process::exit(3);
+ }
+
+ if !compare_outputs(&intrinsics, &toolchain, &c_runner, a32) {
+ std::process::exit(1)
+ }
+}
+
+enum FailureReason {
+ RunC(String),
+ RunRust(String),
+ Difference(String, String, String),
+}
+
+fn compare_outputs(intrinsics: &Vec<Intrinsic>, toolchain: &str, runner: &str, a32: bool) -> bool {
+ let intrinsics = intrinsics
+ .par_iter()
+ .filter_map(|intrinsic| {
+ let c = Command::new("sh")
+ .arg("-c")
+ .arg(format!(
+ "{runner} ./c_programs/{intrinsic}",
+ runner = runner,
+ intrinsic = intrinsic.name,
+ ))
+ .output();
+ let rust = Command::new("sh")
+ .current_dir("rust_programs")
+ .arg("-c")
+ .arg(format!(
+ "cargo {toolchain} run --target {target} --bin {intrinsic}",
+ intrinsic = intrinsic.name,
+ toolchain = toolchain,
+ target = if a32 {
+ "armv7-unknown-linux-gnueabihf"
+ } else {
+ "aarch64-unknown-linux-gnu"
+ },
+ ))
+ .env("RUSTFLAGS", "-Cdebuginfo=0")
+ .output();
+
+ let (c, rust) = match (c, rust) {
+ (Ok(c), Ok(rust)) => (c, rust),
+ a => panic!("{:#?}", a),
+ };
+
+ if !c.status.success() {
+ error!("Failed to run C program for intrinsic {}", intrinsic.name);
+ return Some(FailureReason::RunC(intrinsic.name.clone()));
+ }
+
+ if !rust.status.success() {
+ error!(
+ "Failed to run rust program for intrinsic {}",
+ intrinsic.name
+ );
+ return Some(FailureReason::RunRust(intrinsic.name.clone()));
+ }
+
+ info!("Comparing intrinsic: {}", intrinsic.name);
+
+ let c = std::str::from_utf8(&c.stdout)
+ .unwrap()
+ .to_lowercase()
+ .replace("-nan", "nan");
+ let rust = std::str::from_utf8(&rust.stdout)
+ .unwrap()
+ .to_lowercase()
+ .replace("-nan", "nan");
+
+ if c == rust {
+ None
+ } else {
+ Some(FailureReason::Difference(intrinsic.name.clone(), c, rust))
+ }
+ })
+ .collect::<Vec<_>>();
+
+ intrinsics.iter().for_each(|reason| match reason {
+ FailureReason::Difference(intrinsic, c, rust) => {
+ println!("Difference for intrinsic: {}", intrinsic);
+ let diff = diff::lines(c, rust);
+ diff.iter().for_each(|diff| match diff {
+ diff::Result::Left(c) => println!("C: {}", c),
+ diff::Result::Right(rust) => println!("Rust: {}", rust),
+ diff::Result::Both(_, _) => (),
+ });
+ println!("****************************************************************");
+ }
+ FailureReason::RunC(intrinsic) => {
+ println!("Failed to run C program for intrinsic {}", intrinsic)
+ }
+ FailureReason::RunRust(intrinsic) => {
+ println!("Failed to run rust program for intrinsic {}", intrinsic)
+ }
+ });
+ println!("{} differences found", intrinsics.len());
+ intrinsics.is_empty()
+}
diff --git a/library/stdarch/crates/intrinsic-test/src/types.rs b/library/stdarch/crates/intrinsic-test/src/types.rs
new file mode 100644
index 000000000..e51e61649
--- /dev/null
+++ b/library/stdarch/crates/intrinsic-test/src/types.rs
@@ -0,0 +1,436 @@
+use std::fmt;
+use std::str::FromStr;
+
+use crate::values::values_for_pass;
+use crate::Language;
+
+#[derive(Debug, PartialEq, Copy, Clone)]
+pub enum TypeKind {
+ BFloat,
+ Float,
+ Int,
+ UInt,
+ Poly,
+ Void,
+}
+
+impl FromStr for TypeKind {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "bfloat" => Ok(Self::BFloat),
+ "float" => Ok(Self::Float),
+ "int" => Ok(Self::Int),
+ "poly" => Ok(Self::Poly),
+ "uint" | "unsigned" => Ok(Self::UInt),
+ "void" => Ok(Self::Void),
+ _ => Err(format!("Impossible to parse argument kind {}", s)),
+ }
+ }
+}
+
+impl fmt::Display for TypeKind {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Self::BFloat => "bfloat",
+ Self::Float => "float",
+ Self::Int => "int",
+ Self::UInt => "uint",
+ Self::Poly => "poly",
+ Self::Void => "void",
+ }
+ )
+ }
+}
+
+impl TypeKind {
+ /// Gets the type part of a c typedef for a type that's in the form of {type}{size}_t.
+ pub fn c_prefix(&self) -> &str {
+ match self {
+ Self::Float => "float",
+ Self::Int => "int",
+ Self::UInt => "uint",
+ Self::Poly => "poly",
+ _ => unreachable!("Not used: {:#?}", self),
+ }
+ }
+
+ /// Gets the rust prefix for the type kind i.e. i, u, f.
+ pub fn rust_prefix(&self) -> &str {
+ match self {
+ Self::Float => "f",
+ Self::Int => "i",
+ Self::UInt => "u",
+ Self::Poly => "u",
+ _ => unreachable!("Unused type kind: {:#?}", self),
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub enum IntrinsicType {
+ Ptr {
+ constant: bool,
+ child: Box<IntrinsicType>,
+ },
+ Type {
+ constant: bool,
+ kind: TypeKind,
+ /// The bit length of this type (e.g. 32 for u32).
+ bit_len: Option<u32>,
+
+ /// Length of the SIMD vector (i.e. 4 for uint32x4_t), A value of `None`
+ /// means this is not a simd type. A `None` can be assumed to be 1,
+ /// although in some places a distinction is needed between `u64` and
+ /// `uint64x1_t` this signals that.
+ simd_len: Option<u32>,
+
+ /// The number of rows for SIMD matrices (i.e. 2 for uint8x8x2_t).
+ /// A value of `None` represents a type that does not contain any
+ /// rows encoded in the type (e.g. uint8x8_t).
+ /// A value of `None` can be assumed to be 1 though.
+ vec_len: Option<u32>,
+ },
+}
+
+impl IntrinsicType {
+ /// Get the TypeKind for this type, recursing into pointers.
+ pub fn kind(&self) -> TypeKind {
+ match *self {
+ IntrinsicType::Ptr { ref child, .. } => child.kind(),
+ IntrinsicType::Type { kind, .. } => kind,
+ }
+ }
+
+ /// Get the size of a single element inside this type, recursing into
+ /// pointers, i.e. a pointer to a u16 would be 16 rather than the size
+ /// of a pointer.
+ pub fn inner_size(&self) -> u32 {
+ match *self {
+ IntrinsicType::Ptr { ref child, .. } => child.inner_size(),
+ IntrinsicType::Type {
+ bit_len: Some(bl), ..
+ } => bl,
+ _ => unreachable!(""),
+ }
+ }
+
+ pub fn num_lanes(&self) -> u32 {
+ match *self {
+ IntrinsicType::Ptr { ref child, .. } => child.num_lanes(),
+ IntrinsicType::Type {
+ simd_len: Some(sl), ..
+ } => sl,
+ _ => 1,
+ }
+ }
+
+ pub fn num_vectors(&self) -> u32 {
+ match *self {
+ IntrinsicType::Ptr { ref child, .. } => child.num_vectors(),
+ IntrinsicType::Type {
+ vec_len: Some(vl), ..
+ } => vl,
+ _ => 1,
+ }
+ }
+
+ /// Determine if the type is a simd type, this will treat a type such as
+ /// `uint64x1` as simd.
+ pub fn is_simd(&self) -> bool {
+ match *self {
+ IntrinsicType::Ptr { ref child, .. } => child.is_simd(),
+ IntrinsicType::Type {
+ simd_len: None,
+ vec_len: None,
+ ..
+ } => false,
+ _ => true,
+ }
+ }
+
+ pub fn is_ptr(&self) -> bool {
+ match *self {
+ IntrinsicType::Ptr { .. } => true,
+ IntrinsicType::Type { .. } => false,
+ }
+ }
+
+ #[allow(unused)]
+ fn c_scalar_type(&self) -> String {
+ format!(
+ "{prefix}{bits}_t",
+ prefix = self.kind().c_prefix(),
+ bits = self.inner_size()
+ )
+ }
+
+ fn rust_scalar_type(&self) -> String {
+ format!(
+ "{prefix}{bits}",
+ prefix = self.kind().rust_prefix(),
+ bits = self.inner_size()
+ )
+ }
+
+ /// Gets a string containing the typename for this type in C format.
+ pub fn c_type(&self) -> String {
+ match self {
+ IntrinsicType::Ptr { child, .. } => child.c_type(),
+ IntrinsicType::Type {
+ constant,
+ kind,
+ bit_len: Some(bit_len),
+ simd_len: None,
+ vec_len: None,
+ ..
+ } => format!(
+ "{}{}{}_t",
+ if *constant { "const " } else { "" },
+ kind.c_prefix(),
+ bit_len
+ ),
+ IntrinsicType::Type {
+ kind,
+ bit_len: Some(bit_len),
+ simd_len: Some(simd_len),
+ vec_len: None,
+ ..
+ } => format!("{}{}x{}_t", kind.c_prefix(), bit_len, simd_len),
+ IntrinsicType::Type {
+ kind,
+ bit_len: Some(bit_len),
+ simd_len: Some(simd_len),
+ vec_len: Some(vec_len),
+ ..
+ } => format!("{}{}x{}x{}_t", kind.c_prefix(), bit_len, simd_len, vec_len),
+ _ => todo!("{:#?}", self),
+ }
+ }
+
+ pub fn c_single_vector_type(&self) -> String {
+ match self {
+ IntrinsicType::Ptr { child, .. } => child.c_single_vector_type(),
+ IntrinsicType::Type {
+ kind,
+ bit_len: Some(bit_len),
+ simd_len: Some(simd_len),
+ vec_len: Some(_),
+ ..
+ } => format!("{}{}x{}_t", kind.c_prefix(), bit_len, simd_len),
+ _ => unreachable!("Shouldn't be called on this type"),
+ }
+ }
+
+ pub fn rust_type(&self) -> String {
+ match self {
+ IntrinsicType::Ptr { child, .. } => child.c_type(),
+ IntrinsicType::Type {
+ kind,
+ bit_len: Some(bit_len),
+ simd_len: None,
+ vec_len: None,
+ ..
+ } => format!("{}{}", kind.rust_prefix(), bit_len),
+ IntrinsicType::Type {
+ kind,
+ bit_len: Some(bit_len),
+ simd_len: Some(simd_len),
+ vec_len: None,
+ ..
+ } => format!("{}{}x{}_t", kind.c_prefix(), bit_len, simd_len),
+ IntrinsicType::Type {
+ kind,
+ bit_len: Some(bit_len),
+ simd_len: Some(simd_len),
+ vec_len: Some(vec_len),
+ ..
+ } => format!("{}{}x{}x{}_t", kind.c_prefix(), bit_len, simd_len, vec_len),
+ _ => todo!("{:#?}", self),
+ }
+ }
+
+ /// Gets a cast for this type if needs promotion.
+ /// This is required for 8 bit types due to printing as the 8 bit types use
+ /// a char and when using that in `std::cout` it will print as a character,
+ /// which means value of 0 will be printed as a null byte.
+ ///
+ /// This is also needed for polynomial types because we want them to be
+ /// printed as unsigned integers to match Rust's `Debug` impl.
+ pub fn c_promotion(&self) -> &str {
+ match *self {
+ IntrinsicType::Type {
+ kind,
+ bit_len: Some(bit_len),
+ ..
+ } if bit_len == 8 => match kind {
+ TypeKind::Int => "(int)",
+ TypeKind::UInt => "(unsigned int)",
+ TypeKind::Poly => "(unsigned int)(uint8_t)",
+ _ => "",
+ },
+ IntrinsicType::Type {
+ kind: TypeKind::Poly,
+ bit_len: Some(bit_len),
+ ..
+ } => match bit_len {
+ 8 => unreachable!("handled above"),
+ 16 => "(uint16_t)",
+ 32 => "(uint32_t)",
+ 64 => "(uint64_t)",
+ 128 => "",
+ _ => panic!("invalid bit_len"),
+ },
+ _ => "",
+ }
+ }
+
+ /// Generates a comma list of values that can be used to initialize an
+ /// argument for the intrinsic call.
+ /// This is determistic based on the pass number.
+ ///
+ /// * `pass`: The pass index, i.e. the iteration index for the call to an intrinsic
+ ///
+ /// Returns a string such as
+ /// * `0x1, 0x7F, 0xFF` if `language` is `Language::C`
+ /// * `0x1 as _, 0x7F as _, 0xFF as _` if `language` is `Language::Rust`
+ pub fn populate_random(&self, pass: usize, language: &Language) -> String {
+ match self {
+ IntrinsicType::Ptr { child, .. } => child.populate_random(pass, language),
+ IntrinsicType::Type {
+ bit_len: Some(bit_len),
+ kind,
+ simd_len,
+ vec_len,
+ ..
+ } if kind == &TypeKind::Int || kind == &TypeKind::UInt || kind == &TypeKind::Poly => (0
+ ..(simd_len.unwrap_or(1) * vec_len.unwrap_or(1)))
+ .map(|i| {
+ format!(
+ "{}{}",
+ values_for_pass(*bit_len, i, pass),
+ match language {
+ &Language::Rust => format!(" as {ty} ", ty = self.rust_scalar_type()),
+ &Language::C => String::from(""),
+ }
+ )
+ })
+ .collect::<Vec<_>>()
+ .join(","),
+ IntrinsicType::Type {
+ kind: TypeKind::Float,
+ bit_len: Some(32),
+ simd_len,
+ vec_len,
+ ..
+ } => (0..(simd_len.unwrap_or(1) * vec_len.unwrap_or(1)))
+ .map(|i| {
+ format!(
+ "{}({})",
+ match language {
+ &Language::Rust => "f32::from_bits",
+ &Language::C => "cast<float, uint32_t>",
+ },
+ values_for_pass(32, i, pass),
+ )
+ })
+ .collect::<Vec<_>>()
+ .join(","),
+ IntrinsicType::Type {
+ kind: TypeKind::Float,
+ bit_len: Some(64),
+ simd_len,
+ vec_len,
+ ..
+ } => (0..(simd_len.unwrap_or(1) * vec_len.unwrap_or(1)))
+ .map(|i| {
+ format!(
+ "{}({}{})",
+ match language {
+ &Language::Rust => "f64::from_bits",
+ &Language::C => "cast<double, uint64_t>",
+ },
+ values_for_pass(64, i, pass),
+ match language {
+ &Language::Rust => " as u64",
+ &Language::C => "",
+ }
+ )
+ })
+ .collect::<Vec<_>>()
+ .join(","),
+ _ => unreachable!("populate random: {:#?}", self),
+ }
+ }
+
+ /// Determines the load function for this type.
+ #[allow(unused)]
+ pub fn get_load_function(&self) -> String {
+ match self {
+ IntrinsicType::Ptr { child, .. } => child.get_load_function(),
+ IntrinsicType::Type {
+ kind: k,
+ bit_len: Some(bl),
+ simd_len,
+ vec_len,
+ ..
+ } => {
+ let quad = if (simd_len.unwrap_or(1) * bl) > 64 {
+ "q"
+ } else {
+ ""
+ };
+ format!(
+ "vld{len}{quad}_{type}{size}",
+ type = match k {
+ TypeKind::UInt => "u",
+ TypeKind::Int => "s",
+ TypeKind::Float => "f",
+ TypeKind::Poly => "p",
+ x => todo!("get_load_function TypeKind: {:#?}", x),
+ },
+ size = bl,
+ quad = quad,
+ len = vec_len.unwrap_or(1),
+ )
+ }
+ _ => todo!("get_load_function IntrinsicType: {:#?}", self),
+ }
+ }
+
+ /// Determines the get lane function for this type.
+ pub fn get_lane_function(&self) -> String {
+ match self {
+ IntrinsicType::Ptr { child, .. } => child.get_lane_function(),
+ IntrinsicType::Type {
+ kind: k,
+ bit_len: Some(bl),
+ simd_len,
+ ..
+ } => {
+ let quad = if (simd_len.unwrap_or(1) * bl) > 64 {
+ "q"
+ } else {
+ ""
+ };
+ format!(
+ "vget{quad}_lane_{type}{size}",
+ type = match k {
+ TypeKind::UInt => "u",
+ TypeKind::Int => "s",
+ TypeKind::Float => "f",
+ TypeKind::Poly => "p",
+ x => todo!("get_load_function TypeKind: {:#?}", x),
+ },
+ size = bl,
+ quad = quad,
+ )
+ }
+ _ => todo!("get_lane_function IntrinsicType: {:#?}", self),
+ }
+ }
+}
diff --git a/library/stdarch/crates/intrinsic-test/src/values.rs b/library/stdarch/crates/intrinsic-test/src/values.rs
new file mode 100644
index 000000000..4565edca0
--- /dev/null
+++ b/library/stdarch/crates/intrinsic-test/src/values.rs
@@ -0,0 +1,126 @@
+/// Gets a hex constant value for a single lane in in a determistic way
+/// * `bits`: The number of bits for the type, only 8, 16, 32, 64 are valid values
+/// * `simd`: The index of the simd lane we are generating for
+/// * `pass`: The index of the pass we are generating the values for
+pub fn values_for_pass(bits: u32, simd: u32, pass: usize) -> String {
+ let index = pass + (simd as usize);
+
+ if bits == 8 {
+ format!("{:#X}", VALUES_8[index % VALUES_8.len()])
+ } else if bits == 16 {
+ format!("{:#X}", VALUES_16[index % VALUES_16.len()])
+ } else if bits == 32 {
+ format!("{:#X}", VALUES_32[index % VALUES_32.len()])
+ } else if bits == 64 {
+ format!("{:#X}", VALUES_64[index % VALUES_64.len()])
+ } else {
+ panic!("Unknown size: {}", bits);
+ }
+}
+
+pub const VALUES_8: &[u8] = &[
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0xf0, 0x80, 0x3b, 0xff,
+];
+
+pub const VALUES_16: &[u16] = &[
+ 0x0000, // 0.0
+ 0x0400, // The smallest normal value.
+ 0x37ff, // The value just below 0.5.
+ 0x3800, // 0.5
+ 0x3801, // The value just above 0.5.
+ 0x3bff, // The value just below 1.0.
+ 0x3c00, // 1.0
+ 0x3c01, // The value just above 1.0.
+ 0x3e00, // 1.5
+ 0x4900, // 10
+ 0x7bff, // The largest finite value.
+ 0x7c00, // Infinity.
+ // NaNs.
+ // - Quiet NaNs
+ 0x7f23, 0x7e00, // - Signalling NaNs
+ 0x7d23, 0x7c01, // Subnormals.
+ // - A recognisable bit pattern.
+ 0x0012, // - The largest subnormal value.
+ 0x03ff, // - The smallest subnormal value.
+ 0x0001, // The same values again, but negated.
+ 0x8000, 0x8400, 0xb7ff, 0xb800, 0xb801, 0xbbff, 0xbc00, 0xbc01, 0xbe00, 0xc900, 0xfbff, 0xfc00,
+ 0xff23, 0xfe00, 0xfd23, 0xfc01, 0x8012, 0x83ff, 0x8001,
+];
+
+pub const VALUES_32: &[u32] = &[
+ // Simple values.
+ 0x00000000, // 0.0
+ 0x00800000, // The smallest normal value.
+ 0x3effffff, // The value just below 0.5.
+ 0x3f000000, // 0.5
+ 0x3f000001, // The value just above 0.5.
+ 0x3f7fffff, // The value just below 1.0.
+ 0x3f800000, // 1.0
+ 0x3f800001, // The value just above 1.0.
+ 0x3fc00000, // 1.5
+ 0x41200000, // 10
+ 0x7f8fffff, // The largest finite value.
+ 0x7f800000, // Infinity.
+ // NaNs.
+ // - Quiet NaNs
+ 0x7fd23456, 0x7fc00000, // - Signalling NaNs
+ 0x7f923456, 0x7f800001, // Subnormals.
+ // - A recognisable bit pattern.
+ 0x00123456, // - The largest subnormal value.
+ 0x007fffff, // - The smallest subnormal value.
+ 0x00000001, // The same values again, but negated.
+ 0x80000000, 0x80800000, 0xbeffffff, 0xbf000000, 0xbf000001, 0xbf7fffff, 0xbf800000, 0xbf800001,
+ 0xbfc00000, 0xc1200000, 0xff8fffff, 0xff800000, 0xffd23456, 0xffc00000, 0xff923456, 0xff800001,
+ 0x80123456, 0x807fffff, 0x80000001,
+];
+
+pub const VALUES_64: &[u64] = &[
+ // Simple values.
+ 0x0000000000000000, // 0.0
+ 0x0010000000000000, // The smallest normal value.
+ 0x3fdfffffffffffff, // The value just below 0.5.
+ 0x3fe0000000000000, // 0.5
+ 0x3fe0000000000001, // The value just above 0.5.
+ 0x3fefffffffffffff, // The value just below 1.0.
+ 0x3ff0000000000000, // 1.0
+ 0x3ff0000000000001, // The value just above 1.0.
+ 0x3ff8000000000000, // 1.5
+ 0x4024000000000000, // 10
+ 0x7fefffffffffffff, // The largest finite value.
+ 0x7ff0000000000000, // Infinity.
+ // NaNs.
+ // - Quiet NaNs
+ 0x7ff923456789abcd,
+ 0x7ff8000000000000,
+ // - Signalling NaNs
+ 0x7ff123456789abcd,
+ 0x7ff0000000000000,
+ // Subnormals.
+ // - A recognisable bit pattern.
+ 0x000123456789abcd,
+ // - The largest subnormal value.
+ 0x000fffffffffffff,
+ // - The smallest subnormal value.
+ 0x0000000000000001,
+ // The same values again, but negated.
+ 0x8000000000000000,
+ 0x8010000000000000,
+ 0xbfdfffffffffffff,
+ 0xbfe0000000000000,
+ 0xbfe0000000000001,
+ 0xbfefffffffffffff,
+ 0xbff0000000000000,
+ 0xbff0000000000001,
+ 0xbff8000000000000,
+ 0xc024000000000000,
+ 0xffefffffffffffff,
+ 0xfff0000000000000,
+ 0xfff923456789abcd,
+ 0xfff8000000000000,
+ 0xfff123456789abcd,
+ 0xfff0000000000000,
+ 0x800123456789abcd,
+ 0x800fffffffffffff,
+ 0x8000000000000001,
+];