Repository navigation
v0.9.30
One release of the three crates (archmage, archmage-macros, magetypes); archmage pins archmage-macros exactly and magetypes requires archmage 0.9.30. Full changelog: CHANGELOG.md.
archmage
Fixed
- Token
detect()no longer loses a race againstdisable(): the cold-path probe published its result with an unconditional cache store, so asummon()that entered*_detect()while the cache read "undetected" could overwrite adangerously_disable_token_process_wide(true)landing mid-probe and leavesummon()returningSomefor a disabled token. The generated x86 and aarch64 detects now publish withcompare_exchange(0, ..)and recheck the disabled flag.tests/token_permutations.rsgaineddetect_cannot_resurrect_a_disabled_token, a two-thread reproducer that trips the old code. - archmage depends on archmage-macros at an exact version, so the archmage-macros changes below arrive with this release.
Changed
- The
testable_dispatchdocs say what it costs and where it belongs: it makes everysummon()read a cache, evenX64V1Token's on x86-64, which is otherwise a constant, and Cargo unifies it across the whole build, so enable it from[dev-dependencies]or a dev-only feature, never[dependencies](aea4d41).
archmage-macros
Changed
- The
#[target_feature]function#[arcane]and#[rite]emit keeps the parameters as written ((lo, hi): (f32, f32),_: f32); only the#[arcane]wrapper, which forwards its arguments by name, names non-identifier patterns (__archmage_arg_N), and only a wildcard token is renamed in the feature-enabled function, for the nestedincant!rewrite. A#[magetypes]variant that already carries the#[cfg(target_arch = ...)]the inner macro would add is no longer gated twice. synfloor raised to 3.0.6, the tested version (27f814c).
Added
#[magetypes(rite, ...)]accepts functions without a token parameter, which 0.9.29 rejected ("rite requires a token parameter or a tier name"). Each SIMD variant becomes a tier-based#[rite(<tier>)]function, and the scalar and default variants call the covered scalar callees directly. Token-taking fallbacks keep runtime dispatch (0833022, 3aef13f).#[arcane(in_impl)],#[autoversion(in_impl)]and#[magetypes(in_impl)]handle an associated function without a receiver in an inherent impl: the wrapper or dispatcher callsSelf::, which the macro cannot infer from the function alone (#123).#[arcane(in_trait, _self = Type)](in_traitis an alias ofnested) and the new#[autoversion(in_trait, _self = Type)]work in trait impls with plainselfin the body: the receiver becomes_self, andselfandSelfin the body are rewritten for the nested function.#[autoversion]previously had no trait path at all; its variants now nest inside the dispatcher instead of landing in the trait impl (#123).- Two token parameters on
#[arcane]or#[rite]are a compile error that names both; 0.9.29 silently used the first.#[autoversion]rejects animpl Traitreturn type with a message instead of rustc's type error on generated code.#[rite(in_trait)]and#[rite(in_impl)]say what to use instead; rustc rejects#[target_feature]on a safe trait method, which#[rite]applies directly (#123). - The
incant!(... with token)fallback panic names the token and the tier list when no tier matches (#123). - Fixes from an external review of #123 (gpt-6-astra):
#[arcane]rejects a parameter named like the function it generates (__arcane_<fn>,__simd_inner_<fn>), the one binding that could shadow it inside the wrapper'sunsafecall; nested receivers keep their lifetime, mutability or explicit type (&'a self,self: Box<Self>) in both#[arcane(in_trait)]and#[autoversion(in_trait)], through one shared lowering;#[autoversion(in_trait, _self = T)]substitutesSelfacross the whole signature; theselfrewrite leaves nestedimpl/traitbodies alone; a nestedincant!keeps a covered tier'scfg(feature)gate as an arm (it was an unconditional call, E0425 with the feature off), replaces the caller's named token in the scalar fallback (E0061), and threads a wildcard token under its generated name (E0425);#[autoversion]passes the token at the user's parameter position (E0308 when it was not first). Diagnostics: the two-token message names a working alternative, the#[rite]trait-method text is qualified by architecture, and the rite rustdoc no longer lists-Ctarget-cpuas a safe-call context (#123).
Fixed
#[arcane]on a function that takesScalarTokencompiles; 0.9.29 emitted#[target_feature(enable = "")], which rustc rejects (c766c23).#[magetypes]no longer replaces theTokenmarker in a nestedincant!ordispatch_variant!argument list with the tier's token type, which broke those calls in 0.9.29 (c766c23).- The package includes the MIT and Apache-2.0 license texts (683bef7).
- The
#[rite]unknown-argument error no longer listsstub, which the parser rejects as removed (6f2bd1c). #[arcane]and#[rite]no longer rebind a wildcard parameter with animpl Traitbound aslet _: impl Trait = ..., which rustc rejects (E0562); wildcards are not rebound at all, and#[rite]renames tuple and wildcard parameters the way#[arcane]does (#123).#[autoversion]forwards a parameter written as a pattern, such as(lo, hi): (f32, f32); 0.9.29 kept the pattern in the dispatcher's signature and left the parameter out of the call (E0061). Wildcards and patterns appear as__archmage_arg_Nin the dispatcher's signature (#123).#[arcane]and#[rite]collect generic bounds from every inline andwhereposition, so a token bound split across both, or declared after another parameter's bound, is found (#123).#[arcane]and#[rite]share one argument parser, diagnostic set, parameter renamer and variant emitter, so the fixes above apply to the sibling, nested and WASM expansions and to single- and multi-tier#[rite]alike (#123).#[arcane]on anunsafe fnkeepsunsafeon the generated#[target_feature]function (the__arcane_<fn>sibling, or the nested inner function); 0.9.0 through 0.9.29 (sibling mode, the default since 0.9.0) emitted a safe one, which any function with matching target features in the same module could call withoutunsafe, dropping the preconditions the user declared on the wrapper. The#[autoversion]variants of anunsafe fnare#[arcane]expansions, so they had the same hole (0.9.13 fixed their dispatcher, which had been safe too, and left the siblings). The sibling is private and#[doc(hidden)], so only code in the defining module could reach it, by naming the generated function; no other crate can. A safe input still gets a safe sibling, so#![forbid(unsafe_code)]crates are unaffected. Pinned bytests/soundness/sibling_unsafe_bypass.rsandautoversion_unsafe_bypass.rs(E0133). Found by an external review before this release.#[track_caller]on an#[arcane],#[autoversion]or#[magetypes]function reaches the body: the attribute now goes on the generated#[target_feature]function as well as the wrapper or dispatcher, soLocation::caller()and panic messages name the user's call site. 0.9.29 put it on the wrapper only, which named the body's own line. Pinned bytests/boundary_attrs.rs.#[expect(..)]on an#[arcane],#[autoversion]or#[magetypes]function is fulfilled by the body alone: the generated#[target_feature]function keeps the#[expect]and the wrapper or dispatcher gets#[allow]of the same lints (in nested mode the wrapper keeps the#[expect], which covers the inner function). 0.9.29 copied the#[expect]onto the wrapper, where every parameter is used, so the copy was reported unfulfilled (a warning, or an error underdeny(unfulfilled_lint_expectations)).#[autoversion]variants also now carry the user's lint levels, which 0.9.29 left on the dispatcher only. Pinned bytests/boundary_attrs.rs, which deniesunfulfilled_lint_expectations.
magetypes
Added
- Add magetypes inherent methods such as
splat_t(token, value),zero_t(token), andload_t(token, data)across token-taking constructors, conversions, slice helpers, and single-lane scalar types. The_tmethods hold the implementations; existing names retain their signatures as deprecated forwarders (772ef50, d53d425). - Add raw-register interop to every native-backend vector type:
raw()returns the platform register,from_raw_t(token, raw)wraps one for a caller that holds a token (generic code included), andfrom_raw(raw)takes no token and wraps one inside a matching or stronger target-feature context that rustc checks. That covers all NEON and WASM types, all AVX-512 types (X64V4Token,X64V4xToken) and the x86 V3 128- and 256-bit types, which already hadraw(). The x86 platform names 0.9.29 shipped (from_m128,from_m128d,from_m128i,from_m256,from_m256d,from_m256i) are deprecated forwarders tofrom_raw_t, removed in 0.10. The platform names 0.9.27 removed with the concrete types (NEON, WASM, AVX-512) do not come back: the one published crate that used them, jxl-encoder-simd 0.3.0, does not build on those targets without changes of its own, and it is ours to migrate. No type has a_ttwin of a platform name (772ef50, #125). - The constructor inventory lists the old and new signature, bounds and platform gates of every token-taking constructor for all 40 vector types (e619c59).
- Add
mul_add_portableandmul_sub_portableto every float vector type and tof32x1/f64x1: one rounding on every backend, the same result asf32::mul_add/f64::mul_addapart from NaN payload and sign. x86 v3/v4 and NEON use FMA, the same instruction asmul_add; so do the scalar backend andf32x1/f64x1when the CPU has FMA (always on AArch64; on x86-64 throughX64V3Token::summon(), a constant with-Ctarget-cpufrom Haswell/Zen 1 on), where they cost the same asmul_add(record, #125). Without hardware FMA they fuse in software, WASM included (relaxed madd may round twice), at 2.7–29.5× the time ofa * b + con the scalar backend and 8.2–24× under wasmtime (#116; 6698614; benchmark, WASM). - Add bounds-safe AVX-512 gather and scatter to
u32x16,i32x16andf32x16withX64V4Token, indexed by au32x16:gather_wrappingreads a power-of-two table with wrapping indices,gather_orreads a slice and leaves out-of-range lanes at a fallback, andscatter_selectwrites enabled, in-range lanes. None of them panics; there are no versions for other widths or backends (guide; e354363). Their docs and the guide name each intrinsic they use and link to Intel's entry for it; the guide andsimd_storage.rsquote Intel's description and pseudocode (5b5fd7a).
Deprecated
- Deprecate legacy token-taking constructor and helper names in favor of
_t(token, ...), preserving argument order and signatures. Builds usingdeny(deprecated)ordeny(warnings)must migrate those calls or allow the warnings._tremains supported in the planned 0.10 transition; native NEON names already ending in_tand feature-contextfrom_raw(raw)are not deprecated (d53d425). - Deprecate
cast::Upcastandcast::Downcast. Nothing has ever implemented either: a generic vector carries its token, so moving one to another context means rebuilding it with that context's token,from_array_t(token, v.to_array())for any vector orfrom_raw_t(token, v.raw())on native backends. Width changes uselow(),high(),split()andfrom_halves_t()(b1b2ff6).
Changed
- magetypes keeps all of its
unsafein one module,simd_storage.rs, and deniesunsafe_codeeverywhere else; theTokenStorageimpls there check each wrapper's layout at compile time. Generated code is unchanged on x86_64, aarch64 and wasm32, except that four V3 128-bit pairwise-widen-add kernels name the two operands of their final add in the other order: same instructions, same results (7e49cca). - On WASM built with
relaxed-simd,mul_addandmul_subuse the engine's relaxed madd, which may round once or twice; 0.9.29 multiplied and then added. There is no runtime probe. Under wasmtime it takes 0.96–1.06× the time ofa * b + c(#116; 11a35a8, 6698614). The generic transcendentals usemul_add, so in relaxed builds their last bits can differ from 0.9.29. Every other backend keeps the 0.9.29 contract: one rounding with hardware FMA (x86 v3/v4, NEON), multiply then add on the scalar backend and strict WASM. - The
mul_adddocumentation and the ISA quirks page state what each multiply-add form costs on each backend: with hardware FMA,mul_addtakes 24–38% less time thana * b + cin dependency chains and about the same in streams (d652f72, ea6e5e7). - The transcendental docs state accuracy measured over every f32 input instead of sampled estimates. midp:
log2,lnandlog10at most 4.5 ULP,cbrt3.2,exp21.9 below x = 127.5 and 134.1 above it,expandpowgrowing with the exponent. lowp:exp2,expandpowwithin 0.56% relative error,log2,lnandlog10within 8.5e-6 absolute error (record; eeb58da, 2819d03). all_trueandany_truetest each lane's sign bit on every backend and width, as their documentation always said. In 0.9.29, AVX-512, NEON and WASM tested for nonzero lanes (NEONu32x4for all-ones), and x86 V3i16/u16vectors tested the sign bit of every byte. Comparison masks give the same results as before; other inputs can change, for example a lane holding1is now false (050e25c, 8014e94).- On WASM,
to_u8and the RGBA stores use native rounding, saturating conversion and narrowing instead of the scalar default. Results are unchanged (050e25c). - The published magetypes crate no longer includes tests, benches or examples: 245 KiB instead of 0.9.29's 393 KiB (877a3e6).
Fixed
- On AVX-512, the f32 block operations ran the scalar default instead of native code (#60).
to_u8(both widths) andf32x4::store_4_rgba_u8now narrow withvpmovusdb, and the rest use the AVX2 implementations; results are unchanged. On Zen 5,f32x8::to_u8took 118 ns per 4096 values instead of 6,042 ns, andstore_8_rgba_u8113 ns per 1024 pixels instead of 8,273 ns (benchmark; 3999a29, 2bc19d8). - On x86 V3 (AVX2),
to_u8andstore_*_rgba_u8returned 0 for+infand for values at or above 2^31; they now saturate to 255 like the other backends, and NaN still gives 0. The clamp makes these calls take 1.25–1.6× as long as the 0.9.29 form (benchmark; ff9e0a5). exp2_lowpclamped its input to 126, so every x in [126, 128) returned 2^126, up to 75% low;exp_lowpandpow_lowpinherited it. The clamp is now 127.99: at most 0.56% relative error below it and 1.23% above, at the same speed (64ae008).cbrt_midp_precisereturned NaN, or +inf for a few inputs, for every input from 1.1342859e38 up. It now covers the whole f32 range at 3.2 ULP and takes 15–17% longer with AVX2, 8–9% on the scalar backend.cbrt_midpandcbrt_lowpkeep their speed and document that abovef32::MAX / 3they return NaN, or ±inf near the limit; every fix measured 8–47% slower (2f856d2, 96f85cd).- The package includes the MIT and Apache-2.0 license texts (683bef7).
- The docs of
log2_midp_precise,ln_midp_precise,log10_midp_preciseandpow_midp_precisepromised subnormal handling that they do not have: each is its plain_midpform, solog2_midp_precise(1e-42)returns about -127 (true -139.5). The docs now say so; onlycbrt_midp_precisehandles subnormal inputs. Behavior is unchanged (780e205).
Workspace
Added
- Raw expansion snapshots under
tests/expand-raw/: the macros' own output before cfg evaluation, so every architecture's variant, every feature gate and every diagnostic is visible (the cargo-expand snapshots show only the host's post-cfg view). Written and checked byarchmage-macros/src/raw_snapshots.rs;just expand-snapshotsrefreshes both sets. Newcombinations/*_incant.rsinputs show the nested-dispatch rewrite calling the covered tier directly with nosummon(). magetypes/tests/harvest_shapes.rscompiles every macro signature shape the local consumer crates use: 233 shapes from 2,043#[arcane]/#[rite]/#[autoversion]/#[magetypes]functions in 50 crates, each with placeholder types and a stub body, so a macro change that stops accepting a shape some crate relies on fails here first. Regenerated byjust harvest-shapes ROOT(xtask/harvest_shapes.py) from snapshots of the crates.just ciends with a table of every step's wall time, and runs Miri and the no_std gate as background jobs in their own build directories (target/miri-ci.log,target/nostd): 399 s → 162 s on the same box and load. Three sweeps that are safe lane code joinMIRI_SKIPPED_TESTSwith their retained coverage named (int_uniform_shift_saturating::scalar_backend,precise_reciprocals,exp2_lowp_range); native, SDE and cross-architecture CI still run them.cargo xtask generatewrites expansion snapshots for 40 signature shapes (receivers, wildcards, tuples, generics, bounds,impl Trait, placement flags) across#[arcane],#[rite],#[autoversion]and#[magetypes]undertests/expand/shapes/, and the shapes the macros cannot handle undertests/expand/should-fail/(#123).archmage-macros/src/lib.rsholds the macro entry points and their docs; its unit tests moved toarchmage-macros/src/tests.rs(#123).- Enforce generated-backend size budgets to detect unintended growth (3999a29, a97decd). The AVX-512 tokens' W128/W256 f32 backends are now generated from the
F32x4Backend/F32x8Backenddefinitions (xtask/src/simd_types/v4_delegation_gen.rs), so a trait method cannot be left to its scalar default body for those tokens; that replaces the hand-writtenx86_v4_f32_delegated.rs, itsvalidatecheck and its privateV4Prooftrait. The three AVX-512VL pixel-packing helpers stay hand-written inx86_v4_f32_overrides.rs(#125). - Add tokenful/tokenless calling-convention fixtures on x86, ARM/QEMU, and WASM/Wasmtime (c766c23, 595d13e).
- Record native compilation checks for 23 published consumer libraries, including optional SIMD feature checks; this audit does not establish ARM/WASM consumer compatibility or runtime equivalence (5bfae75, aac6160).
- Record generic-vs-intrinsic codegen and compile-cost measurements (1a0ea59).
- Record magetypes cold-build time against 0.9.29: 3.6–6.7% longer, about 40–60 ms on a Ryzen 9 9950X3D, measured with the new
benchmarks/magetypes_compile_perf.pyharness (ee27ecc; moved beside its results in #125). magetypes/benches/mul_add_cost.rstimesa * b + c,mul_addandmul_add_portableon each backend, in a streaming loop and in a dependency chain (97730e5, ca30e56).xtask soundnesskeeps magetypes' hand-writtenunsafeinsimd_storage.rs: it rejectsunsafeblocks elsewhere (outside the generated backend impls) and gather/scatter intrinsics anywhere else, so every gather/scatter lane offset is bounded against the borrowed slice in that one file (e354363, 3fb0c91).cargo xtask validatefails when archmage-macros or magetypes lacks a license text or its copy differs from the root file (683bef7).- Exercise tests for the NEON dotprod, fp16 and fhm intrinsics and the AVX-512 FP16 intrinsics, which became stable in Rust 1.94 and 1.98; CI runs the suite under Intel SDE's Sapphire Rapids model, the only lane where
Avx512Fp16Tokensummons (916be54). cargo xtask <command>works: the repository defines the alias its docs use (d98d2b4).- The guide explains the safety model of archmage and magetypes: how safe code gets a token, the one
unsafeblock in each#[arcane]expansion, magetypes' singleunsafemodule, the known limits and the checks. Both crate docs and READMEs link it (8e2b871).
Fixed
- The Miri CI job runs on
nightly-2026-10-06: zerocopy 0.8.61 callsalign_of_val_rawundercfg(miri), stable from Rust 1.99, which the oldnightly-2026-02-03pin rejected (E0658) (02bc075). The pin lives inxtask/miri-nightly.txt, whichcargo xtask miri,just ciandjust miri-tokensread too, so a local run uses the toolchain CI uses. - The intrinsic database and browser listed the FHM
vfmlal*/vfmlsl*intrinsics underArm64V2Token; they also needfhm, soArm64V3Token. The extractor now readscfg_attrtarget features (1ed03ff).
Changed
- The archmage and magetypes READMEs open with what each crate does, run their examples, add a direct-intrinsics example, Limits and (magetypes) What's included sections, and move release history to the migration guide (56d2dfb).
- The READMEs put the first example ahead of its explanation, show features in a table with the
avx512forwarding lines, and link to the guide for detail (724e9e0). - The READMEs write the AVX-512 tier as
v4(cfg(avx512))and show theavx512feature your crate needs for it. The AVX-512 guide page recommends the explicit form (47068cd). - The READMEs state the
<name>_<tier>naming rule and show how each macro's functions are called. Theincant!guide page gains a tested example of a hand-written_v3variant beside generated ones (e637cb0). - The AVX-512 guide page says what the
v4context changes forf32x8code, from assembly inspection recorded inbenchmarks/v4_context_asm_2026-10-06.md(463ad6a). - The READMEs and the AVX-512 page say that
f32x4andf32x8on the V4 tokens lack transcendentals, integer conversions and raw interop (463ad6a). - The magetypes README demos AVX-512 with an
f32x16kernel under Features, and its quick start listsv3, neon, wasm128, scalar(d71523b). - The READMEs and the polyfills guide page say what polyfills cost, from assembly inspection recorded in
benchmarks/polyfill_asm_2026-10-06.md(d71523b). - The polyfills guide page and the magetypes README report timings of polyfilled kernels on a Neoverse-N1, an Apple M4 Pro and Zen 5, recorded in
benchmarks/polyfill_timing_2026-10-06.md(584c957). just diff-rsshows.rschanges since the last release tag without generated code or expansion snapshots (584c957, 3159652).- The dispatch-testing page shows the
[dev-dependencies]entry before its caveats, and the safety pages split their longest sentences (78e1ada). - The AVX-512 page states which macros need the caller's
avx512feature (cae4844). - The two guide landing pages say what each crate is before their example (dae6f38).
- The integer codegen probe (
xtask/codegen.py --integer-ops) treats swapped source operands of x86 instructions that commute bit-exactly as equal, like its NEON multiply rule (f287f3b). - Miri skips
int_widen_narrow::scalar_backendand thefused_arithmeticbinary, about 95% of its work, and the newcbrt_rangesweep, which took 432 s under Miri on its own and runs only safe code there.scalar_backend's exhaustive loops reach the sameunsafebitcast path asscalar_w512_bitcast_values, which still runs under Miri;fused_arithmeticis safe code only. Native, SDE and cross-arch CI run everything. The local Miri run takes 255–394 s, depending on load (ec9c66b, d570aa9, 4d48607, 6906185). - The publish workflow runs the complete CI matrix at the release commit, as a reusable workflow, and publishes only if every job passed; a missing, skipped or cancelled job fails the gate, except the PR-only codegen comparison (8444c63).
- CI runs
cargo-semver-checksfor archmage and magetypes on every push and in the release gate; the separate PR-only workflow is removed (f209b89). just ciruns the fullcargo xtask validateset, the same checks as CI's Validate Token Safety job (f4cd76e).
Historical entries below describe bundled releases of archmage, archmage-macros,
and magetypes. Their original content is preserved.