Skip to content

Commit b0aea59

Browse files
committed
simd: a codegen oracle, and the u64 rotate it immediately found
Adds `crates/simd-codegen-oracle` (+ script, baseline, CI job) and removes blake3's C/asm FFI. The oracle answers "does this need intrinsics?" with an assembly measurement instead of intuition — and its first run falsified the design assumptions it was built to check. ## Why TD-T22 measured that under the pinned `-Ctarget-cpu=x86-64-v3`, the "scalar polyfill" SIMD types already compile to packed AVX2 at the instruction floor. A PR had hand-written 700 lines of intrinsics against that nonexistent gap. Nothing in the repo could have caught it: there is no codegen check, only correctness parity harnesses. ## What the oracle measured 13 probes, three groups. Group A expected to vectorize, Group B expected NOT to, Group C (u64 rotate) deliberately unclassified. Group A — all vectorized, as expected. `arx_rounds_u32x16` (10-round ChaCha double-round): 52 packed, 0 scalar arithmetic on lane data. Group B — **3 of 5 predictions were WRONG.** LLVM vectorized: * saturating_abs_i8x32 → vpxor/vpsubsb/vpblendvb, i.e. it synthesized the VPABSB abs+clamp trick on its own * widening_u16_to_f32 → vpmovzxwd + vcvtdq2ps * cross_lane_reverse_u8x64 → vbroadcasti128 + vpshufb + vpermq — a cross-lane permute, from a scalar index loop Only serial_dependent_chain (loop-carried dependency) and gather_lookup_u8 stayed scalar. The "cross-lane/widening/saturating obviously needs intrinsics" intuition is measurably false. Group C — **the u64 rotate does NOT vectorize.** rot_u64x8 / rot_u64x4: 0 packed, one scalar `rorq %cl` per lane. blake2b_g_u64x8: the leading `a+b` goes packed (vpaddq), then LLVM extracts every lane to a GPR and stays scalar through all four rotate stages — including the byte-granular amounts 32/24/16, which at u32 width fold to vpshufb. AVX2 has vpsllq/vpsrlq and LLVM applies exactly that shift-or to u32 rotate-by-12/7; it declines to at 64-bit width. That matters because BLAKE2b is a 64-bit ARX cipher and argon2 uses BLAKE2b. The crate has 8 `rotate_left` methods (all u32) and zero `rotate_right` at any width, so argon2's kernel is unexpressible today. This is the first intrinsic override meeting the entry criterion: a probe proving the generic form fails. Same crate, same week, opposite answers for u32 and u64. That contrast is the argument for the oracle. ## blake3: no more C Operator directive — C is a contamination of ndarray, and vendored builds fail without a C toolchain. blake3's default build ran cc over c/blake3_{sse2,sse41,avx2,avx512}_x86-64_unix.S: measured 33 .o files plus libblake3_avx512_assembly.a, with blake3_*_ffi cfgs set. Now pinned to `default-features = false, features = ["pure"]`, which routes build.rs to build_sse2_sse41_avx2_rust_intrinsics() ("No C code to compile here"). Verified after `cargo clean -p blake3`: the only cfgs actually SET are blake3_{sse2,sse41,avx2}_rust; 0 object files, 0 archives, no cc. (The _ffi names still appear in the build output as rustc-check-cfg DECLARATIONS — cargo listing valid cfg names — not assignments. A grep that cannot tell the two apart reports a false positive here.) Remaining cc build-deps in the lockfile (cmake, openblas-build, openssl-sys) are reachable only through the optional `blas` feature, not `default = ["std", "hpc-extras"]`. blake3 was the only C on the default path — which is why vendoring broke for everyone. ## Honesty rule baked into the tool The analyzer separates "scalar arithmetic on lane data" from loop control, so a trip-counter `decl` is never reported as scalar lane work. That exact overclaim was made and corrected earlier this week; the tool cannot reproduce it. ## Docs * .claude/knowledge/simd-one-spec-design.md — design for collapsing 5 backends (31 macro-generated + 57 hand-written types, 13,253 LoC, three authoring strategies) into one spec, with the oracle as the entry criterion for intrinsic overrides. Includes the table of my three wrong predictions. * .claude/knowledge/crypto-lane-status.md — u32 ARX proven optimal (ChaCha20/BLAKE3 need no SIMD work); u64 ARX absent and now measured as genuinely needing intrinsics.
1 parent 2c8af9a commit b0aea59

