Repository navigation
simd: simd_stride.rs (lut_u8, lut9) + FACET STRIDE rule and guard - #382
Merged
Merged
Conversation
…e lane Looks up each u8 magnitude in a 256-entry table as four 64-entry U8x64::permute_bytes lookups chosen by the magnitude's top two bits. The sign plane passes through, cleared where the result is 0 so a zero result is stored as +0. table[0] must be 0, so a missing row (+0) stays +0 and the presence plane is not touched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
Clippy 1.99 rejects chunks_exact with a constant size. The probe examples/odd_lut_bench.rs times odd_lut_u8 against a scalar i8 table and the scalar sign-magnitude form, after checking the outputs agree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
Contributor
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
AdaWorldAPI
marked this pull request as ready for review
October 27, 2026 16:27
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
A negative value whose result rounds to zero is still stored as +0, but an input -0 now comes out as -0. A writer that only stores +0 never produces it, so the plain reading is unchanged; a class reading that gives -0 its own meaning (observed NaN beside a clear presence bit for not observed) keeps it, the way IEEE carries NaN. The tail needs its live mask again: padding lanes are magnitude 0 and would keep their junk sign bits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
The four-permute lookup ran 5.6x slower than a scalar loop on AVX-512 without VBMI, because permute_bytes is a scalar loop on every backend but AVX-512+VBMI and nightly. lut256 reads the 256-entry table as 16 rows of 16 bytes: the low nibble picks the byte (in-lane vpshufb), the high nibble picks the row. Native on AVX-512 (BW), AVX2 (U8x64 as two U8x32 halves) and AVX-without-AVX2 (two 128-bit halves); the plain loop only in the scalar backend; swizzle_dyn on nightly. examples/odd_lut_bench.rs on AVX-512 without VBMI, 1 Mi elements: scalar u8 table + sign 0.606 ns/elem, odd_lut_u8 0.176 ns/elem. The probe's scalar reference now keeps an input -0, as odd_lut_u8 does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
'{0}..{15}' -f $128.split(): a 128-bit value is sixteen u8 positions,
one mov reads them all. No byte shuffles to compute; scalar loops only
in simd_scalar.rs; u8 has no sign, never mixed with i8.
.claude/hooks/facet-stride-guard.sh runs after every Edit/Write and
blocks added lines that use a shuffle to compute (R1), a scalar lane
loop in a non-scalar backend (R2) or a u8/i8 mix (R3). Waiver:
'// facet-stride: ok <reason>'. --diff and --all for ranges and audits.
Baseline audit of the existing tree: 248 hits outside tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
Sixteen magnitudes are one 128-bit value read in place as positions
{0}..{15}; each position addresses the table directly. No shuffles,
no result assembled from masks. lut256 (16 in-lane vpshufb + mask
fold) is removed from all six backends; it was the forbidden shape and
its nightly arm did not compile.
examples/odd_lut_bench.rs, AVX-512 without VBMI, 1 Mi elements:
scalar u8 table + sign 0.606 ns/elem, odd_lut_u8 0.496 ns/elem.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
The checker reads the whole file and reports only the lines in scope, so hook mode judges added lines with context: test modules (including multi-line cfg(all(test, ..)) and mod *tests) are skipped, a shuffle wrapper's own definition is not compute, and R3 is typed (cast to i8 in a U8 type, to u8 in an I8 type, or a u8<->i8 line). Exclusions with a mandatory reason for cipher rounds, permutation maps and a benchmark of the anti-pattern. --root audits another checkout. Audit: ndarray 166 hits (R1 7, R2 157, R3 2); lance-graph 10 (all R1 nibble-table popcount). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
out[i] = table[values[i]], reading each 16-byte facet's positions where they sit; odd_lut_u8 now uses it for the magnitude lookup. Re-exported through ndarray::simd with FACET_BYTES. Unit tests at every length around the facet, plus parity group 0x15 (21 groups). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
Moves the #382 pattern out of simd_masking_ops into simd_stride beside lut_u8 and names it lut9: a 256-entry magnitude table over a sign-magnitude 9-bit lane (u8 magnitude + sign plane), sign cleared where a nonzero input rounds to zero, input -0 kept. The magnitude lookup is lut_u8. Behaviour unchanged; odd_lut_u8 is renamed, its tests, parity group 0x14 and bench (examples/lut9_bench.rs) follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
…-odd-lut # Conflicts: # .claude/blackboard.md # crates/simd-masking-parity/src/lib.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two reusable FACET STRIDE primitives in a new
src/simd_stride.rs:lut_u8(a byte table) andlut9(an odd function over the sign-magnitude 9-bit lane from #381). Also the FACET STRIDE rule that their development produced, enforced from now on by a guard.███ FACET STRIDE ███
A 128-bit value is sixteen u8 positions. One mov reads all sixteen; nothing has to move to read one. The precedent is
ternary_match_strided16_to_mask: it loads 16-byte registers with plain moves and reads every byte position in place.shuffle_bytes,permute_bytes,*_shuffle_epi8,permutexvar_epi8,swizzle_dyn,vqtbl*). That rules out lookups or folds assembled from shuffles plus masks. A shuffle is only for a genuine byte move that a one-mov cast cannot do.simd_scalar.rs. They are forbidden in the AVX2, AVX-512, AVX, NEON, wasm, AMX and nightly backends when a split realization exists (on AVX2: twoU8x32).Where it lives:
CLAUDE.md: the rule, at the top of the file..claude/blackboard.md: the decision record..claude/hooks/facet-stride-guard.sh: a PostToolUse hook on Edit/Write/MultiEdit, wired in.claude/settings.json.What the guard checks: lines a change adds, judged with the whole file as context.
i8inside a U8 type, tou8inside an I8 type, or a u8/i8 conversion on one line.Test modules are skipped, including multi-line
#[cfg(all(test, ..))]andmod *tests. A hit blocks the edit (exit 2). The waiver is// facet-stride: ok <reason>, and a reason is required.--diff <rev>checks a range,--allaudits the tree, and--root <dir>audits another checkout.Exclusions (
.claude/hooks/facet-stride-exclude.txt, each with a mandatory reason) are only for code whose byte shuffle is the defined operation:vendor/chacha20: cipher rotations by 8 and 16 bits.src/hpc/perm.rs:Perm64is a permutation map.examples/ternlog_popcnt_gap_probe.rs: it measures the anti-pattern on purpose.Verified:
U8x64::lut256code this branch used to carry.--diff origin/masteron this branch: 0 hits.The existing trees are not clean (burn-down lists, not precedents):
.claude/knowledge/facet-stride-audit-2026-10-27.txt).bitwise.rs×4,simd_avx.rs,simd_avx2.rs×2).simd_avx2.rs81, mostly the array-backed AVX2 types; NEON 26; nightly 28; AVX-512 12; wasm 10.splats that cast to thei8intrinsic parameter (AVX2, AVX-512).holograph/src/hamming.rs×2,blasgraph/ndarray_bridge.rs×8). It has no cipher shuffles, so nothing there is excluded.simd_stride.rsBoth are re-exported through
ndarray::simd, together withFACET_BYTES = 16.lut_u8computesout[i] = table[values[i]]. Each 16-byte facet's positions are read where they sit and address the table directly.lut9works on the SM9 lane from #381: au8magnitude per row, au64sign plane, and a separate presence plane. An odd function (f(-x) = -f(x)) needs one table over the 256 magnitudes, and the sign passes through.lut_u8.+0.-0stays-0, the way IEEE arithmetic carries NaN through.table[0]must be0, or the call panics. A missing row is presence clear over0, so it stays0, and the presence plane is never read or written.U8x64::cmpeq_mask. The tail is padded with 0 and masked off the sign plane.How it got here:
permute_bytes. This was 5.6× slower than a scalar loop, because without AVX-512 VBMIpermute_bytesis a scalar loop.vpshufbplus a mask fold (U8x64::lut256on all six backends). This is the shape the rule forbids, and its nightly arm did not compile.Both are gone, and
lut256is removed from every backend.lut9was briefly namedodd_lut_u8on this branch.Measured
examples/lut9_bench.rs, AVX-512 host without VBMI, 1 Mi elements. The outputs are checked against the scalar form before timing.lut9Tests
Unit tests (7):
lut_u8at 14 lengths around the 16-byte facet;lut_u8reading every table entry;lut_u8refusing a short output;lut9at every magnitude, at every lane position and at 12 lengths around the 64-row seams, with junk sign bits past the end and pre-filled outputs;lut9rounding to+0while keeping an input-0;lut9refusing a table withtable[0] != 0;lut9refusing a short sign plane.Parity groups:
0x14xx(lut9): 11 lengths × 2 passes.0x15xx(lut_u8): 12 lengths.CHECKSgoes from 21 to 23 (master already has simd: NaN/presence bijection and SQL NULL=NaN predicates #381's two groups).Disable runs. Each was red in both the unit tests and the parity group.
lut9: input-0keptlut9: tail rows masked off the sign planelut9: lookup reads the right position (table[m ^ 1])lut_u8: the tail is looked upLint: clippy
-D warningsis clean on the lib, the tests, the examples and the parity crate.Backend arms, at e47d456 (before the merge with master): 9/9 pass. Every arm that prints its group count reports all 21 groups: native AVX-512, AVX2 (
config-v3), NEON qemu, AVX qemu, v2 qemu and nightly. wasm, wasm-scalar and wasm-relaxed pass their self-check.After merging master (simd: NaN/presence bijection and SQL NULL=NaN predicates #381): the parity crate conflicted on the import list and the group list; both now carry both PRs' entries. Native parity passes 23/23 groups. Clippy, rustfmt and the guard (
--diff origin/master, 0 hits) are clean, and the libsimd_tests (471) and thesm9_presence_probetests (7) pass.Open
shuffle_bytesgives different results across backends when an index has bit 7 set. AVX2U8x64::shuffle_bytesandpermute_bytesare scalar loops. Both are entries in the audit list.🤖 Generated with Claude Code
https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY