Skip to content

Commit e730109

Browse files
committed
simd: nightly realization complete, codegen witness, shared masking parity, CodeRabbit round 2
Nightly (`--features nightly-simd`) now realizes the whole facade, not just the mask family: `simd_nightly/w1a_types.rs` adds I8x16 (from_i4_packed_u64 sign-extends 0x8 -> -8, saturating_abs(i8::MIN) == i8::MAX), U16x8 + gather_u16, U8x8, palette_lookup_u8x8, prefetch_read_t0/1/2 (documented no-ops) and batch_packed_i4_16, re-exported through simd_nightly and the nightly arm of simd.rs. The five W1a tests in simd_int_ops run on nightly instead of being cfg-gated away. Lib test target and clippy are clean on nightly (192 focused tests + 12 W1a). Codegen witness (examples/ternlog_codegen_probe.rs, [profile.ci-codegen], scripts/codegen-witness.sh, .cargo/config-v4.toml): a tiny opt-level-3 example whose symbols are checked for the instruction each arm must select. Running it found that the CARGO_TARGET_<triple>_RUSTFLAGS recipe loses to .cargo/config.toml's cfg-keyed x86-64-v3 (cargo joins both, last -Ctarget-cpu wins; measured with cargo -v), so the tier4-avx512-check CI job had been checking the AVX2 arm. Fixed to `--config .cargo/config-v4.toml` and the job now asserts vpternlog is emitted. Witness PASS on avx2 (v3), avx512 (v4, 1/1/6 vpternlog) and neon (cross, asm-only; the aarch64 build exposed a missing U32x16::reduce_sum on the NEON and wasm backends, added). Slice-level probes allow bounded loop-control GPR ops (cap 12, measured 9/6). crates/simd-masking-parity: ONE parity program over the shipped facade (all 256 ternlog tables x2 widths, U64x8 algebra, I32x16 compares, every predicate->mask at rows 0/1/63/64/65/130 with the zero-tail and full-overwrite contract, mask algebra + in-place forms + any/all on tails, care-match contiguous and strided, masked sum/min/max, strided group sum, blend). rlib + cdylib + bin; green natively (v3) and under node with and without +simd128 (the scalar realization). CodeRabbit round 2: the 13 mask functions that were public as ndarray::simd_int_ops::<f> on master are re-exported from simd_int_ops (verified complete against master's pub fn list); Group F block no longer splits the oracle README table; a == a comparisons replaced in both parity harnesses; neon-asm-rung3.sh fails on a build error instead of grading stale assembly; the U32x16 ternlog doc paragraph sits on the test it describes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6y3drwKSE2zSgoexheLFX
1 parent 7bd1072 commit e730109

28 files changed

Lines changed: 1905 additions & 35 deletions

File tree

.cargo/config-v4.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[build]
2+
# Plain AVX-512 baseline — `x86-64-v4` (F + BW + CD + DQ + VL), nothing above
3+
# it. Use with:
4+
# cargo --config .cargo/config-v4.toml check --lib
5+
# cargo --config .cargo/config-v4.toml test --lib --no-run
6+
#
7+
# This is the deterministic AVX-512 COMPILE contract the SIMD realization
8+
# matrix builds against: `simd.rs` keys its arm off `target_feature = "avx512f"`,
9+
# so this selects `simd_avx512` and emits `vpternlogq` etc. regardless of the
10+
# build host's silicon — code generation and execution are separate, and a
11+
# GitHub runner does not need AVX-512 to prove LLVM emits it. Do NOT run the
12+
# resulting binary on a host without AVX-512 (SIGILL); use the v3 or native
13+
# configs for execution, or qemu for semantic parity.
14+
#
15+
# `config-avx512.toml` is the stricter Sapphire Rapids EXECUTION config (VNNI,
16+
# BF16, FP16, AMX…); it SIGILLs on any earlier AVX-512 silicon, so it is never
17+
# the CI compile oracle.
18+
[target.'cfg(target_arch = "x86_64")']
19+
rustflags = ["-Ctarget-cpu=x86-64-v4"]