10 files changed

Lines changed: 1533 additions & 2 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# The crypto lane — what is proven, what is missing
2+
3+
> **Status: MEASURED, 2026-07-28.** Every claim here is backed by an
4+
> assembly probe or a grep across all six backends. No estimates.
5+
6+
## READ BY:
7+
- Anyone building on `crates/encryption` (argon2 / BLAKE3 / ChaCha20 / AEAD)
8+
- Anyone asked "do we need new SIMD work for cipher X?"
9+
10+
---
11+
12+
## The u32 ARX lane: PROVEN, at the instruction floor
13+
14+
`crate::simd::U32x16` Add / BitXor / `rotate_left` is the ChaCha20 and BLAKE3
15+
mixing triple. TD-T22 measured it (`td-t22-asm-investigation.md`): the
16+
scalar-storage polyfill compiles to **8 `vpaddd` for 64 u32 lanes — the AVX2
17+
instruction floor** — with no scalar op touching lane data, and
18+
`rotate_left(16)` strength-reduced to `vpshufb`, cheaper than the
19+
`shl|shr|or` triple an intrinsic emits.
20+
21+
**Consequence: ChaCha20 and BLAKE3 need no new SIMD work.** The lane they
22+
ride is already optimal on the default tier.
23+
24+
The float side is likewise done: `add_mul_f32` emits real `vfmadd213ps`
25+
one rounding, mantissa preserved — and `array_chunks` / `array_windows`
26+
(+ `_checked`) already exist as the slice-level primitives in `simd_ops.rs`.
27+
28+
## The u64 ARX lane: DOES NOT EXIST
29+
30+
Measured across `simd_avx512`, `simd_avx2`, `simd_scalar`, `simd_neon`,
31+
`simd_wasm`, and `simd_nightly`: **zero `rotate_left` or `rotate_right`
32+
methods on `U64x8` or `U64x4`. On any backend.**
33+
34+
Whole-crate census of rotate methods — `grep -rhoE "fn rotate_(left|right)" src/`:
35+
36+
| method | count |
37+
|---|---|
38+
| `rotate_left` | 8 (all u32 lanes) |
39+
| `rotate_right` | **0, at any width** |
40+
41+
*(Search validated by control: the same pattern finds `U32x16::rotate_left`
42+
in all four backends that define it, so the empty u64 result is a true
43+
negative and not a broken query.)*
44+
45+
Note the second row. BLAKE2b specifies **right** rotations. `rotr(n)` is
46+
expressible as `rotl(64 - n)`, so this is a naming/API gap rather than a
47+
mathematical one — but a caller writing BLAKE2b today has neither.
48+
49+
This matters because **BLAKE2b is a 64-bit ARX cipher**, and BLAKE2b is what
50+
**argon2** uses. Its G-function is
51+
`a+=b; d=(d^a).rotr(32); c+=d; b=(b^c).rotr(24); a+=b; d=(d^a).rotr(16); c+=d; b=(b^c).rotr(63)`
52+
— four u64 rotates per mixing step, none of which the crate can express
53+
today.
54+
55+
`crates/encryption` currently references exactly one SIMD type:
56+
`simd::U32x16`.
57+
58+
**This is a real gap, unlike the u32 one.** The distinction is the whole
59+
lesson of TD-T22: a missing *source-level* lowering is not a gap when LLVM
60+
already emits the instruction; a missing *method* is a gap regardless of
61+
what LLVM would do with it.
62+
63+
### ANSWERED by the oracle: no, it does not vectorize
64+
65+
Measured on x86_64 v3 via `crates/simd-codegen-oracle`:
66+
67+
| probe | packed | scalar lane-arith | verdict |
68+
|---|---|---|---|
69+
| `rot_u64x8` | **0** | 8 | one scalar `rorq %cl` per lane |
70+
| `rot_u64x4` | **0** | 4 | one scalar `rorq %cl` per lane |
71+
| `blake2b_g_u64x8` | 22 | ~88 | leading add only |
72+
73+
`blake2b_g_u64x8` in detail: the opening `a = a + b` vectorizes (`vpaddq`
74+
across both ymm halves). The moment a rotate is needed LLVM extracts every
75+
lane to a GPR (`vmovq` / `vpextrq`) and stays scalar (`rorxq`/`addq`/`xorq`)
76+
through all four rotate stages, going packed again only for the return
77+
struct's reassembly.
78+
79+
Two things make this a genuine finding rather than a shrug:
80+
81+
1. **The byte-granular amounts stay scalar too.** Rotates by 32/24/16 are
82+
byte-aligned — the class LLVM folds to `vpshufb` for u32 — yet all four
83+
BLAKE2b amounts (32/24/16/63) lowered identically to scalar `rorxq`.
84+
2. **The mechanism exists and is unused.** AVX2 has `vpsllq`/`vpsrlq`, and
85+
LLVM *does* use exactly that shift-or composition for u32's rotate-by-12
86+
and rotate-by-7. It has the tools and declines to apply them at 64-bit
87+
width.
88+
89+
**So the u64 ARX lane is the crate's first intrinsic override that meets the
90+
entry criterion** (a probe proving the generic form fails). AVX-512:
91+
`_mm512_rorv_epi64` / `VPROLVQ`, one instruction. AVX2 / NEON / wasm: write
92+
the `vpsllq`/`vpsrlq`-shaped shift-or explicitly, since LLVM will not.
93+
94+
Contrast with the u32 lane, where hand-writing intrinsics *lost* to the
95+
optimizer. Same crate, same week, opposite answers — which is the argument
96+
for the oracle existing at all.
97+
98+
## Decision gates (operator, not engineering)
99+
100+
Neither of these is blocked on SIMD work:
101+
102+
1. **FIPS.** If any deployment needs FIPS-adjacent claims, BLAKE3 is out and
103+
SHA-384 stays the KDF hash. Settle before investing in a BLAKE3 lane.
104+
2. **`x448` audit provenance.** Gates whether an X25519 port is worth it.
105+
The tripwire test already on master
106+
(`channel::tests::low_order_peer_keys_are_refused_and_honest_ones_are_not`)
107+
asserts both halves, so a mechanical port cannot silently drop RFC 7748's
108+
contributory check — `x448::x448()` returns `Option` where
109+
`x25519_dalek::x25519()` returns a bare `[u8; 32]`.
110+
111+
## Summary
112+
113+
| lane | status | blocks |
114+
|---|---|---|
115+
| u32 ARX (ChaCha20, BLAKE3) | **proven optimal** | nothing |
116+
| f32 FMA (`add_mul`) | **proven fused** | nothing |
117+
| slice chunking | **exists** | nothing |
118+
| **u64 ARX (BLAKE2b → argon2)** | **absent on all 6 backends** | argon2 SIMD |
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# One spec, N backends — collapsing the SIMD type surface
2+
3+
> **Status: DESIGN.** Not implemented. The enabling measurement is done
4+
> (TD-T22, merged 2026-07-28); the migration is a staged epic, not a PR.
5+
6+
## READ BY:
7+
- Anyone about to hand-write a lane type in `src/simd_<arch>.rs`
8+
- Anyone proposing to "add the missing types" to a backend
9+
- `simd-savant`, `truth-architect`
10+
11+
---
12+
13+
## The measurement that makes this possible
14+
15+
TD-T22 (`.claude/knowledge/td-t22-asm-investigation.md`) established, with
16+
assembly evidence: **under a pinned `target-cpu` baseline, LLVM compiles a
17+
scalar-shaped lane loop to optimal packed SIMD.** On the ChaCha20 ARX triple
18+
over the scalar-storage `U32x16`, the emitted code hits the AVX2 instruction
19+
floor — 8 `vpaddd` for 64 u32 lanes — with no scalar op touching lane data,
20+
and `rotate_left(16)` strength-reduced to `vpshufb`, which is *cheaper* than
21+
the `shl|shr|or` triple a hand-written intrinsic emits.
22+
23+
The consequence is not "the polyfill is fine." It is: **for lane-wise
24+
operations, the scalar spec IS the implementation, on every backend.**
25+
26+
## What exists today
27+
28+
| backend | types from a macro | hand-written structs |
29+
|---|---|---|
30+
| `simd_avx2` | 12 | 8 |
31+
| `simd_avx512` | 0 | 21 |
32+
| `simd_scalar` | 19 | 6 |
33+
| `simd_neon` | 0 | 15 |
34+
| `simd_wasm` | 0 | 7 |
35+
| **total** | **31** | **57** |
36+
37+
Plus 72 `impl_bin_op!`-family invocations in `simd_avx512` layered on top of
38+
its hand-written structs. **13,253 LoC across five files, three different
39+
authoring strategies, and the same logical type written five times.**
40+
41+
Three strategies for one problem:
42+
1. `avx2` / `scalar` — type-generating macros (`avx2_int_type!`, `impl_int_type!`)
43+
2. `avx512` — hand-written struct + operator-generating macros
44+
3. `neon` / `wasm` — fully hand-written
45+
46+
This is why adding one lane type is a five-file change, why ten AVX2 int
47+
types are still "unlowered," and why the same boilerplate was hand-typed
48+
twice for `U16x16` and then again for `U32x8`.
49+
50+
## The design
51+
52+
One declaration per logical type. Backends are *generated*, not authored.
53+
54+
```rust
55+
simd_type! {
56+
name: U32x16, elem: u32, lanes: 16, repr: align(64),
57+
58+
// Lane-wise ops. Emitted as the scalar loop form for EVERY backend.
59+
// LLVM vectorizes them under the pinned target-cpu baseline; the
60+
// codegen oracle proves it, per target, in CI.
61+
lanewise: [
62+
add(wrapping), sub(wrapping), mul(wrapping),
63+
and, or, xor, not,
64+
rotate_left, shl(zero_on_overshift), shr(zero_on_overshift),
65+
reduce_sum(wrapping),
66+
],
67+
68+
// ESCAPE HATCH. A per-backend intrinsic override may be added ONLY
69+
// with an oracle probe showing the generic form does not vectorize,
70+
// or a measured win the generic form cannot reach.
71+
intrinsic: {
72+
avx512: { rotate_left: "_mm512_rolv_epi32" }, // VPROLVD, 1 instr
73+
},
74+
}
75+
```
76+
77+
**The entry criterion is the whole point.** Today "should this be an
78+
intrinsic?" is answered by intuition, and intuition said yes to a case where
79+
LLVM was already at the instruction floor. Under this design the question is
80+
answered by `scripts/codegen-oracle.sh`: if the generic form vectorizes, the
81+
override is rejected; if it doesn't, the override is justified and the probe
82+
that justified it is committed alongside.
83+
84+
## What must NOT be generated — MEASURED, and my predictions were wrong
85+
86+
I predicted five classes LLVM could not synthesize from scalar source. The
87+
oracle ran them. **Three of the five vectorized anyway.** Recorded here
88+
because the wrong list is more instructive than the right one: the intuition
89+
that "cross-lane / widening / saturating obviously needs intrinsics" is
90+
exactly the intuition that produced a 700-line PR against a nonexistent gap.
91+
92+
| predicted scalar | actual | what LLVM emitted |
93+
|---|---|---|
94+
| `saturating_abs_i8x32` | **VECTORIZED** (4 packed / 0 scalar) | `vpxor``vpsubsb` (saturating 0−x) → `vpblendvb` — the exact abs+clamp trick the VPABSB correction documents, synthesized on its own |
95+
| `widening_u16_to_f32` | **VECTORIZED** (6 packed / 0 scalar) | `vpmovzxwd` + `vcvtdq2ps` |
96+
| `cross_lane_reverse_u8x64` | **VECTORIZED** (9 packed / 0 scalar) | `vbroadcasti128` + `vpshufb` + `vpermq` — it invented a cross-lane permute from a scalar index loop |
97+
| `serial_dependent_chain` | scalar, as predicted | GPR `rorxl`/`addl`/`xorl` chain — a loop-carried dependency cannot vectorize |
98+
| `gather_lookup_u8` | scalar, as predicted | pure `movzbl`/`movb`; no arithmetic at all |
99+
100+
So the genuine "cannot be generated" list is much shorter than assumed:
101+
102+
- **Loop-carried dependencies.** Structural; no compiler escapes them.
103+
- **Gather / table lookup.** No contiguous load to widen.
104+
- **u64 lane rotates** — see below. The one case where LLVM has the
105+
mechanism and declines to use it.
106+
107+
**Everything else measured so far is free.** `U16x16`'s hand-written
108+
`permute2x128`/`blend_epi32` may still be justified — they are *explicit API
109+
surface* consumers call directly, not something to be synthesized — but the
110+
claim that cross-lane work inherently requires intrinsics is false.
111+
112+
## The u64 rotate — the first earned intrinsic override
113+
114+
Measured (`rot_u64x8`, `rot_u64x4`, `blake2b_g_u64x8`):
115+
116+
- `rot_u64x8` / `rot_u64x4`: **0 packed.** Each lane's `u64::rotate_right(n)`
117+
becomes a scalar GPR `rorq %cl, reg`, one per lane.
118+
- `blake2b_g_u64x8`: the leading `a = a + b` vectorizes (`vpaddq` over both
119+
ymm halves); the moment a rotate appears LLVM extracts every lane
120+
(`vmovq`/`vpextrq`) and stays scalar (`rorxq`/`addq`/`xorq`) through all
121+
four rotate stages, reassembling only at the return.
122+
123+
The striking part: **the byte-granular amounts 32/24/16 stay scalar here**,
124+
while the same class of amounts (16, 8) fold to `vpshufb` for u32. And AVX2
125+
has `vpsllq`/`vpsrlq` — the exact shift-or mechanism LLVM *does* apply to
126+
u32's rotate-by-12 and rotate-by-7. It has the tools and does not reach for
127+
them on u64.
128+
129+
This is the crate's first intrinsic override that meets the entry criterion:
130+
a probe showing the generic form does not vectorize. `_mm512_rorv_epi64`
131+
(`VPROLVQ`) is a single instruction on AVX-512; AVX2/NEON/wasm get the
132+
shift-or composition written explicitly.
133+
134+
## Staged migration (not one PR)
135+
136+
1. **Oracle first.** `crates/simd-codegen-oracle` + CI job. Without it the
137+
entry criterion is unenforceable and this design is just a refactor.
138+
2. **Characterize.** Run the oracle across x86-64-v3 / v4 / aarch64 / wasm32.
139+
Produce the per-target table of what vectorizes and what doesn't. That
140+
table *is* the specification of which intrinsic overrides are legitimate.
141+
3. **Pilot on one type family.** The u32 lanes (`U32x8`, `U32x16`) — smallest
142+
blast radius, best-understood semantics, already measured.
143+
4. **Migrate the 31 macro-generated types.** Mechanical; the macros already
144+
prove the shape is regular.
145+
5. **Migrate the 57 hand-written types**, keeping every intrinsic the oracle
146+
justifies and deleting the rest. Expect the survivors to be concentrated
147+
in the cross-lane / widening / saturating families above.
148+
149+
## Invariants the design must preserve
150+
151+
- **`repr(align(64))` on every lane type.** Nine sites across
152+
`scalar`/`neon`/`wasm` carry it; it is a cacheline guarantee, not an
153+
accident. A `repr(transparent)` wrapper over `__m256i` LOSES it (measured:
154+
`U32x8` size 64→32, `U32x16` align 64→32). The spec must emit `align(64)`
155+
by default.
156+
- **One API on every backend.** The generated surface is identical by
157+
construction — which structurally eliminates the class of bug where a
158+
method exists on x86_64 and nowhere else.
159+
- **`U32x8` must not be `U32x16`'s building block** (operator ruling,
160+
2026-07-28). Composition, where needed, is an implementation detail of the
161+
generated backend, never a public half-width type standing in for the lane
162+
the substrate actually uses.
163+
- **No `core::simd` or `hpc::` in a public signature; consumers only ever
164+
name `crate::simd::*`.** Backend-internal construction uses concrete
165+
backend types — a backend file is compiled even when its dispatch arm is
166+
not selected.
167+
168+
## What this buys
169+
170+
- Adding a lane type: one declaration instead of a five-file change.
171+
- Ten currently-unlowered AVX2 int types: free.
172+
- The "is this fast enough?" argument: replaced by a CI check.
173+
- ~13k LoC of hand-maintained backend code: substantially reduced, with the
174+
remainder being exactly the intrinsics that earn their place.

