use std::ops::Range; use crate::types::{IntrinsicType, TypeKind}; 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, } #[derive(Debug, PartialEq, Clone)] pub enum Constraint { Equal(i64), Range(Range), } impl Constraint { pub fn to_range(&self) -> Range { 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, } 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::>() .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::>() .join(", ") } pub fn as_constraint_parameters_rust(&self) -> String { self.args .iter() .filter(|a| a.has_constraint()) .map(|arg| arg.name.clone()) .collect::>() .join(", ") } /// Creates a line for each argument that initializes an array for C from which `loads` argument /// values can be loaded as a sliding window. /// e.g `const int32x2_t a_vals = {0x3effffff, 0x3effffff, 0x3f7fffff}`, if loads=2. pub fn gen_arglists_c(&self, loads: u32) -> String { self.iter() .filter_map(|arg| { (!arg.has_constraint()).then(|| { format!( "const {ty} {name}_vals[] = {{ {values} }};", ty = arg.ty.c_scalar_type(), name = arg.name, values = arg.ty.populate_random(loads, &Language::C) ) }) }) .collect::>() .join("\n") } /// Creates a line for each argument that initializes an array for Rust from which `loads` argument /// values can be loaded as a sliding window, e.g `const A_VALS: [u32; 20] = [...];` pub fn gen_arglists_rust(&self, loads: u32) -> String { self.iter() .filter_map(|arg| { (!arg.has_constraint()).then(|| { format!( "const {upper_name}_VALS: [{ty}; {load_size}] = unsafe{{ [{values}] }};", upper_name = arg.name.to_uppercase(), ty = arg.ty.rust_scalar_type(), load_size = arg.ty.num_lanes() * arg.ty.num_vectors() + loads - 1, values = arg.ty.populate_random(loads, &Language::Rust) ) }) }) .collect::>() .join("\n") } /// Creates a line for each argument that initalizes the argument from an array [arg]_vals at /// an offset i using a load intrinsic, in C. /// e.g `uint8x8_t a = vld1_u8(&a_vals[i]);` pub fn load_values_c(&self, p64_armv7_workaround: bool) -> String { self.iter() .filter_map(|arg| { // The ACLE doesn't support 64-bit polynomial loads on Armv7 // This and the cast are a workaround for this let armv7_p64 = if let TypeKind::Poly = arg.ty.kind() { p64_armv7_workaround } else { false }; (!arg.has_constraint()).then(|| { format!( "{ty} {name} = {open_cast}{load}(&{name}_vals[i]){close_cast};", ty = arg.to_c_type(), name = arg.name, load = if arg.is_simd() { arg.ty.get_load_function(p64_armv7_workaround) } else { "*".to_string() }, open_cast = if armv7_p64 { format!("cast<{}>(", arg.to_c_type()) } else { "".to_string() }, close_cast = if armv7_p64 { ")".to_string() } else { "".to_string() } ) }) }) .collect::>() .join("\n ") } /// Creates a line for each argument that initalizes 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));` pub fn load_values_rust(&self) -> String { self.iter() .filter_map(|arg| { (!arg.has_constraint()).then(|| { format!( "let {name} = {load}({upper_name}_VALS.as_ptr().offset(i));", name = arg.name, upper_name = arg.name.to_uppercase(), load = if arg.is_simd() { arg.ty.get_load_function(false) } else { "*".to_string() }, ) }) }) .collect::>() .join("\n ") } pub fn iter(&self) -> std::slice::Iter<'_, Argument> { self.args.iter() } }