imazen / imazen/archmage

magetypes: mul_add is fused on v3/neon but unfused on scalar tier (and wasm128) — cross-tier output divergence

Open
#61 0 comments 0 reactions 1 assignee View on GitHub

@lilith is already working on this.

Since Jul 17, 2026.

Dominant language
Rust
Stars
12
Forks
2
Avg merge
13h 55m
Merged PRs (30d)
28

Description

The generic `mul_add` lowers to `_mm256_fmadd_ps`/`_mm_fmadd_ps` on x86 v3/v4 and vfma on NEON (exactly fused), but the scalar tier maps to `nostd_math::fmaf`, which is a documented non-fused fallback (`a * b + c`), and wasm128 has no FMA either. A `#[magetypes]` kernel written once therefore computes different results per tier on inputs where fused-vs-unfused rounding straddles a boundary.

Real-world hit: imazen/zenavif's float YUV kernels — the i686 CI leg (which exercises the scalar tier) failed a byte-identity test against a scalar reference on a 1-LSB green-channel mismatch (1 pixel in 3072; red since 2026-07-06). Fixed on the zenavif side by removing `mul_add` from determinism-sensitive kernels (zenavif@96200c2e).

Not proposing a behavior change — hardware without FMA can't fuse, and software-exact fmaf is slow — but two things would help consumers:

1. Document on `mul_add` (and `mul_sub`) that fuse-ness is tier-dependent, so output-byte-determinism kernels must use unfused `a * b + c` (which is exact per-op IEEE and identical on every tier). `round()` is already consistent (ties-even everywhere, including scalar `roundevenf` — that consistency note on scalar `round()` is a good model).
2. Optionally: a doc note that the i686/scalar path is the determinism canary — nothing else in CI typically exercises the scalar tier on x86.

For reference, the scalar fallback: `nostd_math::fmaf(a, b, c) = a * b + c` ("non-fused fallback").

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.