.github/workflows/ci.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,22 @@ jobs:
163163
- name: NEON SIMD parity (cross-build + qemu run)
164164
run: ./scripts/neon-parity.sh
165165

166+
simd-codegen-oracle:
167+
# Proves (in both directions) what `.cargo/config.toml`'s
168+
# `-Ctarget-cpu=x86-64-v3` baseline actually does to this crate's
169+
# "scalar polyfill" SIMD storage types: Group A probes must show packed
170+
# AVX2 codegen from scalar *source*, Group B probes must show none, and
171+
# Group C probes (the u64 rotate gap) are reported without a pass/fail
172+
# verdict. See crates/simd-codegen-oracle/src/main.rs and
173+
# scripts/codegen_oracle_analyze.py for the full picture.
174+
runs-on: ubuntu-latest
175+
name: simd-codegen-oracle/instruction-histogram
176+
steps:
177+
- uses: actions/checkout@v4
178+
- uses: dtolnay/rust-toolchain@stable
179+
- name: SIMD codegen oracle (build --emit asm + classify + baseline compare)
180+
run: ./scripts/codegen-oracle.sh
181+
166182
tests:
167183
runs-on: ubuntu-latest
168184
needs: pass-msrv
@@ -388,6 +404,7 @@ jobs:
388404
- nostd
389405
- wasm_simd
390406
- neon_simd
407+
- simd-codegen-oracle
391408
- tests
392409
- native-backend
393410
- hpc-stream-parallel

