Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 19 additions & 25 deletions fearless_simd_gen/src/arch/fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@

#![expect(
clippy::match_single_binding,
unreachable_pub,
reason = "TODO: https://github.com/linebender/fearless_simd/issues/40"
)]

use crate::arch::Arch;
use crate::types::{ScalarType, VecType};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
Expand Down Expand Up @@ -64,35 +62,31 @@ pub(crate) fn translate_op(op: &str, is_float: bool) -> Option<&'static str> {
})
}

pub fn simple_intrinsic(name: &str, ty: &VecType) -> TokenStream {
let ty_prefix = Fallback.arch_ty(ty);
pub(crate) fn simple_intrinsic(name: &str, ty: &VecType) -> TokenStream {
let ty_prefix = arch_ty(ty);
let ident = Ident::new(name, Span::call_site());

quote! {#ty_prefix::#ident}
}

pub struct Fallback;

impl Arch for Fallback {
fn arch_ty(&self, ty: &VecType) -> TokenStream {
let scalar = match ty.scalar {
ScalarType::Float => "f",
ScalarType::Unsigned => "u",
ScalarType::Int | ScalarType::Mask => "i",
};
let name = format!("{}{}", scalar, ty.scalar_bits);
let ident = Ident::new(&name, Span::call_site());
quote! { #ident }
}
pub(crate) fn arch_ty(ty: &VecType) -> TokenStream {
let scalar = match ty.scalar {
ScalarType::Float => "f",
ScalarType::Unsigned => "u",
ScalarType::Int | ScalarType::Mask => "i",
};
let name = format!("{}{}", scalar, ty.scalar_bits);
let ident = Ident::new(&name, Span::call_site());
quote! { #ident }
}

fn expr(&self, op: &str, ty: &VecType, args: &[TokenStream]) -> TokenStream {
if let Some(translated) = translate_op(op, ty.scalar == ScalarType::Float) {
let intrinsic = simple_intrinsic(translated, ty);
quote! { #intrinsic ( #( #args ),* ) }
} else {
match op {
_ => unimplemented!("missing {op}"),
}
pub(crate) fn expr(op: &str, ty: &VecType, args: &[TokenStream]) -> TokenStream {
if let Some(translated) = translate_op(op, ty.scalar == ScalarType::Float) {
let intrinsic = simple_intrinsic(translated, ty);
quote! { #intrinsic ( #( #args ),* ) }
} else {
match op {
_ => unimplemented!("missing {op}"),
}
}
}
9 changes: 0 additions & 9 deletions fearless_simd_gen/src/arch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,3 @@ pub(crate) mod fallback;
pub(crate) mod neon;
pub(crate) mod wasm;
pub(crate) mod x86;

use proc_macro2::TokenStream;

use crate::types::VecType;

pub(crate) trait Arch {
fn arch_ty(&self, ty: &VecType) -> TokenStream;
fn expr(&self, op: &str, ty: &VecType, args: &[TokenStream]) -> TokenStream;
}
98 changes: 44 additions & 54 deletions fearless_simd_gen/src/arch/neon.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
// Copyright 2025 the Fearless_SIMD Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

#![expect(
unreachable_pub,
reason = "TODO: https://github.com/linebender/fearless_simd/issues/40"
)]

use crate::arch::Arch;
use crate::types::{ScalarType, VecType};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;

pub struct Neon;

fn translate_op(op: &str) -> Option<&'static str> {
Some(match op {
"abs" => "vabs",
Expand Down Expand Up @@ -46,54 +38,52 @@ fn translate_op(op: &str) -> Option<&'static str> {
})
}

impl Arch for Neon {
fn arch_ty(&self, ty: &VecType) -> TokenStream {
let scalar = match ty.scalar {
ScalarType::Float => "float",
ScalarType::Unsigned => "uint",
ScalarType::Int | ScalarType::Mask => "int",
};
let name = if ty.n_bits() == 256 {
format!("{}{}x{}x2_t", scalar, ty.scalar_bits, ty.len / 2)
} else if ty.n_bits() == 512 {
format!("{}{}x{}x4_t", scalar, ty.scalar_bits, ty.len / 4)
} else {
format!("{}{}x{}_t", scalar, ty.scalar_bits, ty.len)
};
let ident = Ident::new(&name, Span::call_site());
quote! { #ident }
}
pub(crate) fn arch_ty(ty: &VecType) -> TokenStream {
let scalar = match ty.scalar {
ScalarType::Float => "float",
ScalarType::Unsigned => "uint",
ScalarType::Int | ScalarType::Mask => "int",
};
let name = if ty.n_bits() == 256 {
format!("{}{}x{}x2_t", scalar, ty.scalar_bits, ty.len / 2)
} else if ty.n_bits() == 512 {
format!("{}{}x{}x4_t", scalar, ty.scalar_bits, ty.len / 4)
} else {
format!("{}{}x{}_t", scalar, ty.scalar_bits, ty.len)
};
let ident = Ident::new(&name, Span::call_site());
quote! { #ident }
}

// expects args and return value in arch dialect
fn expr(&self, op: &str, ty: &VecType, args: &[TokenStream]) -> TokenStream {
// There is no logical NOT for 64-bit, so we need this workaround.
if op == "not" && ty.scalar_bits == 64 && ty.scalar == ScalarType::Mask {
return quote! { vreinterpretq_s64_s32(vmvnq_s32(vreinterpretq_s32_s64(a.into()))) };
}
// expects args and return value in arch dialect
pub(crate) fn expr(op: &str, ty: &VecType, args: &[TokenStream]) -> TokenStream {
// There is no logical NOT for 64-bit, so we need this workaround.
if op == "not" && ty.scalar_bits == 64 && ty.scalar == ScalarType::Mask {
return quote! { vreinterpretq_s64_s32(vmvnq_s32(vreinterpretq_s32_s64(a.into()))) };
}

if let Some(xlat) = translate_op(op) {
let intrinsic = simple_intrinsic(xlat, ty);
return quote! { #intrinsic ( #( #args ),* ) };
if let Some(xlat) = translate_op(op) {
let intrinsic = simple_intrinsic(xlat, ty);
return quote! { #intrinsic ( #( #args ),* ) };
}
match op {
"splat" => {
let intrinsic = split_intrinsic("vdup", "n", ty);
quote! { #intrinsic ( #( #args ),* ) }
}
match op {
"splat" => {
let intrinsic = split_intrinsic("vdup", "n", ty);
quote! { #intrinsic ( #( #args ),* ) }
}
"fract" => {
let to = VecType::new(ScalarType::Int, ty.scalar_bits, ty.len);
let c1 = cvt_intrinsic("vcvt", &to, ty);
let c2 = cvt_intrinsic("vcvt", ty, &to);
let sub = simple_intrinsic("vsub", ty);
quote! {
let c1 = #c1(a.into());
let c2 = #c2(c1);
"fract" => {
let to = VecType::new(ScalarType::Int, ty.scalar_bits, ty.len);
let c1 = cvt_intrinsic("vcvt", &to, ty);
let c2 = cvt_intrinsic("vcvt", ty, &to);
let sub = simple_intrinsic("vsub", ty);
quote! {
let c1 = #c1(a.into());
let c2 = #c2(c1);

#sub(a.into(), c2)
}
#sub(a.into(), c2)
}
_ => unimplemented!("missing {op}"),
}
_ => unimplemented!("missing {op}"),
}
}

Expand All @@ -106,31 +96,31 @@ fn neon_array_type(ty: &VecType) -> (&'static str, &'static str, usize) {
(opt_q(ty), scalar_c, ty.scalar_bits)
}

pub fn opt_q(ty: &VecType) -> &'static str {
pub(crate) fn opt_q(ty: &VecType) -> &'static str {
match ty.n_bits() {
64 => "",
128 => "q",
_ => panic!("unsupported simd width"),
}
}

pub fn simple_intrinsic(name: &str, ty: &VecType) -> Ident {
pub(crate) fn simple_intrinsic(name: &str, ty: &VecType) -> Ident {
let (opt_q, scalar_c, size) = neon_array_type(ty);
Ident::new(
&format!("{name}{opt_q}_{scalar_c}{size}"),
Span::call_site(),
)
}

pub fn split_intrinsic(name: &str, name2: &str, ty: &VecType) -> Ident {
pub(crate) fn split_intrinsic(name: &str, name2: &str, ty: &VecType) -> Ident {
let (opt_q, scalar_c, size) = neon_array_type(ty);
Ident::new(
&format!("{name}{opt_q}_{name2}_{scalar_c}{size}"),
Span::call_site(),
)
}

pub fn cvt_intrinsic(name: &str, to_ty: &VecType, from_ty: &VecType) -> Ident {
pub(crate) fn cvt_intrinsic(name: &str, to_ty: &VecType, from_ty: &VecType) -> Ident {
let (opt_q, from_scalar_c, from_size) = neon_array_type(from_ty);
let (_opt_q, to_scalar_c, to_size) = neon_array_type(to_ty);
Ident::new(
Expand Down
58 changes: 26 additions & 32 deletions fearless_simd_gen/src/arch/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@
#![expect(
clippy::match_single_binding,
clippy::uninlined_format_args,
unreachable_pub,
reason = "TODO: https://github.com/linebender/fearless_simd/issues/40"
)]

use crate::arch::Arch;
use crate::types::{ScalarType, VecType};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;

pub struct Wasm;

fn translate_op(op: &str) -> Option<&'static str> {
Some(match op {
"abs" => "abs",
Expand Down Expand Up @@ -48,7 +44,7 @@ fn translate_op(op: &str) -> Option<&'static str> {
}

fn simple_intrinsic(name: &str, ty: &VecType) -> TokenStream {
let ty_prefix = Wasm.arch_ty(ty);
let ty_prefix = arch_ty(ty);
let ident = Ident::new(name, Span::call_site());
let combined_ident = Ident::new(&format!("{}_{}", ty_prefix, ident), Span::call_site());
quote! { #combined_ident }
Expand All @@ -61,35 +57,33 @@ fn v128_intrinsic(name: &str) -> TokenStream {
quote! { #combined_ident }
}

impl Arch for Wasm {
fn arch_ty(&self, ty: &VecType) -> TokenStream {
let scalar = match ty.scalar {
ScalarType::Float => "f",
ScalarType::Unsigned => "u",
ScalarType::Int | ScalarType::Mask => "i",
};
let name = format!("{}{}x{}", scalar, ty.scalar_bits, ty.len);
let ident = Ident::new(&name, Span::call_site());
quote! { #ident }
}
pub(crate) fn arch_ty(ty: &VecType) -> TokenStream {
let scalar = match ty.scalar {
ScalarType::Float => "f",
ScalarType::Unsigned => "u",
ScalarType::Int | ScalarType::Mask => "i",
};
let name = format!("{}{}x{}", scalar, ty.scalar_bits, ty.len);
let ident = Ident::new(&name, Span::call_site());
quote! { #ident }
}

// expects args and return value in arch dialect
fn expr(&self, op: &str, ty: &VecType, args: &[TokenStream]) -> TokenStream {
if let Some(translated) = translate_op(op) {
let intrinsic = match translated {
"not" => v128_intrinsic(translated),
"and" => v128_intrinsic(translated),
"or" => v128_intrinsic(translated),
"xor" => v128_intrinsic(translated),
_ => simple_intrinsic(translated, ty),
};
// expects args and return value in arch dialect
pub(crate) fn expr(op: &str, ty: &VecType, args: &[TokenStream]) -> TokenStream {
if let Some(translated) = translate_op(op) {
let intrinsic = match translated {
"not" => v128_intrinsic(translated),
"and" => v128_intrinsic(translated),
"or" => v128_intrinsic(translated),
"xor" => v128_intrinsic(translated),
_ => simple_intrinsic(translated, ty),
};

quote! { #intrinsic ( #( #args ),* ) }
} else {
match op {
// Add any special case operations here if needed
_ => unimplemented!("missing {op}"),
}
quote! { #intrinsic ( #( #args ),* ) }
} else {
match op {
// Add any special case operations here if needed
_ => unimplemented!("missing {op}"),
}
}
}
Loading