Update generator to facilitate big endian

This commit is contained in:
James Barford-Evans 2025-01-31 11:14:29 +00:00 committed by Amanieu d'Antras
parent 67468b20ff
commit f64b610918
5 changed files with 564 additions and 104 deletions

View file

@ -0,0 +1,221 @@
use crate::expression::LetVariant;
use crate::wildstring::WildStringPart;
use crate::{
expression::{Expression, IdentifierType},
typekinds::*,
wildstring::WildString,
};
/// Simplifies creating a string that can be used in an Expression, as Expression
/// expects all strings to be `WildString`
fn create_single_wild_string(name: &str) -> WildString {
WildString(vec![WildStringPart::String(name.to_string())])
}
/// Creates an Identifier with name `name` with no wildcards. This, for example,
/// can be used to create variables, function names or arbitrary input. Is is
/// extremely flexible.
pub fn create_symbol_identifier(arbitrary_string: &str) -> Expression {
let identifier_name = create_single_wild_string(arbitrary_string);
Expression::Identifier(identifier_name, IdentifierType::Symbol)
}
/// To compose the simd_shuffle! call we need:
/// - simd_shuffle!(<arg1>, <arg2>, <array>)
///
/// Here we are creating a string version of the `<array>` that can be used as an
/// Expression Identifier
///
/// In textual form `a: int32x4_t` which has 4 lanes would generate:
/// ```
/// [0, 1, 2, 3]
/// ```
fn create_array(lanes: u32, reverse: bool) -> Option<String> {
if reverse {
match lanes {
1 => None, /* Makes no sense to shuffle an array of size 1 */
2 => Some("[1, 0]".to_string()),
3 => Some("[2, 1, 0]".to_string()),
4 => Some("[3, 2, 1, 0]".to_string()),
8 => Some("[7, 6, 5, 4, 3, 2, 1, 0]".to_string()),
16 => Some("[15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]".to_string()),
_ => panic!("Incorrect vector number of vector lanes: {}", lanes),
}
} else {
match lanes {
1 => None, /* Makes no sense to shuffle an array of size 1 */
2 => Some("[0, 1]".to_string()),
3 => Some("[0, 1, 2]".to_string()),
4 => Some("[0, 1, 2, 3]".to_string()),
8 => Some("[0, 1, 2, 3, 4, 5, 6, 7]".to_string()),
16 => Some("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]".to_string()),
_ => panic!("Incorrect vector number of vector lanes: {}", lanes),
}
}
}
/// Creates: `let <variable_name>: <type> = <expression>`
pub fn create_let_variable(
variable_name: &str,
type_kind: &TypeKind,
expression: Expression,
) -> Expression {
let identifier_name = create_single_wild_string(variable_name);
Expression::Let(LetVariant::WithType(
identifier_name,
type_kind.clone(),
Box::new(expression),
))
}
pub fn create_mut_let_variable(
variable_name: &str,
type_kind: &TypeKind,
expression: Expression,
) -> Expression {
let identifier_name = create_single_wild_string(variable_name);
Expression::Let(LetVariant::MutWithType(
identifier_name,
type_kind.clone(),
Box::new(expression),
))
}
pub fn type_has_tuple(type_kind: &TypeKind) -> bool {
if let TypeKind::Vector(vector_type) = type_kind {
vector_type.tuple_size().is_some()
} else {
false
}
}
pub fn make_variable_mutable(variable_name: &str, type_kind: &TypeKind) -> Expression {
let mut_variable = format!(
"let mut {}: {} = {}",
variable_name,
type_kind.to_string(),
variable_name
);
let identifier_name = create_single_wild_string(&mut_variable);
Expression::Identifier(identifier_name, IdentifierType::Symbol)
}
/// For creating shuffle calls, accepts function pointers for formatting for tuple
/// types and types without a tuple
///
/// Example:
///
/// `a: int32x4_t` with formatting function `create_shuffle_call_fmt` creates:
/// ```
/// simd_shuffle!(a, a, [0, 1, 2, 3])
/// ```
///
/// `a: int32x4x2_t` creates:
/// ```
/// a.0 = simd_shuffle!(a.0, a.0, [0, 1, 2, 3])
/// a.1 = simd_shuffle!(a.1, a.1, [0, 1, 2, 3])
/// ```
fn create_shuffle_internal(
variable_name: &String,
type_kind: &TypeKind,
reverse: bool,
fmt_tuple: fn(variable_name: &String, idx: u32, array_lanes: &String) -> String,
fmt: fn(variable_name: &String, type_kind: &TypeKind, array_lanes: &String) -> String,
) -> Option<Expression> {
let TypeKind::Vector(vector_type) = type_kind else {
return None;
};
let lane_count = vector_type.lanes();
let Some(array_lanes) = create_array(lane_count, reverse) else {
return None;
};
let tuple_count = vector_type.tuple_size().map_or_else(|| 0, |t| t.to_int());
if tuple_count > 0 {
let capacity_estimate: usize =
tuple_count as usize * (lane_count as usize + ((variable_name.len() + 2) * 3));
let mut string_builder = String::with_capacity(capacity_estimate);
/* <var_name>.idx = simd_shuffle!(<var_name>.idx, <var_name>.idx, [<indexes>]) */
for idx in 0..tuple_count {
let formatted = fmt_tuple(variable_name, idx, &array_lanes);
string_builder += formatted.as_str();
}
Some(create_symbol_identifier(&string_builder))
} else {
/* Generate a list of shuffles for each tuple */
let expression = fmt(variable_name, type_kind, &array_lanes);
Some(create_symbol_identifier(&expression))
}
}
fn create_assigned_tuple_shuffle_call_fmt(
variable_name: &String,
idx: u32,
array_lanes: &String,
) -> String {
format!(
"{variable_name}.{idx} = simd_shuffle!({variable_name}.{idx}, {variable_name}.{idx}, {array_lanes});\n",
variable_name = variable_name,
idx = idx,
array_lanes = array_lanes
)
}
fn create_assigned_shuffle_call_fmt(
variable_name: &String,
type_kind: &TypeKind,
array_lanes: &String,
) -> String {
format!(
"let {variable_name}: {type_kind} = simd_shuffle!({variable_name}, {variable_name}, {array_lanes})",
type_kind = type_kind.to_string(),
variable_name = variable_name,
array_lanes = array_lanes
)
}
fn create_shuffle_call_fmt(
variable_name: &String,
_type_kind: &TypeKind,
array_lanes: &String,
) -> String {
format!(
"simd_shuffle!({variable_name}, {variable_name}, {array_lanes})",
variable_name = variable_name,
array_lanes = array_lanes
)
}
/// Create a `simd_shuffle!(<...>, [...])` call, where the output is stored
/// in a variable named `variable_name`
pub fn create_assigned_shuffle_call(
variable_name: &String,
type_kind: &TypeKind,
reverse: bool,
) -> Option<Expression> {
create_shuffle_internal(
variable_name,
type_kind,
reverse,
create_assigned_tuple_shuffle_call_fmt,
create_assigned_shuffle_call_fmt,
)
}
/// Create a `simd_shuffle!(<...>, [...])` call
pub fn create_shuffle_call(
variable_name: &String,
type_kind: &TypeKind,
reverse: bool,
) -> Option<Expression> {
create_shuffle_internal(
variable_name,
type_kind,
reverse,
create_assigned_tuple_shuffle_call_fmt,
create_shuffle_call_fmt,
)
}