.claude/blackboard.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,15 @@ simd_{avx512,avx2,neon,wasm,scalar}.rs each owns its realization, as a PEER
154154
`simd_int_ops.rs` wholesale (predicates→mask, mask algebra, ternlog,
155155
masked reductions, care-masked register match, blend) with its tests.
156156
`simd_int_ops.rs` is integer arithmetic/conversion again. Every moved
157-
`pub fn` (31/31) still re-exports through `ndarray::simd`; the
158-
`ndarray::simd_int_ops::<mask fn>` MODULE paths are gone (`simd_int_ops`
159-
is `pub mod`, so that IS a public-path removal — "public surface
160-
unchanged" was an overclaim, C2). The one known consumer of the module
161-
path (`lance-graph-planner` `examples/dcr_w0_replay_budget.rs`) is moved
162-
to the facade in the lance-graph PR, not in this one.
157+
`pub fn` (31/31) still re-exports through `ndarray::simd`, AND the 13
158+
mask functions that were public on master as `ndarray::simd_int_ops::<f>`
159+
(`simd_int_ops` is `pub mod`, so those were public paths — the first
160+
draft dropped them and called the surface "unchanged", C2; CodeRabbit
161+
round 2 caught the downstream break) are re-exported from `simd_int_ops`
162+
as a compatibility surface, verified complete by diffing master's
163+
`pub fn` list against HEAD's `pub fn` + `pub use` set (0 missing). The
164+
canonical path is the facade; `lance-graph-planner`
165+
`examples/dcr_w0_replay_budget.rs` moves to it in the lance-graph PR.
163166
2. **`tools/gen_ternlog_bodies.py`** — Shannon-lowers each 8-bit table into
164167
two 2-input tables (`f = (!c & T0) | (c & T1)`), ≤ 7 ops (the naive
165168
minterm form was up to 36), self-checks all 256 tables in Python, and

.claude/knowledge/simd-codegen-oracle/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ Measured on x86_64 + `x86-64-v3`, rustc 1.95.0. Full narrative in
7878
| `cross_lane_reverse_u8x64` | 9 | 0 | `vbroadcasti128`/`vpshufb`/`vpermq` — invented a cross-lane permute from a scalar index loop |
7979
| **`rot_u64x8`** | **0** | 8 | scalar `rorq %cl`, one per lane |
8080
| **`rot_u64x4`** | **0** | 4 | scalar `rorq %cl`, one per lane |
81+
| **`blake2b_g_u64x8`** | 22 | ~88 | packed leading add; scalar through all four rotates |
82+
| `gather_lookup_u8` | 0 | 0 | `movzbl` chain, no arithmetic |
83+
| `serial_dependent_chain` | 0 | 27 | loop-carried dependency |
8184

8285
Group F — the mask family (PR #306), measured 2026-09-14 on rustc 1.98.1
8386
through the shipped library methods. Two runs: array polyfill first, then
@@ -100,9 +103,6 @@ The two "mixed" rows are the instructive ones: a shape can be *mostly*
100103
packed and still carry a scalar peel, and the method's doc comment had
101104
claimed a clean lowering it never had. Measure the shipped symbol, not the
102105
look-alike.
103-
| **`blake2b_g_u64x8`** | 22 | ~88 | packed leading add; scalar through all four rotates |
104-
| `gather_lookup_u8` | 0 | 0 | `movzbl` chain, no arithmetic |
105-
| `serial_dependent_chain` | 0 | 27 | loop-carried dependency |
106106

107107
**The headline:** LLVM vectorizes far more than intuition suggests —
108108
including cross-lane permutes, widening converts, and saturating

.github/workflows/ci.yaml

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,18 +250,31 @@ jobs:
250250
# to host build scripts. Combined with explicit `--target` (so cargo
251251
# distinguishes host from target even when they're the same triple),
252252
# this gives us "v4 for our crate, baseline for build scripts."
253+
#
254+
# # Why `--config .cargo/config-v4.toml` and NOT that env var (2026-09-14):
255+
#
256+
# cargo JOINS every matching `target.<triple>.rustflags` and
257+
# `target.<cfg>.rustflags` entry, and the last `-Ctarget-cpu` wins.
258+
# Measured with `cargo -v`: the env var form passed `x86-64-v4` and THEN
259+
# `.cargo/config.toml`'s cfg-keyed `x86-64-v3`, so this job had been
260+
# checking the AVX2 arm while named "tier4-avx512-check" — the codegen
261+
# witness on PR #306 found 0 `vpternlog` in a "v4" build. `--config`
262+
# is the same cfg key at higher precedence, placed LAST, so v4 wins;
263+
# explicit `--target` still keeps build scripts on the host baseline.
253264
runs-on: ubuntu-latest
254265
name: tier4-avx512-check
255-
env:
256-
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-D warnings -Ctarget-cpu=x86-64-v4"
257266
steps:
258267
- uses: actions/checkout@v4
259268
- uses: dtolnay/rust-toolchain@stable
260269
- uses: Swatinem/rust-cache@v2
261270
- name: cargo check (v4 / AVX-512 dispatch arm)
262-
run: cargo check --target=x86_64-unknown-linux-gnu -p ndarray --features approx,serde,rayon
271+
run: cargo --config .cargo/config-v4.toml check --target=x86_64-unknown-linux-gnu -p ndarray --features approx,serde,rayon
263272
- name: cargo check (v4 / AVX-512 + hpc-extras)
264-
run: cargo check --target=x86_64-unknown-linux-gnu -p ndarray --features approx,serde,rayon,hpc-extras
273+
run: cargo --config .cargo/config-v4.toml check --target=x86_64-unknown-linux-gnu -p ndarray --features approx,serde,rayon,hpc-extras
274+
- name: prove the arm actually selected (a v4 build must emit vpternlog)
275+
run: |
276+
cargo --config .cargo/config-v4.toml rustc --profile ci-codegen --example ternlog_codegen_probe --target=x86_64-unknown-linux-gnu -- --emit=asm -C debuginfo=0
277+
grep -c vpternlog target/x86_64-unknown-linux-gnu/ci-codegen/examples/ternlog_codegen_probe-*.s
265278
266279
nightly-simd-polyfill:
267280
# TD-SIMD-9 from .claude/knowledge/simd-dispatch-architecture.md.

Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ required-features = ["std"]
5959
name = "ternlog_amortization_probe"
6060
required-features = ["std"]
6161

62+
[[example]]
63+
name = "ternlog_codegen_probe"
64+
required-features = ["std"]
65+
6266
[[example]]
6367
name = "amx_gemm_bench"
6468
required-features = ["std"]
@@ -452,6 +456,7 @@ exclude = [
452456
"crates/burn",
453457
"crates/wasm-simd-parity",
454458
"crates/neon-simd-parity",
459+
"crates/simd-masking-parity",
455460
# Cross-repo: its dev-dep is a PATH into a lance-graph sibling checkout.
456461
# In-workspace, a missing sibling would fail resolution for EVERY member.
457462
"crates/sigker-parity",
@@ -484,6 +489,19 @@ cblas-sys = { version = "0.1.4", default-features = false }
484489
[profile.bench]
485490
debug = true
486491

492+
# The codegen-witness profile (`scripts/codegen-witness.sh`): optimized so LLVM
493+
# actually performs the vectorization the SIMD realization matrix certifies,
494+
# with no debuginfo and no LTO so it stays a TINY build of one example, not a
495+
# release build of the crate. The semantic arms of the matrix run at opt-level
496+
# 0 instead — bits are proven cheaply there, machine code is proven here.
497+
[profile.ci-codegen]
498+
inherits = "release"
499+
opt-level = 3
500+
debug = 0
501+
lto = false
502+
incremental = false
503+
panic = "abort"
504+
487505
[profile.test.package.numeric-tests]
488506
opt-level = 2
489507
[profile.test.package.blas-tests]

crates/neon-simd-parity/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ mod checks {
370370
if (a - b).to_array() != core::array::from_fn(|i| a_arr[i].wrapping_sub(b_arr[i])) {
371371
return Err(0x30D);
372372
}
373-
if !(a == a) || a == b {
373+
if !(a == U64x8::from_array(a_arr)) || a == b {
374374
return Err(0x30E);
375375
}
376376
Ok(())
@@ -438,7 +438,7 @@ mod checks {
438438
if a.to_i16_array() != core::array::from_fn(|i| a_arr[i] as i16) {
439439
return Err(0x40C);
440440
}
441-
if !(a == a) || a == b {
441+
if !(a == I32x16::from_array(a_arr)) || a == b {
442442
return Err(0x40D);
443443
}
444444
Ok(())
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# simd-masking-parity — ONE masking parity program, compiled under every SIMD
2+
# realization the matrix certifies (scalar / v3-avx2 / v4-avx512 / native /
3+
# nightly) × every platform (linux-x86 / linux-arm64 / macos-arm64 /
4+
# wasm32-simd128 / wasm32-without-simd128 = the scalar arm).
5+
#
6+
# It calls the SHIPPED `ndarray::simd` facade only — never a backend module,
7+
# never an intrinsic, never a `cfg(target_feature)` on lane data — and checks
8+
# every result against a bit-serial reference computed in the same module.
9+
# Whatever backend `simd.rs` selected for the build is the thing under test;
10+
# the program has no idea which one that is, and must not.
11+
#
12+
# Ships as an rlib + cdylib (`selfcheck()` for the node harness on wasm) and a
13+
# bin (`main.rs`, exits non-zero on the first mismatch, for native / qemu).
14+
# EXCLUDED from the workspace (root Cargo.toml `exclude`) so it has zero effect
15+
# on ordinary builds; `scripts/masking-parity.sh` builds it via --manifest-path.
16+
[package]
17+
name = "simd-masking-parity"
18+
version = "0.0.0"
19+
edition = "2021"
20+
publish = false
21+
22+
[lib]
23+
crate-type = ["cdylib", "rlib"]
24+
25+
[[bin]]
26+
name = "simd-masking-parity"
27+
path = "src/main.rs"
28+
29+
[dependencies]
30+
# `simd` + `simd_masking_ops` need only ndarray's `std` feature.
31+
ndarray = { path = "../..", default-features = false, features = ["std"] }
32+
33+
[features]
34+
# The nightly realization: forwards to ndarray's portable-simd backend.
35+
nightly-simd = ["ndarray/nightly-simd"]
36+
37+
[profile.release]
38+
panic = "abort"
39+
opt-level = 2

crates/simd-masking-parity/run.mjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Node harness for the wasm arms of the masking parity matrix: instantiate the
2+
// .wasm and assert selfcheck() == 0. A nonzero rc names the failing group+op
3+
// (see src/lib.rs, `Code`).
4+
import { readFileSync } from 'node:fs';
5+
const wasmPath = process.argv[2];
6+
if (!wasmPath) {
7+
console.error('usage: node run.mjs <path-to.wasm>');
8+
process.exit(2);
9+
}
10+
const { instance } = await WebAssembly.instantiate(readFileSync(wasmPath), {});
11+
const rc = instance.exports.selfcheck();
12+
if (rc === 0) {
13+
console.log('masking parity: OK (selfcheck rc=0)');
14+
process.exit(0);
15+
} else {
16+
console.error(`masking parity: FAIL — selfcheck rc=0x${rc.toString(16)} (see crates/simd-masking-parity/src/lib.rs)`);
17+
process.exit(1);
18+
}

0 commit comments

Comments
 (0)