Skip to content

simd: exchange<const G> on U32x16 — the transpose surface, all six backends - #270

Merged
AdaWorldAPI merged 2 commits into
masterfrom
claude/u32x16-exchange
Jul 29, 2026
Merged

simd: exchange<const G> on U32x16 — the transpose surface, all six backends#270
AdaWorldAPI merged 2 commits into
masterfrom
claude/u32x16-exchange

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

blake3-on-ndarray-simd.md identified this gap and prescribed the shape:

"What is missing is a method surface on U32x16, implemented as the scalar index loops LLVM already handles. The measured probe suggests it wants to be one const-generic operation rather than five named ones… About fifteen lines, once."

This is that operation.

exchange::<G>(lo, hi) -> (lo', hi')   // pairs lane c with lane c ^ G
G role replaces in upstream BLAKE3's rust_avx2.rs
1 32-bit unpack _mm256_unpack{lo,hi}_epi32 (8 sites)
2 64-bit unpack _mm256_unpack{lo,hi}_epi64 (8 sites)
4 128-bit lane exchange _mm256_permute2x128_si256 (2 sites)
8 256-bit half exchange no AVX2 analogue — a 512-bit lane needs it

Four stages over it — pairing row r with row r | G — compose a complete 16×16 transpose.

Why const-generic, not five named methods

G must be compile-time constant so every shuffle pattern is fixed. That is the precondition for LLVM to select a shuffle instead of an indexed copy, and it is the entire difference the oracle measured between two spellings of the same function:

spelling packed what was emitted
transpose_16x16_u32 — monolithic index loop 0 1088 B of stack, a 256-iteration scalar element copy, stride-64 scatter
transpose_16x16_composed — four butterfly stages 79 / 0 scalar 19 real shuffles: vinserti128 ×7, vpshufd ×3, vpblendd ×3, vpunpcklqdq ×2, vpermq ×2, vunpcklpd, vpermpd

No intrinsic override is earned

Under the entry criterion in simd-one-spec-design.md, an override needs a probe showing the generic form fails. It does not fail here — the primitive vectorizes, and so does a composition of primitives. Only the monolithic spelling fails, and that is a spelling, not a capability.

Bodies are scalar index loops. Zero unsafe, zero core::arch, on all six backends (avx512 / avx2 / neon / wasm / scalar / nightly).

Semantics: this is not interleave_* / concat_*

Those were added in #267 and mirror x86's per-128-bit-lane unpack. exchange pairs across the whole vector. They are different permutations and neither substitutes for the other. Both are now tested.

Tests

  • u32x16_exchange_stages_compose_a_transpose — composes the four stages and checks against the expected transpose. This is the control the oracle's driver applies to its probe-local twin, brought into the suite so the library method is verified rather than a copy of it. A packed-but-wrong shuffle network passes a codegen histogram and fails this.
  • u32x16_exchange_is_lane_exact_per_granularity — pins each G independently, so one bad stage cannot hide inside the composition.

34 simd tests pass on 1.95.0.

Not claimed

Throughput. The oracle measures instruction class, not speed. Per the knowledge doc: "'Emits packed shuffles' is not 'is faster than the intrinsic backend' — that needs a bench against rust_avx2.rs, which has not been run." No benchmark has been run on either side, and this PR does not change that.

Also unmeasured on aarch64 and wasm — the oracle is per-triple and only baseline-x86_64-v3.toml exists. The lane-exact test covers those backends for correctness; nothing covers their codegen.

Relationship to the other open PRs

Independent of #269 (drops the blake3 dependency) and AdaWorldAPI/BLAKE3#1 (removes the C backends). This is the primitive both of those eventually want — BLAKE3#1's ndarray_simd backend currently does its message transpose as a scalar gather, which is the 0-packed spelling above, and should be rewritten over exchange once this lands.


Generated by Claude Code

…ckends

blake3-on-ndarray-simd.md identified the gap and prescribed the shape: "what
is missing is a method surface on U32x16, implemented as the scalar index
loops LLVM already handles... it wants to be one const-generic operation
rather than five named ones." This is that operation.

  exchange::<G>(lo, hi) -> (lo', hi')

pairs lane c with lane c ^ G. G = 1 is the 32-bit unpack, G = 2 the 64-bit
unpack, G = 4 the 128-bit lane exchange, G = 8 the 256-bit half exchange that
a 512-bit lane needs and for which no AVX2 intrinsic exists. Four stages over
it -- pairing row r with row r | G -- compose a complete 16x16 transpose.

Why const-generic rather than five named methods: G must be compile-time
constant so every shuffle pattern is fixed. That is the precondition for LLVM
to select a shuffle instead of an indexed copy, and it is the difference the
oracle measured between the two spellings of the same function:

  transpose_16x16_u32       (monolithic index loop)  0 packed
                            1088 B of stack, a 256-iteration scalar copy
  transpose_16x16_composed  (four butterfly stages)  79 packed / 0 scalar
                            19 real shuffles: vinserti128, vpshufd, vpblendd,
                            vpunpcklqdq, vpermq, vunpcklpd, vpermpd

No intrinsic override is earned. Under the entry criterion in
simd-one-spec-design.md an override needs a probe showing the generic form
fails; it does not fail here. Bodies are scalar index loops, zero unsafe, zero
core::arch, on every backend.

Semantics note: this is NOT interleave_*/concat_*, which were added in #267
and mirror x86's per-128-bit-lane unpack. exchange pairs across the whole
vector. Neither substitutes for the other, and both are now tested.

Two tests. u32x16_exchange_stages_compose_a_transpose composes the four stages
and checks the result against the expected transpose -- the same control the
oracle driver applies to its probe-local twin, brought into the suite so the
library method is verified rather than its copy. A packed-but-wrong network
passes a codegen histogram and fails this. u32x16_exchange_is_lane_exact_per_
granularity pins each G independently so one bad stage cannot hide inside the
composition.

34 simd tests pass on 1.95.0.

Not claimed: throughput. The oracle measures instruction class, not speed --
"emits packed shuffles" is not "beats the intrinsic backend", and no benchmark
against rust_avx2.rs has been run on either side.

Claude-Session: https://claude.ai/code/session_01VdfbkUCBbtZhy3yjSfCDHp
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@AdaWorldAPI, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a282073-e7d6-4d7f-9a1d-0cc4d08a6d51

📥 Commits

Reviewing files that changed from the base of the PR and between b574ecc and c8dc217.

📒 Files selected for processing (7)
  • src/simd.rs
  • src/simd_avx2.rs
  • src/simd_avx512.rs
  • src/simd_neon.rs
  • src/simd_nightly/u_word_types.rs
  • src/simd_scalar.rs
  • src/simd_wasm.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_98aa19a6-5c06-48a3-a3e4-313cdb529c02)

format/stable failed on the previous commit. No logic change.
@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review July 29, 2026 16:26
@AdaWorldAPI
AdaWorldAPI merged commit 4c91834 into master Jul 29, 2026
19 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c8dc217aee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/simd_avx2.rs
Comment on lines +1745 to +1746
nl[c] = if c & G == 0 { l[c] } else { h[c ^ G] };
nh[c] = if c & G != 0 { h[c] } else { l[c ^ G] };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject unsupported exchange granularities

Because this public const-generic API does not constrain G to the documented values 1, 2, 4, and 8, calls such as exchange::<3> silently duplicate and discard lanes rather than performing an exchange, while exchange::<16> indexes beyond the 16-lane arrays and panics. The tests exercise only valid powers of two, so add a const/runtime assertion for the supported set to this implementation and the five replicated backend implementations.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants