imazen / imazen/archmage

F32x8Convert + transcendentals: missing impls for X64V4Token (and audit other Convert-trait gaps)

Open
#45 3 comments 0 reactions 1 assignee Claimed by @lilith View on GitHub
enhancement
Dominant language
Rust
Stars
12
Forks
2
Avg merge
13h 55m
Merged PRs (30d)
28

Description

## Symptom

Code parameterized on `T: F32x8Convert` (e.g. `f32x8::pow_midp`, `f32x8::log2_midp`, `f32x8::pow_midp_precise`, `f32x8::log2_midp_precise`, `f32x8::exp2_*`) cannot accept `X64V4Token` because `magetypes 0.9.22` does not implement `F32x8Convert for X64V4Token`.

Practical effect: any `#[magetypes(...)]` body that calls `pow_midp` / `log2_midp` on an `f32x8` cannot list a `v4(cfg(avx512))` tier alongside `v3, neon, wasm128, scalar`. AVX-512 hardware therefore falls back to the `v3` tier — still vectorized at 256-bit lanes, just not 512.

zentone PR1 ([`26e9c56`](https://github.com/imazen/zentone/commit/26e9c56)) and PR2 ([`46fbe67`](https://github.com/imazen/zentone/commit/46fbe67)) hit this with the AgX, BT.2408, BT.2446 and HLG kernels; the workaround there is hand-written `_v3 / _neon / _wasm128 / _scalar` per-tier routes with no `_v4x` slot.

## Audit — Convert / Bitcast trait coverage matrix

Across `magetypes/src/simd/impls/*.rs` on `main` (`38b0c52`, magetypes 0.9.22):

| Trait | ScalarToken | X64V3Token | X64V4Token | X64V4xToken | Avx512Fp16Token | NeonToken | Wasm128Token |
|--------------------|:-----------:|:----------:|:----------:|:-----------:|:---------------:|:---------:|:------------:|
| `F32x4Convert` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| `F32x8Convert` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| `F32x16Convert` | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| `U32x4Bitcast` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| `U32x8Bitcast` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| `I64x2Bitcast` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| `I64x4Bitcast` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| `I8x16Bitcast` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| `I8x32Bitcast` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| `I16x8Bitcast` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| `I16x16Bitcast` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| `U64x2Bitcast` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |
| `U64x4Bitcast` | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ |

(Only `F32x16Convert` made it through for `X64V4Token` / `X64V4xToken`. Avx512Fp16Token is not covered for any Convert trait.)

The shape is identical for every `❌`: the underlying narrow integer / f64 backends (`I32x4Backend`, `I32x8Backend`, `U32x4Backend`, `U32x8Backend`, `I64x2Backend`, `I64x4Backend`, `F64x2Backend`, `F64x4Backend`, plus `I8x16/U8x16`, `I8x32/U8x32`, `I16x8/U16x8`, `I16x16/U16x16`, `U64x2`, `U64x4`) are *also* missing on the V4-family tokens. This is the same shape that `0.9.21` solved for `F32x4Backend` / `F32x8Backend` via the hand-written `magetypes/src/simd/impls/x86_v4_f32_delegated.rs` delegation file (V4 ⊃ V3, so delegating narrower-width methods to V3 with the `.v3()` extractor is sound).

Per the [0.9.21 changelog](https://github.com/imazen/archmage/blob/main/CHANGELOG.md#0921--2026-04-20):

> Backend delegation: `X64V4Token`, `X64V4xToken`, and `Avx512Fp16Token` now implement `F32x4Backend` and `F32x8Backend` by delegating to `X64V3Token` via the `.v3()` extractor.

This issue is the natural follow-on: extend the same delegation pattern to (a) the narrow integer / f64 backends and (b) every `Convert` / `Bitcast` trait on top of them.

## Generated transcendentals coverage

`magetypes/src/simd/generic/generated/transcendentals_f32x{4,8,16}.rs` each define `pow_lowp`, `pow_midp`, `pow_midp_precise`, `log2_lowp`, `log2_midp`, `log2_midp_precise`, `exp2_lowp`, `exp2_midp`, `exp2_midp_precise` on `impl f32xN`. So `f32x8::pow_midp` is uniformly available everywhere `F32x8Convert` is implemented — and unavailable everywhere it isn't. The matrix above is therefore also the matrix of "what hardware can run pow_midp on f32x8 generic kernels."

## Practical impact

- **Correctness:** none. V3 fallback is still vectorized; output is identical.
- **Performance:** AVX-512 hardware running `f32x8`-shaped kernels can't widen to `f32x16` *without rewriting the kernel*, AND can't even slot a hand-tuned 512-bit `_v4x` variant into a `#[magetypes(...)]` body that uses `pow_midp` because the trait bound rejects V4. Effectively caps `pow_midp`-using `f32x8` kernels at AVX2 width on AVX-512 silicon.
- **Ergonomics:** zentone, ultrahdr-core, zenfilters, and other downstream crates have to choose between (a) writing two parallel kernels (one f32x8 for V3/Neon/Wasm128, one f32x16 for V4) or (b) accepting the AVX2 ceiling. PR1 + PR2 in zentone took option (b) because option (a) doubles the kernel surface area.

## Proposed fix

Extend the existing delegation pattern from `x86_v4_f32_delegated.rs` to cover every narrow integer / f64 backend on `X64V4Token` / `X64V4xToken` / `Avx512Fp16Token`, then implement every `Convert` / `Bitcast` trait on those tokens by chaining through the new narrow backend delegations.

Concretely, scope is roughly:
1. **Narrow backend delegation** — extend or add a sibling to `x86_v4_f32_delegated.rs` that delegates `I32x4Backend`, `I32x8Backend`, `U32x4Backend`, `U32x8Backend`, `I64x2Backend`, `I64x4Backend`, `F64x2Backend`, `F64x4Backend`, `I8x16/U8x16`, `I8x32/U8x32`, `I16x8/U16x8`, `I16x16/U16x16`, `U64x2/U64x4` from V4-family tokens to V3 via `.v3()`.
2. **Convert / Bitcast impls** — once narrow backends exist, implement `F32x4Convert`, `F32x8Convert`, and every `*Bitcast` trait by delegating to V3 (same `.v3()` pattern).
3. **Tests** — extend `transcendentals_f32x8` test sweep to V4 tokens; add a parity check (V4 result == V3 result, since V4 path *is* V3 path under the hood).

Soundness argument is identical to the 0.9.21 work: V4 ⊃ V3 (strict feature-set superset), so any V3 intrinsic is safe under V4. `Self::Repr` matches V3's repr at every width. Long term the generators in `cargo xtask generate` should produce these delegations natively; the hand-written file is a stopgap (same status as the existing `x86_v4_f32_delegated.rs`).

## Why this is bigger than a single trait impl

Implementing `F32x8Convert for X64V4Token` requires `I32x8Backend for X64V4Token`, which doesn't exist on `main` either. So the fix isn't 1 impl block — it's a delegation cascade across ~16 narrow backends + 13 convert/bitcast traits × 3 tokens (V4, V4x, Avx512Fp16). The audit matrix is the scope; doing it right means landing all of it together so the trait surface is uniform and downstream crates can stop hand-rolling per-tier kernels.

I'd like to flag this for review before implementing, since it's a ~30-impl extension of the hand-written delegation file and the right shape might be to fold it into the generator instead. Happy to draft either way.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.