diff options
Diffstat (limited to 'library/stdarch/crates/stdarch-gen-arm/src/intrinsic.rs')
| -rw-r--r-- | library/stdarch/crates/stdarch-gen-arm/src/intrinsic.rs | 1624 |
1 files changed, 1624 insertions, 0 deletions
diff --git a/library/stdarch/crates/stdarch-gen-arm/src/intrinsic.rs b/library/stdarch/crates/stdarch-gen-arm/src/intrinsic.rs new file mode 100644 index 00000000000..4a973691b17 --- /dev/null +++ b/library/stdarch/crates/stdarch-gen-arm/src/intrinsic.rs @@ -0,0 +1,1624 @@ +use itertools::Itertools; +use proc_macro2::{Punct, Spacing, TokenStream}; +use quote::{format_ident, quote, ToTokens, TokenStreamExt}; +use serde::{Deserialize, Serialize}; +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use std::collections::{HashMap, HashSet}; +use std::fmt::{self}; +use std::num::ParseIntError; +use std::ops::RangeInclusive; +use std::str::FromStr; + +use crate::assert_instr::InstructionAssertionsForBaseType; +use crate::context::{GlobalContext, GroupContext}; +use crate::input::{InputSet, InputSetEntry}; +use crate::predicate_forms::{DontCareMethod, PredicateForm, PredicationMask, ZeroingMethod}; +use crate::{ + assert_instr::InstructionAssertionMethod, + context::{self, ArchitectureSettings, Context, LocalContext, VariableType}, + expression::{Expression, FnCall, IdentifierType}, + fn_suffix::{type_to_size, SuffixKind}, + input::IntrinsicInput, + matching::{KindMatchable, SizeMatchable}, + typekinds::*, + wildcards::Wildcard, + wildstring::WildString, +}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SubstitutionType { + MatchSize(SizeMatchable<WildString>), + MatchKind(KindMatchable<WildString>), +} + +impl SubstitutionType { + pub fn get(&mut self, ctx: &LocalContext) -> context::Result<WildString> { + match self { + Self::MatchSize(smws) => { + smws.perform_match(ctx)?; + Ok(smws.as_ref().clone()) + } + Self::MatchKind(kmws) => { + kmws.perform_match(ctx)?; + Ok(kmws.as_ref().clone()) + } + } + } +} + +/// Mutability level +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum AccessLevel { + /// Immutable + R, + /// Mutable + RW, +} + +/// Function signature argument. +/// +/// Prepend the `mut` keyword for a mutable argument. Separate argument name +/// and type with a semicolon `:`. Usage examples: +/// - Mutable argument: `mut arg1: *u64` +/// - Immutable argument: `arg2: u32` +#[derive(Debug, Clone, SerializeDisplay, DeserializeFromStr)] +pub struct Argument { + /// Argument name + pub name: WildString, + /// Mutability level + pub rw: AccessLevel, + /// Argument type + pub kind: TypeKind, +} + +impl Argument { + pub fn populate_variables(&self, vars: &mut HashMap<String, (TypeKind, VariableType)>) { + vars.insert( + self.name.to_string(), + (self.kind.clone(), VariableType::Argument), + ); + } +} + +impl FromStr for Argument { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let mut it = s.splitn(2, ':').map(<str>::trim); + if let Some(mut lhs) = it.next().map(|s| s.split_whitespace()) { + let lhs_len = lhs.clone().count(); + match (lhs_len, lhs.next(), it.next()) { + (2, Some("mut"), Some(kind)) => Ok(Argument { + name: lhs.next().unwrap().parse()?, + rw: AccessLevel::RW, + kind: kind.parse()?, + }), + (2, Some(ident), _) => Err(format!("invalid {ident:#?} keyword")), + (1, Some(name), Some(kind)) => Ok(Argument { + name: name.parse()?, + rw: AccessLevel::R, + kind: kind.parse()?, + }), + _ => Err(format!("invalid argument `{s}` provided")), + } + } else { + Err(format!("invalid argument `{s}` provided")) + } + } +} + +impl fmt::Display for Argument { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let AccessLevel::RW = &self.rw { + write!(f, "mut ")?; + } + + write!(f, "{}: {}", self.name, self.kind) + } +} + +impl ToTokens for Argument { + fn to_tokens(&self, tokens: &mut TokenStream) { + if let AccessLevel::RW = &self.rw { + tokens.append(format_ident!("mut")) + } + + let (name, kind) = (format_ident!("{}", self.name.to_string()), &self.kind); + tokens.append_all(quote! { #name: #kind }) + } +} + +/// Static definition part of the signature. It may evaluate to a constant +/// expression with e.g. `const imm: u64`, or a generic `T: Into<u64>`. +#[derive(Debug, Clone, SerializeDisplay, DeserializeFromStr)] +pub enum StaticDefinition { + /// Constant expression + Constant(Argument), + /// Generic type + Generic(String), +} + +impl StaticDefinition { + pub fn as_variable(&self) -> Option<(String, (TypeKind, VariableType))> { + match self { + StaticDefinition::Constant(arg) => Some(( + arg.name.to_string(), + (arg.kind.clone(), VariableType::Argument), + )), + StaticDefinition::Generic(..) => None, + } + } +} + +impl FromStr for StaticDefinition { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s.trim() { + s if s.starts_with("const ") => Ok(StaticDefinition::Constant(s[6..].trim().parse()?)), + s => Ok(StaticDefinition::Generic(s.to_string())), + } + } +} + +impl fmt::Display for StaticDefinition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + StaticDefinition::Constant(arg) => write!(f, "const {arg}"), + StaticDefinition::Generic(generic) => write!(f, "{generic}"), + } + } +} + +impl ToTokens for StaticDefinition { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(match self { + StaticDefinition::Constant(arg) => quote! { const #arg }, + StaticDefinition::Generic(generic) => { + let generic: TokenStream = generic.parse().expect("invalid Rust code"); + quote! { #generic } + } + }) + } +} + +/// Function constraints +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum Constraint { + /// Asserts that the given variable equals to any of the given integer values + AnyI32 { + variable: String, + any_values: Vec<i32>, + }, + /// WildString version of RangeI32. If the string values given for the range + /// are valid, this gets built into a RangeI32. + RangeWildstring { + variable: String, + range: (WildString, WildString), + }, + /// Asserts that the given variable's value falls in the specified range + RangeI32 { + variable: String, + range: SizeMatchable<RangeInclusive<i32>>, + }, + /// Asserts that the number of elements/lanes does not exceed the 2048-bit SVE constraint + SVEMaxElems { + variable: String, + sve_max_elems_type: TypeKind, + }, + /// Asserts that the number of elements/lanes does not exceed the 128-bit register constraint + VecMaxElems { + variable: String, + vec_max_elems_type: TypeKind, + }, +} + +impl Constraint { + fn variable(&self) -> &str { + match self { + Constraint::AnyI32 { variable, .. } + | Constraint::RangeWildstring { variable, .. } + | Constraint::RangeI32 { variable, .. } + | Constraint::SVEMaxElems { variable, .. } + | Constraint::VecMaxElems { variable, .. } => variable, + } + } + pub fn build(&mut self, ctx: &Context) -> context::Result { + if let Self::RangeWildstring { + variable, + range: (min, max), + } = self + { + min.build_acle(ctx.local)?; + max.build_acle(ctx.local)?; + let min = min.to_string(); + let max = max.to_string(); + let min: i32 = min + .parse() + .map_err(|_| format!("the minimum value `{min}` is not a valid number"))?; + let max: i32 = max + .parse() + .or_else(|_| Ok(type_to_size(max.as_str()))) + .map_err(|_: ParseIntError| { + format!("the maximum value `{max}` is not a valid number") + })?; + *self = Self::RangeI32 { + variable: variable.to_owned(), + range: SizeMatchable::Matched(RangeInclusive::new(min, max)), + } + } + + if let Self::SVEMaxElems { + sve_max_elems_type: ty, + .. + } + | Self::VecMaxElems { + vec_max_elems_type: ty, + .. + } = self + { + if let Some(w) = ty.wildcard() { + ty.populate_wildcard(ctx.local.provide_type_wildcard(w)?)?; + } + } + + if let Self::RangeI32 { range, .. } = self { + range.perform_match(ctx.local)?; + } + + let variable = self.variable(); + ctx.local + .variables + .contains_key(variable) + .then_some(()) + .ok_or_else(|| format!("cannot build constraint, could not find variable {variable}")) + } +} + +/// Function signature +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Signature { + /// Function name + pub name: WildString, + /// List of function arguments, leave unset or empty for no arguments + pub arguments: Vec<Argument>, + /// Function return type, leave unset for void + pub return_type: Option<TypeKind>, + + /// For some neon intrinsics we want to modify the suffix of the function name + pub suffix_type: Option<SuffixKind>, + + /// List of static definitions, leave unset of empty if not required + #[serde(default)] + pub static_defs: Vec<StaticDefinition>, + + /// **Internal use only.** + /// Condition for which the ultimate function is specific to predicates. + #[serde(skip)] + pub is_predicate_specific: bool, + + /// **Internal use only.** + /// Setting this property will trigger the signature builder to convert any `svbool*_t` to `svbool_t` in the input and output. + #[serde(skip)] + pub predicate_needs_conversion: bool, +} + +impl Signature { + pub fn drop_argument(&mut self, arg_name: &WildString) -> Result<(), String> { + if let Some(idx) = self + .arguments + .iter() + .position(|arg| arg.name.to_string() == arg_name.to_string()) + { + self.arguments.remove(idx); + Ok(()) + } else { + Err(format!("no argument {arg_name} found to drop")) + } + } + + pub fn build(&mut self, ctx: &LocalContext) -> context::Result { + if self.name_has_neon_suffix() { + self.name.build_neon_intrinsic_signature(ctx)?; + } else { + self.name.build_acle(ctx)?; + } + + if let Some(ref mut return_type) = self.return_type { + if let Some(w) = return_type.clone().wildcard() { + return_type.populate_wildcard(ctx.provide_type_wildcard(w)?)?; + } + } + + self.arguments + .iter_mut() + .try_for_each(|arg| arg.name.build_acle(ctx))?; + + self.arguments + .iter_mut() + .filter_map(|arg| { + arg.kind + .clone() + .wildcard() + .map(|w| (&mut arg.kind, w.clone())) + }) + .try_for_each(|(ty, w)| ty.populate_wildcard(ctx.provide_type_wildcard(&w)?)) + } + + pub fn fn_name(&self) -> WildString { + self.name.replace(['[', ']'], "") + } + + pub fn doc_name(&self) -> String { + self.name.to_string() + } + + fn name_has_neon_suffix(&self) -> bool { + for part in self.name.wildcards() { + let has_suffix = match part { + Wildcard::NEONType(_, _, suffix_type) => suffix_type.is_some(), + _ => false, + }; + + if has_suffix { + return true; + } + } + false + } +} + +impl ToTokens for Signature { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name_ident = format_ident!("{}", self.fn_name().to_string()); + let arguments = self + .arguments + .clone() + .into_iter() + .map(|mut arg| { + if arg + .kind + .vector() + .map_or(false, |ty| ty.base_type().is_bool()) + && self.predicate_needs_conversion + { + arg.kind = TypeKind::Vector(VectorType::make_predicate_from_bitsize(8)) + } + arg + }) + .collect_vec(); + let static_defs = &self.static_defs; + tokens.append_all(quote! { fn #name_ident<#(#static_defs),*>(#(#arguments),*) }); + + if let Some(ref return_type) = self.return_type { + if return_type + .vector() + .map_or(false, |ty| ty.base_type().is_bool()) + && self.predicate_needs_conversion + { + tokens.append_all(quote! { -> svbool_t }) + } else { + tokens.append_all(quote! { -> #return_type }) + } + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LLVMLinkAttribute { + /// Either one architecture or a comma separated list of architectures with NO spaces + pub arch: String, + pub link: WildString, +} + +impl ToTokens for LLVMLinkAttribute { + fn to_tokens(&self, tokens: &mut TokenStream) { + let LLVMLinkAttribute { arch, link } = self; + let link = link.to_string(); + let archs: Vec<&str> = arch.split(',').collect(); + let arch_len = archs.len(); + + if arch_len == 1 { + tokens.append_all(quote! { + #[cfg_attr(target_arch = #arch, link_name = #link)] + }) + } else { + tokens.append(Punct::new('#', Spacing::Alone)); + tokens.append(Punct::new('[', Spacing::Alone)); + tokens.append_all(quote! { cfg_attr }); + tokens.append(Punct::new('(', Spacing::Alone)); + tokens.append_all(quote! { any }); + tokens.append(Punct::new('(', Spacing::Alone)); + let mut i = 0; + while i < arch_len { + let arch = archs[i].to_string(); + tokens.append_all(quote! { target_arch = #arch }); + if i + 1 != arch_len { + tokens.append(Punct::new(',', Spacing::Alone)); + } + i += 1; + } + tokens.append(Punct::new(')', Spacing::Alone)); + tokens.append(Punct::new(',', Spacing::Alone)); + tokens.append_all(quote! { link_name = #link }); + tokens.append(Punct::new(')', Spacing::Alone)); + tokens.append(Punct::new(']', Spacing::Alone)); + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LLVMLink { + /// LLVM link function name without namespace and types, + /// e.g. `st1` in `llvm.aarch64.sve.st1.nxv4i32` + pub name: WildString, + + /// LLVM link signature arguments, leave unset if it inherits from intrinsic's signature + pub arguments: Option<Vec<Argument>>, + /// LLVM link signature return type, leave unset if it inherits from intrinsic's signature + pub return_type: Option<TypeKind>, + + /// **This will be set automatically if not set** + /// Attribute LLVM links for the function. First element is the architecture it targets, + /// second element is the LLVM link itself. + pub links: Option<Vec<LLVMLinkAttribute>>, + + /// **Internal use only. Do not set.** + /// Generated signature from these `arguments` and/or `return_type` if set, and the intrinsic's signature. + #[serde(skip)] + pub signature: Option<Box<Signature>>, +} + +impl LLVMLink { + pub fn resolve(&self, cfg: &ArchitectureSettings) -> String { + self.name + .starts_with("llvm") + .then(|| self.name.to_string()) + .unwrap_or_else(|| format!("{}.{}", cfg.llvm_link_prefix, self.name)) + } + + pub fn build_and_save(&mut self, ctx: &mut Context) -> context::Result { + self.build(ctx)?; + + // Save LLVM link to the group context + ctx.global.arch_cfgs.iter().for_each(|cfg| { + ctx.group + .links + .insert(self.resolve(cfg), ctx.local.input.clone()); + }); + + Ok(()) + } + + pub fn build(&mut self, ctx: &mut Context) -> context::Result { + let mut sig_name = ctx.local.signature.name.clone(); + sig_name.prepend_str("_"); + + let mut sig = Signature { + name: sig_name, + arguments: self + .arguments + .clone() + .unwrap_or_else(|| ctx.local.signature.arguments.clone()), + return_type: self + .return_type + .clone() + .or_else(|| ctx.local.signature.return_type.clone()), + suffix_type: None, + static_defs: vec![], + is_predicate_specific: ctx.local.signature.is_predicate_specific, + predicate_needs_conversion: false, + }; + + sig.build(ctx.local)?; + self.name.build(ctx.local, TypeRepr::LLVMMachine)?; + + // Add link function name to context + ctx.local + .substitutions + .insert(Wildcard::LLVMLink, sig.fn_name().to_string()); + + self.signature = Some(Box::new(sig)); + + if let Some(ref mut links) = self.links { + links.iter_mut().for_each(|ele| { + ele.link + .build(&ctx.local, TypeRepr::LLVMMachine) + .expect("Failed to transform to LLVMMachine representation"); + }); + } else { + self.links = Some( + ctx.global + .arch_cfgs + .iter() + .map(|cfg| LLVMLinkAttribute { + arch: cfg.arch_name.to_owned(), + link: self.resolve(cfg).into(), + }) + .collect_vec(), + ); + } + + Ok(()) + } + + /// Alters all the unsigned types from the signature, as unsupported by LLVM. + pub fn sanitise_uints(&mut self) { + let transform = |tk: &mut TypeKind| { + if let Some(BaseType::Sized(BaseTypeKind::UInt, size)) = tk.base_type() { + *tk.base_type_mut().unwrap() = BaseType::Sized(BaseTypeKind::Int, *size) + } + }; + + if let Some(sig) = self.signature.as_mut() { + for arg in sig.arguments.iter_mut() { + transform(&mut arg.kind); + } + + sig.return_type.as_mut().map(transform); + } + } + + /// Make a function call to the LLVM link + pub fn make_fn_call(&self, intrinsic_sig: &Signature) -> context::Result<Expression> { + let link_sig = self.signature.as_ref().ok_or_else(|| { + "cannot derive the LLVM link call, as it does not hold a valid function signature" + .to_string() + })?; + + if intrinsic_sig.arguments.len() != link_sig.arguments.len() { + return Err( + "cannot derive the LLVM link call, the number of arguments does not match" + .to_string(), + ); + } + + let call_args = intrinsic_sig + .arguments + .iter() + .zip(link_sig.arguments.iter()) + .map(|(intrinsic_arg, link_arg)| { + // Could also add a type check... + if intrinsic_arg.name == link_arg.name { + Ok(Expression::Identifier( + intrinsic_arg.name.to_owned(), + IdentifierType::Variable, + )) + } else { + Err("cannot derive the LLVM link call, the arguments do not match".to_string()) + } + }) + .try_collect()?; + + Ok(FnCall::new_expression(link_sig.fn_name().into(), call_args)) + } + + /// Given a FnCall, apply all the predicate and unsigned conversions as required. + pub fn apply_conversions_to_call( + &self, + mut fn_call: FnCall, + ctx: &LocalContext, + ) -> context::Result<Expression> { + use BaseType::{Sized, Unsized}; + use BaseTypeKind::{Bool, UInt}; + use VariableType::Argument; + + let convert = + |method: &str, ex| Expression::MethodCall(Box::new(ex), method.to_string(), vec![]); + + fn_call.1 = fn_call + .1 + .into_iter() + .map(|arg| -> context::Result<Expression> { + if let Expression::Identifier(ref var_name, IdentifierType::Variable) = arg { + let (kind, scope) = ctx + .variables + .get(&var_name.to_string()) + .ok_or_else(|| format!("invalid variable {var_name:?} being referenced"))?; + + match (scope, kind.base_type()) { + (Argument, Some(Sized(Bool, bitsize))) if *bitsize != 8 => { + Ok(convert("into", arg)) + } + (Argument, Some(Sized(UInt, _) | Unsized(UInt))) => { + Ok(convert("as_signed", arg)) + } + _ => Ok(arg), + } + } else { + Ok(arg) + } + }) + .try_collect()?; + + let return_type_requires_conversion = self + .signature + .as_ref() + .and_then(|sig| sig.return_type.as_ref()) + .and_then(|ty| { + if let Some(Sized(Bool, bitsize)) = ty.base_type() { + (*bitsize != 8).then_some(Bool) + } else if let Some(Sized(UInt, _) | Unsized(UInt)) = ty.base_type() { + Some(UInt) + } else { + None + } + }); + + let fn_call = Expression::FnCall(fn_call); + match return_type_requires_conversion { + Some(Bool) => Ok(convert("into", fn_call)), + Some(UInt) => Ok(convert("as_unsigned", fn_call)), + _ => Ok(fn_call), + } + } +} + +impl ToTokens for LLVMLink { + fn to_tokens(&self, tokens: &mut TokenStream) { + assert!( + self.signature.is_some() && self.links.is_some(), + "expression {self:#?} was not built before calling to_tokens" + ); + + let signature = self.signature.as_ref().unwrap(); + let links = self.links.as_ref().unwrap(); + tokens.append_all(quote! { + extern "unadjusted" { + #(#links)* + #signature; + } + }) + } +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum FunctionVisibility { + #[default] + Public, + Private, +} + +/// Whether to generate a load/store test, and which typeset index +/// represents the data type of the load/store target address +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum Test { + #[default] + #[serde(skip)] + None, // Covered by `intrinsic-test` + Load(usize), + Store(usize), +} + +impl Test { + pub fn get_typeset_index(&self) -> Option<usize> { + match *self { + Test::Load(n) => Some(n), + Test::Store(n) => Some(n), + _ => None, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum Safety { + Safe, + Unsafe(Vec<UnsafetyComment>), +} + +impl Safety { + /// Return `Ok(Safety::Safe)` if safety appears reasonable for the given `intrinsic`'s name and + /// prototype. Otherwise, return `Err()` with a suitable diagnostic. + fn safe_checked(intrinsic: &Intrinsic) -> Result<Self, String> { + let name = intrinsic.signature.doc_name(); + if name.starts_with("sv") { + let handles_pointers = intrinsic + .signature + .arguments + .iter() + .any(|arg| matches!(arg.kind, TypeKind::Pointer(..))); + if name.starts_with("svld") + || name.starts_with("svst") + || name.starts_with("svprf") + || name.starts_with("svundef") + || handles_pointers + { + let doc = intrinsic.doc.as_ref().map(|s| s.to_string()); + let doc = doc.as_deref().unwrap_or("..."); + Err(format!( + "`{name}` has no safety specification, but it looks like it should be unsafe. \ + Consider specifying (un)safety explicitly: + + - name: {name} + doc: {doc} + safety: + unsafe: + - ... + ... +" + )) + } else { + Ok(Self::Safe) + } + } else { + Err(format!( + "Safety::safe_checked() for non-SVE intrinsic: {name}" + )) + } + } + + fn is_safe(&self) -> bool { + match self { + Self::Safe => true, + Self::Unsafe(..) => false, + } + } + + fn is_unsafe(&self) -> bool { + !self.is_safe() + } + + fn has_doc_comments(&self) -> bool { + match self { + Self::Safe => false, + Self::Unsafe(v) => !v.is_empty(), + } + } + + fn doc_comments(&self) -> &[UnsafetyComment] { + match self { + Self::Safe => &[], + Self::Unsafe(v) => v.as_slice(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum UnsafetyComment { + Custom(String), + Uninitialized, + PointerOffset(GovernedBy), + PointerOffsetVnum(GovernedBy), + Dereference(GovernedBy), + UnpredictableOnFault, + NonTemporal, + Neon, + NoProvenance(String), +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum GovernedBy { + #[default] + Predicated, + PredicatedNonFaulting, + PredicatedFirstFaulting, +} + +impl fmt::Display for GovernedBy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Predicated => write!(f, " (governed by `pg`)"), + Self::PredicatedNonFaulting => write!( + f, + " (governed by `pg`, the first-fault register (`FFR`) \ + and non-faulting behaviour)" + ), + Self::PredicatedFirstFaulting => write!( + f, + " (governed by `pg`, the first-fault register (`FFR`) \ + and first-faulting behaviour)" + ), + } + } +} + +impl fmt::Display for UnsafetyComment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Custom(s) => s.fmt(f), + Self::Neon => write!(f, "Neon instrinsic unsafe"), + Self::Uninitialized => write!( + f, + "This creates an uninitialized value, and may be unsound (like \ + [`core::mem::uninitialized`])." + ), + Self::PointerOffset(gov) => write!( + f, + "[`pointer::offset`](pointer#method.offset) safety constraints must \ + be met for the address calculation for each active element{gov}." + ), + Self::PointerOffsetVnum(gov) => write!( + f, + "[`pointer::offset`](pointer#method.offset) safety constraints must \ + be met for the address calculation for each active element{gov}. \ + In particular, note that `vnum` is scaled by the vector \ + length, `VL`, which is not known at compile time." + ), + Self::Dereference(gov) => write!( + f, + "This dereferences and accesses the calculated address for each \ + active element{gov}." + ), + Self::NonTemporal => write!( + f, + "Non-temporal accesses have special memory ordering rules, and \ + [explicit barriers may be required for some applications]\ + (https://developer.arm.com/documentation/den0024/a/Memory-Ordering/Barriers/Non-temporal-load-and-store-pair?lang=en)." + ), + Self::NoProvenance(arg) => write!( + f, + "Addresses passed in `{arg}` lack provenance, so this is similar to using a \ + `usize as ptr` cast (or [`core::ptr::from_exposed_addr`]) on each lane before \ + using it." + ), + Self::UnpredictableOnFault => write!( + f, + "Result lanes corresponding to inactive FFR lanes (either before or as a result \ + of this intrinsic) have \"CONSTRAINED UNPREDICTABLE\" values, irrespective of \ + predication. Refer to architectural documentation for details." + ), + } + } +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Intrinsic { + #[serde(default)] + pub visibility: FunctionVisibility, + #[serde(default)] + pub doc: Option<WildString>, + #[serde(flatten)] + pub signature: Signature, + /// Function sequential composition + pub compose: Vec<Expression>, + /// Input to generate the intrinsic against. Leave empty if the intrinsic + /// does not have any variants. + /// Specific variants contain one InputSet + #[serde(flatten, default)] + pub input: IntrinsicInput, + #[serde(default)] + pub constraints: Vec<Constraint>, + /// Additional target features to add to the global settings + #[serde(default)] + pub target_features: Vec<String>, + /// Should the intrinsic be `unsafe`? By default, the generator will try to guess from the + /// prototype, but it errs on the side of `unsafe`, and prints a warning in that case. + #[serde(default)] + pub safety: Option<Safety>, + #[serde(default)] + pub substitutions: HashMap<String, SubstitutionType>, + /// List of the only indices in a typeset that require conversion to signed + /// when deferring unsigned intrinsics to signed. (optional, default + /// behaviour is all unsigned types are converted to signed) + #[serde(default)] + pub defer_to_signed_only_indices: HashSet<usize>, + pub assert_instr: Option<Vec<InstructionAssertionMethod>>, + /// Whether we should generate a test for this intrinsic + #[serde(default)] + pub test: Test, + /// Primary base type, used for instruction assertion. + #[serde(skip)] + pub base_type: Option<BaseType>, + /// Attributes for the function + pub attr: Option<Vec<Expression>>, +} + +impl Intrinsic { + pub fn llvm_link(&self) -> Option<&LLVMLink> { + self.compose.iter().find_map(|ex| { + if let Expression::LLVMLink(llvm_link) = ex { + Some(llvm_link) + } else { + None + } + }) + } + + pub fn llvm_link_mut(&mut self) -> Option<&mut LLVMLink> { + self.compose.iter_mut().find_map(|ex| { + if let Expression::LLVMLink(llvm_link) = ex { + Some(llvm_link) + } else { + None + } + }) + } + + pub fn generate_variants(&self, global_ctx: &GlobalContext) -> context::Result<Vec<Intrinsic>> { + let wrap_err = |err| format!("{}: {err}", self.signature.name); + + let mut group_ctx = GroupContext::default(); + self.input + .variants(self) + .map_err(wrap_err)? + .map(|input| { + self.generate_variant(input.clone(), &mut group_ctx, global_ctx) + .map_err(wrap_err) + .map(|variant| (variant, input)) + }) + .collect::<context::Result<Vec<_>>>() + .and_then(|mut variants| { + variants.sort_by_cached_key(|(_, input)| input.to_owned()); + + if variants.is_empty() { + let standalone_variant = self + .generate_variant(InputSet::default(), &mut group_ctx, global_ctx) + .map_err(wrap_err)?; + + Ok(vec![standalone_variant]) + } else { + Ok(variants + .into_iter() + .map(|(variant, _)| variant) + .collect_vec()) + } + }) + } + + pub fn generate_variant( + &self, + input: InputSet, + group_ctx: &mut GroupContext, + global_ctx: &GlobalContext, + ) -> context::Result<Intrinsic> { + let mut variant = self.clone(); + + variant.input.types = vec![InputSetEntry::new(vec![input.clone()])]; + + let mut local_ctx = LocalContext::new(input, self); + let mut ctx = Context { + local: &mut local_ctx, + group: group_ctx, + global: global_ctx, + }; + + variant.pre_build(&mut ctx)?; + + match ctx.local.predicate_form().cloned() { + Some(PredicateForm::DontCare(method)) => { + variant.compose = variant.generate_dont_care_pass_through(&mut ctx, method)? + } + Some(PredicateForm::Zeroing(method)) => { + variant.compose = variant.generate_zeroing_pass_through(&mut ctx, method)? + } + _ => { + for idx in 0..variant.compose.len() { + let mut ex = variant.compose[idx].clone(); + ex.build(&variant, &mut ctx)?; + variant.compose[idx] = ex; + } + } + }; + + if variant.attr.is_none() && variant.assert_instr.is_none() { + panic!("Error: {} is missing both 'attr' and 'assert_instr' fields. You must either manually declare the attributes using the 'attr' field or use 'assert_instr'!", variant.signature.name.to_string()); + } + + if variant.attr.is_some() { + let attr: &Vec<Expression> = &variant.attr.clone().unwrap(); + let mut expanded_attr: Vec<Expression> = Vec::new(); + for idx in 0..attr.len() { + let mut ex = attr[idx].clone(); + ex.build(&variant, &mut ctx)?; + expanded_attr.push(ex); + } + variant.attr = Some(expanded_attr); + } + + variant.post_build(&mut ctx)?; + + if let Some(n_variant_op) = ctx.local.n_variant_op().cloned() { + variant.generate_n_variant(n_variant_op, &mut ctx) + } else { + Ok(variant) + } + } + + /// Implement a "zeroing" (_z) method by calling an existing "merging" (_m) method, as required. + fn generate_zeroing_pass_through( + &mut self, + ctx: &mut Context, + method: ZeroingMethod, + ) -> context::Result<Vec<Expression>> { + PredicationMask::try_from(&ctx.local.signature.name) + .ok() + .filter(|mask| mask.has_merging()) + .ok_or_else(|| format!("cannot generate zeroing passthrough for {}, no merging predicate form is specified", self.signature.name))?; + + // Determine the function to pass through to. + let mut target_ctx = ctx.local.clone(); + // Change target function predicate form to merging + *target_ctx.input.iter_mut() + .find_map(|arg| arg.predicate_form_mut()) + .expect("failed to generate zeroing pass through, could not find predicate form in the InputSet") = PredicateForm::Merging; + + let mut sig = target_ctx.signature.clone(); + sig.build(&target_ctx)?; + + let args_as_expressions = |arg: &Argument| -> context::Result<Expression> { + let arg_name = arg.name.to_string(); + match &method { + ZeroingMethod::Drop { drop } if arg_name == drop.to_string() => { + Ok(PredicateForm::make_zeroinitializer(&arg.kind)) + } + ZeroingMethod::Select { select } if arg_name == select.to_string() => { + let pg = sig + .arguments + .iter() + .find_map(|arg| match arg.kind.vector() { + Some(ty) if ty.base_type().is_bool() => Some(arg.name.clone()), + _ => None, + }) + .ok_or_else(|| { + format!("cannot generate zeroing passthrough for {}, no predicate found in the signature for zero selection", self.signature.name) + })?; + Ok(PredicateForm::make_zeroselector( + pg, + select.clone(), + &arg.kind, + )) + } + _ => Ok(arg.into()), + } + }; + + let name: Expression = sig.fn_name().into(); + let args: Vec<Expression> = sig + .arguments + .iter() + .map(args_as_expressions) + .try_collect()?; + let statics: Vec<Expression> = sig + .static_defs + .iter() + .map(|sd| sd.try_into()) + .try_collect()?; + let mut call: Expression = FnCall(Box::new(name), args, statics).into(); + call.build(self, ctx)?; + Ok(vec![call]) + } + + /// Implement a "don't care" (_x) method by calling an existing "merging" (_m). + fn generate_dont_care_pass_through( + &mut self, + ctx: &mut Context, + method: DontCareMethod, + ) -> context::Result<Vec<Expression>> { + PredicationMask::try_from(&ctx.local.signature.name).and_then(|mask| match method { + DontCareMethod::AsMerging if mask.has_merging() => Ok(()), + DontCareMethod::AsZeroing if mask.has_zeroing() => Ok(()), + _ => Err(format!( + "cannot generate don't care passthrough for {}, no {} predicate form is specified", + self.signature.name, + match method { + DontCareMethod::AsMerging => "merging", + DontCareMethod::AsZeroing => "zeroing", + _ => unreachable!(), + } + )), + })?; + + // Determine the function to pass through to. + let mut target_ctx = ctx.local.clone(); + // Change target function predicate form to merging + *target_ctx.input.iter_mut() + .find_map(|arg| arg.predicate_form_mut()) + .expect("failed to generate don't care passthrough, could not find predicate form in the InputSet") = PredicateForm::Merging; + + let mut sig = target_ctx.signature.clone(); + sig.build(&target_ctx)?; + + // We might need to drop an argument for a zeroing pass-through. + let drop = match (method, &self.input.predication_methods.zeroing_method) { + (DontCareMethod::AsZeroing, Some(ZeroingMethod::Drop { drop })) => Some(drop), + _ => None, + }; + + let name: Expression = sig.fn_name().into(); + let args: Vec<Expression> = sig + .arguments + .iter() + .map(|arg| { + if Some(arg.name.to_string()) == drop.as_ref().map(|v| v.to_string()) { + // This argument is present in the _m form, but missing from the _x form. Clang + // typically replaces these with an uninitialised vector, but to avoid + // materialising uninitialised values in Rust, we instead merge with a known + // vector. This usually results in the same code generation. + // TODO: In many cases, it'll be better to use an unpredicated (or zeroing) form. + sig.arguments + .iter() + .filter(|&other| arg.name.to_string() != other.name.to_string()) + .find_map(|other| { + arg.kind.express_reinterpretation_from(&other.kind, other) + }) + .unwrap_or_else(|| PredicateForm::make_zeroinitializer(&arg.kind)) + } else { + arg.into() + } + }) + .collect(); + let statics: Vec<Expression> = sig + .static_defs + .iter() + .map(|sd| sd.try_into()) + .try_collect()?; + let mut call: Expression = FnCall(Box::new(name), args, statics).into(); + call.build(self, ctx)?; + Ok(vec![call]) + } + + /// Implement a "_n" variant based on the given operand + fn generate_n_variant( + &self, + mut n_variant_op: WildString, + ctx: &mut Context, + ) -> context::Result<Intrinsic> { + let mut variant = self.clone(); + + n_variant_op.build_acle(ctx.local)?; + + let n_op_arg_idx = variant + .signature + .arguments + .iter_mut() + .position(|arg| arg.name.to_string() == n_variant_op.to_string()) + .ok_or_else(|| { + format!( + "cannot generate `_n` variant for {}, operand `{n_variant_op}` not found", + variant.signature.name + ) + })?; + + let has_n_wildcard = ctx + .local + .signature + .name + .wildcards() + .any(|w| matches!(w, Wildcard::NVariant)); + + if !has_n_wildcard { + return Err(format!("cannot generate `_n` variant for {}, no wildcard {{_n}} was specified in the intrinsic's name", variant.signature.name)); + } + + // Build signature + variant.signature = ctx.local.signature.clone(); + if let Some(pf) = ctx.local.predicate_form() { + // WARN: this may break in the future according to the underlying implementation + // Drops unwanted arguments if needed (required for the collection of arguments to pass to the function) + pf.post_build(&mut variant)?; + } + + let sig = &mut variant.signature; + + ctx.local + .substitutions + .insert(Wildcard::NVariant, "_n".to_owned()); + + let arg_kind = &mut sig.arguments.get_mut(n_op_arg_idx).unwrap().kind; + *arg_kind = match arg_kind { + TypeKind::Wildcard(Wildcard::SVEType(idx, None)) => { + TypeKind::Wildcard(Wildcard::Type(*idx)) + } + _ => { + return Err(format!( + "cannot generate `_n` variant for {}, the given operand is not a valid SVE type", + variant.signature.name + )) + } + }; + + sig.build(ctx.local)?; + + // Build compose + let name: Expression = self.signature.fn_name().into(); + let args: Vec<Expression> = sig + .arguments + .iter() + .enumerate() + .map(|(idx, arg)| { + let ty = arg.kind.acle_notation_repr(); + if idx == n_op_arg_idx { + FnCall::new_expression( + WildString::from(format!("svdup_n_{ty}")).into(), + vec![arg.into()], + ) + } else { + arg.into() + } + }) + .collect(); + let statics: Vec<Expression> = sig + .static_defs + .iter() + .map(|sd| sd.try_into()) + .try_collect()?; + let mut call: Expression = FnCall(Box::new(name), args, statics).into(); + call.build(self, ctx)?; + + variant.compose = vec![call]; + variant.signature.predicate_needs_conversion = true; + + Ok(variant) + } + + fn pre_build(&mut self, ctx: &mut Context) -> context::Result { + self.substitutions + .iter_mut() + .try_for_each(|(k, v)| -> context::Result { + let mut ws = v.get(ctx.local)?; + ws.build_acle(ctx.local)?; + ctx.local + .substitutions + .insert(Wildcard::Custom(k.to_owned()), ws.to_string()); + Ok(()) + })?; + + self.signature.build(ctx.local)?; + + if self.safety.is_none() { + self.safety = match Safety::safe_checked(self) { + Ok(safe) => Some(safe), + Err(err) => { + eprintln!("{err}"); + return Err(format!( + "Refusing to infer unsafety for {name}", + name = self.signature.doc_name() + )); + } + } + } + + if let Some(doc) = &mut self.doc { + doc.build_acle(ctx.local)? + } + + // Add arguments to variable tracking + self.signature + .arguments + .iter() + .for_each(|arg| arg.populate_variables(&mut ctx.local.variables)); + + // Add constant expressions to variable tracking + self.signature + .static_defs + .iter() + .filter_map(StaticDefinition::as_variable) + .for_each(|(var_name, var_properties)| { + ctx.local.variables.insert(var_name, var_properties); + }); + + // Pre-build compose expressions + for idx in 0..self.compose.len() { + let mut ex = self.compose[idx].clone(); + ex.pre_build(ctx)?; + self.compose[idx] = ex; + } + + if !ctx.local.input.is_empty() { + // We simplify the LLVM link transmute logic by deferring to a variant employing the same LLVM link where possible + if let Some(link) = self.compose.iter().find_map(|ex| match ex { + Expression::LLVMLink(link) => Some(link), + _ => None, + }) { + let mut link = link.clone(); + link.build(ctx)?; + + for cfg in ctx.global.arch_cfgs.iter() { + let expected_link = link.resolve(cfg); + if let Some(target_inputset) = ctx.group.links.get(&expected_link) { + self.defer_to_existing_llvm_link(ctx.local, target_inputset)?; + break; + } + } + } + } + + if let Some(ref mut assert_instr) = self.assert_instr { + assert_instr.iter_mut().try_for_each(|ai| ai.build(ctx))?; + } + + // Prepend constraint assertions + self.constraints.iter_mut().try_for_each(|c| c.build(ctx))?; + let assertions: Vec<_> = self + .constraints + .iter() + .map(|c| ctx.local.make_assertion_from_constraint(c)) + .try_collect()?; + self.compose.splice(0..0, assertions); + + Ok(()) + } + + fn post_build(&mut self, ctx: &mut Context) -> context::Result { + if let Some(Expression::LLVMLink(link)) = self.compose.last() { + let mut fn_call = link.make_fn_call(&self.signature)?; + // Required to inject conversions + fn_call.build(self, ctx)?; + self.compose.push(fn_call) + } + + if let Some(llvm_link) = self.llvm_link_mut() { + // Turn all Rust unsigned types into signed + llvm_link.sanitise_uints(); + } + + if let Some(predicate_form) = ctx.local.predicate_form() { + predicate_form.post_build(self)? + } + + // Set for ToTokens<Signature> to display a generic svbool_t + self.signature.predicate_needs_conversion = true; + + // Set base type kind for instruction assertion + self.base_type = ctx + .local + .input + .get(0) + .and_then(|arg| arg.typekind()) + .and_then(|ty| ty.base_type()) + .map(BaseType::clone); + + // Add global target features + self.target_features = ctx + .global + .arch_cfgs + .iter() + .flat_map(|cfg| cfg.target_feature.clone()) + .chain(self.target_features.clone()) + .collect_vec(); + + Ok(()) + } + + fn defer_to_existing_llvm_link( + &mut self, + ctx: &LocalContext, + target_inputset: &InputSet, + ) -> context::Result { + let mut target_ctx = ctx.clone(); + target_ctx.input = target_inputset.clone(); + + let mut target_signature = target_ctx.signature.clone(); + target_signature.build(&target_ctx)?; + + let drop_var = if let Some(pred) = ctx.predicate_form().cloned() { + match pred { + PredicateForm::Zeroing(ZeroingMethod::Drop { drop }) => Some(drop), + PredicateForm::DontCare(DontCareMethod::AsZeroing) => { + if let Some(ZeroingMethod::Drop { drop }) = + self.input.predication_methods.zeroing_method.to_owned() + { + Some(drop) + } else { + None + } + } + _ => None, + } + } else { + None + }; + + let call_method = + |ex, method: &str| Expression::MethodCall(Box::new(ex), method.to_string(), vec![]); + let as_unsigned = |ex| call_method(ex, "as_unsigned"); + let as_signed = |ex| call_method(ex, "as_signed"); + let convert_if_required = |w: Option<&Wildcard>, from: &InputSet, to: &InputSet, ex| { + if let Some(w) = w { + if let Some(dest_idx) = w.get_typeset_index() { + let from_type = from.get(dest_idx); + let to_type = to.get(dest_idx); + + if from_type != to_type { + let from_base_type = from_type + .and_then(|in_arg| in_arg.typekind()) + .and_then(|ty| ty.base_type()) + .map(|bt| bt.kind()); + let to_base_type = to_type + .and_then(|in_arg| in_arg.typekind()) + .and_then(|ty| ty.base_type()) + .map(|bt| bt.kind()); + + match (from_base_type, to_base_type) { + // Use AsSigned for uint -> int + (Some(BaseTypeKind::UInt), Some(BaseTypeKind::Int)) => as_signed(ex), + (Some(BaseTypeKind::Int), Some(BaseTypeKind::Int)) => ex, + // Use AsUnsigned for int -> uint + (Some(BaseTypeKind::Int), Some(BaseTypeKind::UInt)) => as_unsigned(ex), + (Some(BaseTypeKind::Float), Some(BaseTypeKind::Float)) => ex, + (Some(BaseTypeKind::UInt), Some(BaseTypeKind::UInt)) => ex, + (Some(BaseTypeKind::Poly), Some(BaseTypeKind::Poly)) => ex, + + (None, None) => ex, + _ => unreachable!("unsupported conversion case from {from_base_type:?} to {to_base_type:?} hit"), + } + } else { + ex + } + } else { + ex + } + } else { + ex + } + }; + + let args = ctx + .signature + .arguments + .iter() + .filter_map(|arg| { + let var = Expression::Identifier(arg.name.to_owned(), IdentifierType::Variable); + if drop_var.as_ref().map(|v| v.to_string()) != Some(arg.name.to_string()) { + Some(convert_if_required( + arg.kind.wildcard(), + &ctx.input, + target_inputset, + var, + )) + } else { + None + } + }) + .collect_vec(); + + let turbofish = self + .signature + .static_defs + .iter() + .map(|def| { + let name = match def { + StaticDefinition::Constant(Argument { name, .. }) => name.to_string(), + StaticDefinition::Generic(name) => name.to_string(), + }; + Expression::Identifier(name.into(), IdentifierType::Symbol) + }) + .collect_vec(); + + let ret_wildcard = ctx + .signature + .return_type + .as_ref() + .and_then(|t| t.wildcard()); + let call = FnCall(Box::new(target_signature.fn_name().into()), args, turbofish).into(); + + self.compose = vec![convert_if_required( + ret_wildcard, + target_inputset, + &ctx.input, + call, + )]; + + Ok(()) + } +} + +impl ToTokens for Intrinsic { + fn to_tokens(&self, tokens: &mut TokenStream) { + let signature = &self.signature; + let fn_name = signature.fn_name().to_string(); + let target_feature = self.target_features.join(","); + let safety = self + .safety + .as_ref() + .expect("safety should be determined during `pre_build`"); + + if let Some(doc) = &self.doc { + let mut doc = vec![doc.to_string()]; + + doc.push(format!("[Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/{})", &signature.doc_name())); + + if safety.has_doc_comments() { + doc.push("## Safety".to_string()); + for comment in safety.doc_comments() { + doc.push(format!(" * {comment}")); + } + } else { + assert!( + safety.is_safe(), + "{fn_name} is both public and unsafe, and so needs safety documentation" + ); + } + + tokens.append_all(quote! { #(#[doc = #doc])* }); + } else { + assert!( + matches!(self.visibility, FunctionVisibility::Private), + "{fn_name} needs to be private, or to have documentation." + ); + assert!( + !safety.has_doc_comments(), + "{fn_name} needs a documentation section for its safety comments." + ); + } + + tokens.append_all(quote! { #[inline] }); + + /* If we have manually defined attributes on the block of yaml with + * 'attr:' we want to add them */ + if let Some(attr) = &self.attr { + /* Scan to see if we have defined `FnCall: [target_feature, ['<bespoke>']]`*/ + if !has_target_feature_attr(attr) { + /* If not add the default one that is defined at the top of + * the yaml file. This does mean we scan the attributes vector + * twice, once to see if the `target_feature` exists and again + * to actually append the tokens. We could impose that the + * `target_feature` call has to be the first argument of the + * `attr` block */ + tokens.append_all(quote! { + #[target_feature(enable = #target_feature)] + }); + } + + /* Target feature will get added here */ + let attr_expressions = &mut attr.iter().peekable(); + while let Some(ex) = attr_expressions.next() { + tokens.append(Punct::new('#', Spacing::Alone)); + tokens.append(Punct::new('[', Spacing::Alone)); + ex.to_tokens(tokens); + tokens.append(Punct::new(']', Spacing::Alone)); + } + } else { + tokens.append_all(quote! { + #[target_feature(enable = #target_feature)] + }); + } + + if let Some(assert_instr) = &self.assert_instr { + if !assert_instr.is_empty() { + InstructionAssertionsForBaseType(&assert_instr, &self.base_type.as_ref()) + .to_tokens(tokens) + } + } + + match &self.visibility { + FunctionVisibility::Public => tokens.append_all(quote! { pub }), + FunctionVisibility::Private => {} + } + if safety.is_unsafe() { + tokens.append_all(quote! { unsafe }); + } + tokens.append_all(quote! { #signature }); + tokens.append(Punct::new('{', Spacing::Alone)); + + let mut body_unsafe = false; + let mut expressions = self.compose.iter().peekable(); + while let Some(ex) = expressions.next() { + if !body_unsafe && safety.is_safe() && ex.requires_unsafe_wrapper(&fn_name) { + body_unsafe = true; + tokens.append_all(quote! { unsafe }); + tokens.append(Punct::new('{', Spacing::Alone)); + } + // If it's not the last and not a LLVM link, add a trailing semicolon + if expressions.peek().is_some() && !matches!(ex, Expression::LLVMLink(_)) { + tokens.append_all(quote! { #ex; }) + } else { + ex.to_tokens(tokens) + } + } + if body_unsafe { + tokens.append(Punct::new('}', Spacing::Alone)); + } + + tokens.append(Punct::new('}', Spacing::Alone)); + tokens.append(Punct::new('\n', Spacing::Alone)); + tokens.append(Punct::new('\n', Spacing::Alone)); + } +} + +fn has_target_feature_attr(attrs: &[Expression]) -> bool { + attrs.iter().any(|attr| { + if let Expression::FnCall(fn_call) = attr { + fn_call.is_target_feature_call() + } else { + false + } + }) +} |