View file

@ -35,6 +35,10 @@ pub struct GlobalContext {
pub arch_cfgs: Vec<ArchitectureSettings>,
#[serde(default)]
pub uses_neon_types: bool,
/// Should the yaml file automagically generate big endian shuffling
#[serde(default)]
pub auto_big_endian: Option<bool>,
}
/// Context of an intrinsic group

View file

@ -9,6 +9,7 @@ use std::fmt;
use std::str::FromStr;
use crate::intrinsic::Intrinsic;
use crate::wildstring::WildStringPart;
use crate::{
context::{self, Context, VariableType},
intrinsic::{Argument, LLVMLink, StaticDefinition},
@ -29,6 +30,7 @@ pub enum IdentifierType {
pub enum LetVariant {
Basic(WildString, Box<Expression>),
WithType(WildString, TypeKind, Box<Expression>),
MutWithType(WildString, TypeKind, Box<Expression>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -155,9 +157,11 @@ impl Expression {
cl_ptr_ex.pre_build(ctx)?;
arg_exs.iter_mut().try_for_each(|ex| ex.pre_build(ctx))
}
Self::Let(LetVariant::Basic(_, ex) | LetVariant::WithType(_, _, ex)) => {
ex.pre_build(ctx)
}
Self::Let(
LetVariant::Basic(_, ex)
| LetVariant::WithType(_, _, ex)
| LetVariant::MutWithType(_, _, ex),
) => ex.pre_build(ctx),
Self::CastAs(ex, _) => ex.pre_build(ctx),
Self::Multiply(lhs, rhs) | Self::Xor(lhs, rhs) => {
lhs.pre_build(ctx)?;
@ -214,7 +218,8 @@ impl Expression {
Self::Let(variant) => {
let (var_name, ex, ty) = match variant {
LetVariant::Basic(var_name, ex) => (var_name, ex, None),
LetVariant::WithType(var_name, ty, ex) => {
LetVariant::WithType(var_name, ty, ex)
| LetVariant::MutWithType(var_name, ty, ex) => {
if let Some(w) = ty.wildcard() {
ty.populate_wildcard(ctx.local.provide_type_wildcard(w)?)?;
}
@ -285,9 +290,11 @@ impl Expression {
// Nested structures that aren't inherently unsafe, but could contain other expressions
// that might be.
Self::Assign(_var, exp) => exp.requires_unsafe_wrapper(ctx_fn),
Self::Let(LetVariant::Basic(_, exp) | LetVariant::WithType(_, _, exp)) => {
exp.requires_unsafe_wrapper(ctx_fn)
}
Self::Let(
LetVariant::Basic(_, exp)
| LetVariant::WithType(_, _, exp)
| LetVariant::MutWithType(_, _, exp),
) => exp.requires_unsafe_wrapper(ctx_fn),
Self::Array(exps) => exps.iter().any(|exp| exp.requires_unsafe_wrapper(ctx_fn)),
Self::Multiply(lhs, rhs) | Self::Xor(lhs, rhs) => {
lhs.requires_unsafe_wrapper(ctx_fn) || rhs.requires_unsafe_wrapper(ctx_fn)
@ -330,6 +337,32 @@ impl Expression {
}
}
}
/// Determine if an expression is a `static_assert<...>` function call.
pub fn is_static_assert(&self) -> bool {
match self {
Expression::FnCall(fn_call) => match fn_call.0.as_ref() {
Expression::Identifier(wild_string, _) => {
if let WildStringPart::String(function_name) = &wild_string.0[0] {
function_name.starts_with("static_assert")
} else {
false
}
}
_ => panic!("Badly defined function call: {:?}", fn_call),
},
_ => false,
}
}
/// Determine if an espression is a LLVM binding
pub fn is_llvm_link(&self) -> bool {
if let Expression::LLVMLink(_) = self {
true
} else {
false
}
}
}
impl FromStr for Expression {
@ -422,6 +455,10 @@ impl ToTokens for Expression {
let var_ident = format_ident!("{}", var_name.to_string());
tokens.append_all(quote! { let #var_ident: #ty = #exp })
}
Self::Let(LetVariant::MutWithType(var_name, ty, exp)) => {
let var_ident = format_ident!("{}", var_name.to_string());
tokens.append_all(quote! { let mut #var_ident: #ty = #exp })
}
Self::Assign(var_name, exp) => {
/* If we are dereferencing a variable to assign a value \
* the 'format_ident!' macro does not like the asterix */

View file

@ -10,6 +10,10 @@ use std::ops::RangeInclusive;
use std::str::FromStr;
use crate::assert_instr::InstructionAssertionsForBaseType;
use crate::big_endian::{
create_assigned_shuffle_call, create_let_variable, create_mut_let_variable,
create_shuffle_call, create_symbol_identifier, make_variable_mutable, type_has_tuple,
};
use crate::context::{GlobalContext, GroupContext};
use crate::input::{InputSet, InputSetEntry};
use crate::predicate_forms::{DontCareMethod, PredicateForm, PredicationMask, ZeroingMethod};
@ -284,6 +288,7 @@ pub struct Signature {
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>,
@ -493,12 +498,14 @@ impl LLVMLink {
let mut sig_name = ctx.local.signature.name.clone();
sig_name.prepend_str("_");
let argv = self
.arguments
.clone()
.unwrap_or_else(|| ctx.local.signature.arguments.clone());
let mut sig = Signature {
name: sig_name,
arguments: self
.arguments
.clone()
.unwrap_or_else(|| ctx.local.signature.arguments.clone()),
arguments: argv,
return_type: self
.return_type
.clone()
@ -905,6 +912,13 @@ pub struct Intrinsic {
pub base_type: Option<BaseType>,
/// Attributes for the function
pub attr: Option<Vec<Expression>>,
/// Big endian variant for composing, this gets populated internally
#[serde(skip)]
pub big_endian_compose: Vec<Expression>,
/// Big endian sometimes needs the bits inverted from the default reverse
/// to work correctly
#[serde(default)]
pub big_endian_inverse: Option<bool>,
}
impl Intrinsic {
@ -1014,6 +1028,12 @@ impl Intrinsic {
variant.post_build(&mut ctx)?;
/* If we should generate big endian we shall do so. It's possible
* we may not want to in some instances */
if ctx.global.auto_big_endian.unwrap_or(false) {
self.generate_big_endian(&mut variant);
}
if let Some(n_variant_op) = ctx.local.n_variant_op().cloned() {
variant.generate_n_variant(n_variant_op, &mut ctx)
} else {
@ -1021,6 +1041,146 @@ impl Intrinsic {
}
}
/// Add a big endian implementation
fn generate_big_endian(&self, variant: &mut Intrinsic) {
/* We can't always blindly reverse the bits we sometimes need a
* different order - thus this allows us to have the ability to do so
* without having to play codegolf witht the yaml AST */
let should_reverse = {
if let Some(should_reverse) = variant.big_endian_inverse {
should_reverse
} else if variant.compose.len() == 1 {
match &variant.compose[0] {
Expression::FnCall(fn_call) => fn_call.0.to_string() == "transmute",
_ => false,
}
} else {
false
}
};
let mut big_endian_expressions: Vec<Expression> = Vec::new();
/* We cannot assign `a.0 = ` directly to a function parameter so
* need to make them mutable */
for function_parameter in &variant.signature.arguments {
if type_has_tuple(&function_parameter.kind) {
/* We do not want to be creating a `mut` variant if the type
* has one lane. If it has one lane that means it does not need
* shuffling */
if let TypeKind::Vector(vector_type) = &function_parameter.kind {
if vector_type.lanes() == 1 {
continue;
}
}
let mutable_variable = make_variable_mutable(
&function_parameter.name.to_string(),
&function_parameter.kind,
);
big_endian_expressions.push(mutable_variable);
}
}
/* Possibly shuffle the vectors */
for function_parameter in &variant.signature.arguments {
if let Some(shuffle_call) = create_assigned_shuffle_call(
&function_parameter.name.to_string(),
&function_parameter.kind,
should_reverse,
) {
big_endian_expressions.push(shuffle_call);
}
}
if !big_endian_expressions.is_empty() {
Vec::reserve(
&mut variant.big_endian_compose,
big_endian_expressions.len() + variant.compose.len(),
);
let mut expression = &variant.compose[0];
let needs_reordering = expression.is_static_assert() || expression.is_llvm_link();
/* We want to keep the asserts and llvm links at the start of
* the new big_endian_compose vector that we are creating */
if needs_reordering {
let mut expression_idx = 0;
while expression.is_static_assert() || expression.is_llvm_link() {
/* Add static asserts and llvm links to the start of the
* vector */
variant.big_endian_compose.push(expression.clone());
expression_idx += 1;
expression = &variant.compose[expression_idx];
}
/* Add the big endian specific expressions */
variant.big_endian_compose.extend(big_endian_expressions);
/* Add the rest of the expressions */
for i in expression_idx..variant.compose.len() {
variant.big_endian_compose.push(variant.compose[i].clone());
}
} else {
/* If we do not need to reorder anything then immediately add
* the expressions from the big_endian_expressions and
* concatinate the compose vector */
variant.big_endian_compose.extend(big_endian_expressions);
variant
.big_endian_compose
.extend(variant.compose.iter().cloned());
}
}
/* If we have a return type, there is a possibility we want to generate
* a shuffle call */
if let Some(return_type) = &variant.signature.return_type {
let return_value = variant
.compose
.last()
.expect("Cannot define a return type with an empty function body");
/* If we do not create a shuffle call we do not need modify the
* return value and append to the big endian ast array. A bit confusing
* as in code we are making the final call before caputuring the return
* value of the intrinsic that has been called.*/
let ret_val_name = "ret_val".to_string();
if let Some(simd_shuffle_call) =
create_shuffle_call(&ret_val_name, return_type, should_reverse)
{
/* There is a possibility that the funcion arguments did not
* require big endian treatment, thus we need to now add the
* original function body before appending the return value.*/
if variant.big_endian_compose.is_empty() {
variant
.big_endian_compose
.extend(variant.compose.iter().cloned());
}
/* Now we shuffle the return value - we are creating a new
* return value for the intrinsic. */
let return_value_variable = if type_has_tuple(&return_type) {
create_mut_let_variable(&ret_val_name, return_type, return_value.clone())
} else {
create_let_variable(&ret_val_name, return_type, return_value.clone())
};
/* Remove the last item which will be the return value */
variant.big_endian_compose.pop();
variant.big_endian_compose.push(return_value_variable);
variant.big_endian_compose.push(simd_shuffle_call);
if type_has_tuple(return_type) {
/* We generated `tuple_count` number of calls to shuffle
* re-assigning each tuple however those generated calls do
* not make the parent function return. So we add the return
* value here */
variant
.big_endian_compose
.push(create_symbol_identifier(&ret_val_name));
}
}
}
}
/// Implement a "zeroing" (_z) method by calling an existing "merging" (_m) method, as required.
fn generate_zeroing_pass_through(
&mut self,
@ -1505,120 +1665,157 @@ impl Intrinsic {
}
}
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`");
/// Some intrinsics require a little endian and big endian implementation, others
/// do not
enum Endianness {
Little,
Big,
NA,
}
if let Some(doc) = &self.doc {
let mut doc = vec![doc.to_string()];
/// Based on the endianess will create the appropriate intrinsic, or simply
/// create the desired intrinsic without any endianess
fn create_tokens(intrinsic: &Intrinsic, endianness: Endianness, tokens: &mut TokenStream) {
let signature = &intrinsic.signature;
let fn_name = signature.fn_name().to_string();
let target_feature = intrinsic.target_features.join(",");
let safety = intrinsic
.safety
.as_ref()
.expect("safety should be determined during `pre_build`");
doc.push(format!("[Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/{})", &signature.doc_name()));
if let Some(doc) = &intrinsic.doc {
let mut doc = vec![doc.to_string()];
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"
);
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}"));
}
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."
safety.is_safe(),
"{fn_name} is both public and unsafe, and so needs safety documentation"
);
}
tokens.append_all(quote! { #[inline] });
tokens.append_all(quote! { #(#[doc = #doc])* });
} else {
assert!(
matches!(intrinsic.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."
);
}
/* 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)]
});
}
tokens.append_all(quote! { #[inline] });
/* Target feature will get added here */
let attr_expressions = &mut attr.iter().peekable();
while let Some(ex) = attr_expressions.next() {
let mut inner = TokenStream::new();
ex.to_tokens(&mut inner);
tokens.append(Punct::new('#', Spacing::Alone));
tokens.append(Group::new(Delimiter::Bracket, inner));
}
} else {
match endianness {
Endianness::Little => tokens.append_all(quote! { #[cfg(target_endian = "little")] }),
Endianness::Big => tokens.append_all(quote! { #[cfg(target_endian = "big")] }),
Endianness::NA => {}
};
/* If we have manually defined attributes on the block of yaml with
* 'attr:' we want to add them */
if let Some(attr) = &intrinsic.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)]
});
}
if let Some(assert_instr) = &self.assert_instr {
if !assert_instr.is_empty() {
InstructionAssertionsForBaseType(&assert_instr, &self.base_type.as_ref())
.to_tokens(tokens)
}
/* Target feature will get added here */
let attr_expressions = &mut attr.iter().peekable();
while let Some(ex) = attr_expressions.next() {
let mut inner = TokenStream::new();
ex.to_tokens(&mut inner);
tokens.append(Punct::new('#', Spacing::Alone));
tokens.append(Group::new(Delimiter::Bracket, inner));
}
} else {
tokens.append_all(quote! {
#[target_feature(enable = #target_feature)]
});
}
match &self.visibility {
FunctionVisibility::Public => tokens.append_all(quote! { pub }),
FunctionVisibility::Private => {}
if let Some(assert_instr) = &intrinsic.assert_instr {
if !assert_instr.is_empty() {
InstructionAssertionsForBaseType(&assert_instr, &intrinsic.base_type.as_ref())
.to_tokens(tokens)
}
if safety.is_unsafe() {
tokens.append_all(quote! { unsafe });
}
tokens.append_all(quote! { #signature });
}
// If the intrinsic function is explicitly unsafe, we populate `body_default_safety` with
// the implementation. No explicit unsafe blocks are required.
//
// If the intrinsic is safe, we fill `body_default_safety` until we encounter an expression
// that requires an unsafe wrapper, then switch to `body_unsafe`. Since the unsafe
// operation (e.g. memory access) is typically the last step, this tends to minimises the
// amount of unsafe code required.
let mut body_default_safety = TokenStream::new();
let mut body_unsafe = TokenStream::new();
let mut body_current = &mut body_default_safety;
for (pos, ex) in self.compose.iter().with_position() {
if safety.is_safe() && ex.requires_unsafe_wrapper(&fn_name) {
body_current = &mut body_unsafe;
}
ex.to_tokens(body_current);
let is_last = matches!(pos, itertools::Position::Last | itertools::Position::Only);
let is_llvm_link = matches!(ex, Expression::LLVMLink(_));
if !is_last && !is_llvm_link {
body_current.append(Punct::new(';', Spacing::Alone));
}
}
let mut body = body_default_safety;
if !body_unsafe.is_empty() {
body.append_all(quote! { unsafe { #body_unsafe } });
}
match &intrinsic.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(Group::new(Delimiter::Brace, body));
let expressions = match endianness {
Endianness::Little | Endianness::NA => &intrinsic.compose,
Endianness::Big => &intrinsic.big_endian_compose,
};
tokens.append_all(quote! { #signature });
// If the intrinsic function is explicitly unsafe, we populate `body_default_safety` with
// the implementation. No explicit unsafe blocks are required.
//
// If the intrinsic is safe, we fill `body_default_safety` until we encounter an expression
// that requires an unsafe wrapper, then switch to `body_unsafe`. Since the unsafe
// operation (e.g. memory access) is typically the last step, this tends to minimises the
// amount of unsafe code required.
let mut body_default_safety = TokenStream::new();
let mut body_unsafe = TokenStream::new();
let mut body_current = &mut body_default_safety;
for (pos, ex) in expressions.iter().with_position() {
if safety.is_safe() && ex.requires_unsafe_wrapper(&fn_name) {
body_current = &mut body_unsafe;
}
ex.to_tokens(body_current);
let is_last = matches!(pos, itertools::Position::Last | itertools::Position::Only);
let is_llvm_link = matches!(ex, Expression::LLVMLink(_));
if !is_last && !is_llvm_link {
body_current.append(Punct::new(';', Spacing::Alone));
}
}
let mut body = body_default_safety;
if !body_unsafe.is_empty() {
body.append_all(quote! { unsafe { #body_unsafe } });
}
tokens.append(Group::new(Delimiter::Brace, body));
}
impl ToTokens for Intrinsic {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.big_endian_compose.len() >= 1 {
for i in 0..2 {
match i {
0 => create_tokens(self, Endianness::Little, tokens),
1 => create_tokens(self, Endianness::Big, tokens),
_ => panic!("Currently only little and big endian exist"),
}
}
} else {
create_tokens(self, Endianness::NA, tokens);
}
}
}

View file

@ -1,6 +1,7 @@
#![feature(pattern)]
mod assert_instr;
mod big_endian;
mod context;
mod expression;
mod fn_suffix;