Cargo.toml

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,32 @@ matrixmultiply = { version = "0.3.2", default-features = false, features=["cgemm
185185
# If not, leave the `optional = true` + `std`-feature pinning in place.
186186
#
187187
# =====================================================================
188-
blake3 = { version = "1", optional = true }
188+
# `default-features = false` + `pure`: NO C/ASM FFI. Operator directive
189+
# 2026-07-28 — "I don't want any ffi with c".
190+
#
191+
# blake3's default build runs `cc::Build` over `c/blake3_{sse2,sse41,avx2,
192+
# avx512}_x86-64_unix.S` and links them (measured before this change: 33 `.o`
193+
# files plus `libblake3_avx512_assembly.a` in target/, with `blake3_*_ffi`
194+
# cfgs set). The `pure` feature routes build.rs to
195+
# `build_sse2_sse41_avx2_rust_intrinsics()` — its own comment: "No C code to
196+
# compile here" — which only sets `blake3_{sse2,sse41,avx2}_rust` cfgs and
197+
# lets the normal cargo build compile the Rust intrinsics modules.
198+
#
199+
# What `pure` costs (build.rs:344-371): the hand-tuned x86-64 assembly is
200+
# replaced by Rust intrinsics, the AVX-512 path is dropped entirely, and the
201+
# aarch64 NEON C intrinsics are dropped. What it removes: every byte of C.
202+
#
203+
# `default-features = false` also drops blake3's own `std` (which only gates
204+
# `constant_time_eq/std`); this crate's `std` feature is what pulls blake3 in
205+
# at all, so nothing here needs blake3's.
206+
#
207+
# NOTE: `pure` still leaves blake3's Rust SSE2/SSE4.1/AVX2 intrinsics — a
208+
# second SIMD surface beside `ndarray::simd`, which the matryoshka pattern
209+
# exists to prevent. Closing that means implementing BLAKE3's compression on
210+
# `ndarray::simd::U32x16` (it is a ChaCha-derived u32 ARX kernel, and that
211+
# lane is proven at the AVX2 instruction floor — see
212+
# `.claude/knowledge/td-t22-asm-investigation.md`). Tracked, not done here.
213+
blake3 = { version = "1", optional = true, default-features = false, features = ["pure"] }
189214

190215
# p64 + fractal — specialized convergence / manifold math. Gated behind
191216
# `hpc-extras` since they pull in a dep tree burn-ndarray doesn't need.
@@ -402,7 +427,13 @@ members = [
402427
"ndarray-rand",
403428
"crates/*",
404429
]
405-
exclude = ["crates/burn", "crates/wasm-simd-parity", "crates/neon-simd-parity", "vendor/chacha20"]
430+
exclude = [
431+
"crates/burn",
432+
"crates/wasm-simd-parity",
433+
"crates/neon-simd-parity",
434+
"crates/simd-codegen-oracle",
435+
"vendor/chacha20",
436+
]
406437
default-members = [
407438
".",
408439
"ndarray-rand",

0 commit comments

Comments
 (0)