archmage
Guide · Intrinsics browser · Archmage API · Magetypes API
Archmage lets you write SIMD code in Rust without unsafe — your crate can keep #![forbid(unsafe_code)] while calling intrinsics directly.
You pick a CPU tier, prove it's present once with summon(), and the type system keeps every intrinsic call sound. Rust already makes most intrinsics safe inside a function compiled for their CPU features; archmage supplies the proof that the CPU has them.
It runs on x86-64, AArch64 and WASM, is no_std + alloc, and needs Rust 1.89 or later.
0.9.31 beta: unified macros
This prerelease adds #[attune], attuned!, and reattune!. Existing attributes
and incant! remain supported. Use matching beta versions of archmage and
magetypes so their tokens come from the same archmage package.
use *;
// Generate direct tier functions and an ordinary callable dispatcher.
let mut values = ;
scale;
assert_eq!;
#[attune(v3)] adds features to one function; #[attune(wrap)] creates a proof
wrapper using its token parameter. Bare #[attune] infers a direct _v3 or
proof-wrapper _v3_t suffix. A token parameter alone does not imply wrap.
The beta guide
covers the selector grammar, call and proof rules, inline policies, a legacy-attribute
mapping, and current restrictions.
Quick start
[]
= "=0.9.31-beta.1"
= "=0.9.31-beta.1" # vector types such as f32x8
Multiply a buffer by a gain, using AVX2, NEON or WASM SIMD where available:
use *;
let mut plane = ;
apply_gain;
assert_eq!;
#[magetypes]compilesgain_implonce per tier in its list and names each copy for its tier:gain_impl_v3(AVX2 and FMA),gain_impl_neon,gain_impl_wasm128andgain_impl_scalar. In each copy,Tokenis that tier's token type andf32x8is that tier's vector.incant!finds the copies by those names. It callssummon()for each tier, best first, and runs the first copy the CPU supports. Call it around your loop, as here, not inside it.- Constructors ending in
_ttake the token as their first argument.
The kernel is adapted from zenfilters;
Reusable generic kernels
links the production source. If you are upgrading, 0.9.30 deprecates the older
constructor names (splat, load, …): the
migration guide
has the mapping.
Calling intrinsics directly
For code tied to one instruction set, write a function per tier and let
incant! choose:
use *;
assert_eq!;
#[arcane]compiles the function with its token's CPU features and makes it safe to call from ordinary code.import_intrinsicsbrings the architecture's intrinsics into scope. Loads and stores take references instead of raw pointers.- The names do the dispatch.
incant!(multiply(data), [v3, scalar])callsmultiply_v3where the CPU has thev3features andmultiply_scalareverywhere else.
Mark helper functions with #[rite] instead. They get the same features and
inline into the kernel that calls them. The
intrinsics browser shows which
intrinsics each token unlocks.
Naming: <name>_<tier>
incant!(gain_impl(…), [v3, scalar]) calls gain_impl_v3(token, …) or
gain_impl_scalar(token, …). That is the whole contract: the tier as a suffix,
and that tier's token as the first argument. #[magetypes] and #[autoversion]
generate functions of that shape.
Name your own tier functions the same way:
- They join the same family. Leave
v3out of a#[magetypes]list, writegain_impl_v3by hand, andincant!still finds it: hand-tuned variants. - Inside a tier function,
incant!needs no CPU check. In av3function,incant!(helper(…), [v3, scalar])compiles to a direct call tohelper_v3: calls inside a tier. - The suffix shows, at every call site, which CPU features a function needs.
Which macro
| You want | Write | Call it |
|---|---|---|
| One kernel with vector types, for every CPU | #[magetypes(v3, neon, scalar)] on fn kernel(token: Token, …) |
incant!(kernel(…), [v3, neon, scalar]) |
| A plain loop the compiler vectorizes for each tier | #[autoversion] on fn sum(data: &[f32]) -> f32 |
sum(data). The macro writes the dispatcher |
| The intrinsics of one instruction set | #[arcane] on fn kernel_v3(token: X64V3Token, …) |
incant!(kernel(…), [v3, scalar]) |
| A helper inside SIMD code | #[rite(v3)] on fn helper(…) |
helper(…), from a function that has the v3 features |
| An algorithm shared between kernels | A generic helper: #[inline(always)] fn helper<T: F32x8Backend>(token: T, …) |
helper(token, …), from a #[magetypes] kernel |
| The tier chosen by hand | X64V3Token::summon() and an if let |
kernel_v3(token, …), behind #[cfg(target_arch = "x86_64")]: manual dispatch |
The guide also covers tokens and tiers, testing every tier and AVX-512.
Features
| Feature | Crate | Default | Effect |
|---|---|---|---|
std |
both | on | Runtime CPU detection. Without it, summon() sees only the features enabled at compile time. |
avx512 |
both | off | archmage: AVX-512 intrinsics through import_intrinsics. magetypes: native AVX-512 vectors. |
w512 |
magetypes | on | The 512-bit vector types. They run as narrower vectors where native AVX-512 is not in use. |
AVX-512 is opt-in from your own crate. Give it an avx512 feature:
[]
= ["archmage/avx512", "magetypes/avx512"]
Then write the kernel with a 512-bit type and name the tier in both lists. Its copy is compiled only when that feature is on:
#[magetypes(define(f32x16), v4(cfg(avx512)), v3, neon, wasm128, scalar)]
incant!(gain_impl(plane, gain), [v4(cfg(avx512)), v3, neon, wasm128, scalar])
The other tiers run the same f32x16 as a polyfill: two 256-bit operations on
AVX2, four 128-bit operations on NEON and WASM. In the two kernels we
inspected, each polyfilled operation compiled to those native operations and
nothing else (assembly results). The
magetypes README
has the runnable example and what polyfills cost.
Listing v4 on an f32x8 kernel does not widen it: f32x8 stays eight lanes
there.
Installation
has the no_std setup and the remaining features.
Safety
You write no unsafe, so your crate can keep #![forbid(unsafe_code)].
Rust does most of the work. It checks every call into a #[target_feature]
function, and since 1.87 it lets safe code call most intrinsics inside one.
Archmage adds the proof that the CPU has the features: a token such as
X64V3Token. Safe code gets one only once the features are confirmed, normally
by summon(). The macros generate the one unsafe call into feature-enabled
code, and the token is what makes it sound.
magetypes builds on the same tokens and adds its own compile-time checks. What
is left for unsafe there is a few one-line blocks that load, store, gather and
scatter vector storage.
The safety model
has the details, and
SOUNDNESS.md
lists every unsafe in both crates.
Limits
- There is no SVE tier: stable Rust has no SVE intrinsics yet. Targets other than x86-64, AArch64 and WASM, including 32-bit x86, run the scalar fallback.
- Calling an
#[arcane]function once per loop iteration is slow. On two small kernels it measured 4.1× and 6.2× slower than one call around the whole loop (docs/PERFORMANCE.md). Dispatch once, outside the loop. - A vector wider than the CPU's registers is a polyfill. It runs as two or
four native operations, and each value takes that many registers: an
f32x8on NEON is twof32x4s. Polyfills has the measured costs. - With the
avx512feature, AVX-512 tokens also support the 128- and 256-bit float and integer types. These narrower backend, conversion and bitcast implementations delegate to V3; 512-bit types use AVX-512. - Some floating-point results differ between backends.
mul_addrounds once where the hardware fuses (x86 v3/v4, NEON) and twice on the scalar backend and strict WASM;mul_add_portablerounds once everywhere, in software where needed. ISA quirks and fixups lists every difference, and Transcendentals gives each function's domain and precision.
License
MIT OR Apache-2.0
Image tech I maintain
| Codecs ¹ | zenjpeg · zenpng · zenwebp · zengif · zenavif · zenjxl · zenbitmaps · heic · zentiff · zenpdf · zensvg · zenjp2 · zenraw · ultrahdr |
| Codec internals | zenjxl-decoder · jxl-encoder · zenrav1e · rav1d-safe · zenavif-parse · zenavif-serialize |
| Compression | zenflate · zenzop · zenzstd |
| Processing | zenresize · zenquant · zenblend · zenfilters · zensally · zentone |
| Pixels & color | zenpixels · zenpixels-convert · linear-srgb · garb |
| Pipeline & framework | zenpipe · zencodec · zencodecs · zenlayout · zennode · zenwasm · zentract |
| Metrics | zensim · fast-ssim2 · butteraugli · zenmetrics · resamplescope-rs |
| Pickers & ML | zenanalyze · zenpredict · zenpicker |
| Products | Imageflow image engine (.NET · Node · Go) · Imageflow Server · ImageResizer (C#) |
¹ pure-Rust, #![forbid(unsafe_code)] codecs, as of 2026
General Rust awesomeness
zenbench · archmage · magetypes · enough · whereat · cargo-copter