Skip to content

Replace stdlib keccak with XKCP for ~1.4x speedup - #28

Merged
koko1123 merged 3 commits into
mainfrom
keccak-xkcp-optimization
Mar 3, 2026
Merged

Replace stdlib keccak with XKCP for ~1.4x speedup#28
koko1123 merged 3 commits into
mainfrom
keccak-xkcp-optimization

Conversation

@koko1123

@koko1123 koko1123 commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Vendors XKCP (eXtended Keccak Code Package) optimized C/assembly implementations
  • Integrates via Zig build system with comptime CPU feature detection (AVX2, AVX-512 on x86_64; optimized C on aarch64)
  • Keeps Zig stdlib for comptime hashing (C FFI unavailable at comptime)
  • All 12 keccak tests + full test suite pass unchanged

Benchmark Results (Apple Silicon)

Benchmark Before (ns/op) After (ns/op) Speedup
keccak256_empty 614 350 1.75x
keccak256_32b 594 356 1.67x
keccak256_256b 1189 705 1.69x
keccak256_1kb 3275 2785 1.18x
keccak256_4kb 11351 10636 1.07x

Hyperfine comparison vs Voltaire's keccak-asm (Rust FFI)

32 bytes (most common Ethereum hash size) × 1M iterations:

  • XKCP (ours): 365ms
  • Rust keccak-asm (Voltaire): 353ms (within 3%)
  • Zig stdlib: 456ms

256 bytes × 1M iterations:

  • XKCP (ours): 708ms (fastest)
  • Rust keccak-asm: 730ms
  • Zig stdlib: 872ms

Achieves parity with Voltaire's Rust keccak-asm without requiring a Rust toolchain dependency.

Platform backends

Target Backend
aarch64 XKCP optimized 64-bit C (-O3)
x86_64 + AVX-512 XKCP AVX-512 assembly
x86_64 + AVX2 XKCP AVX2 assembly
x86_64 generic XKCP optimized 64-bit C
Other XKCP optimized 64-bit C

Test plan

  • zig build test passes (all 12 keccak tests + full suite)
  • zig build bench shows improvement
  • Hyperfine comparison vs Rust keccak-asm
  • Comptime hash == runtime hash verified
  • Test on x86_64 Linux (AVX2/AVX-512 assembly paths)

Summary by CodeRabbit

  • New Features

    • Added an XKCP Keccak-256 backend with CPU-specific optimized implementations and a new incremental hasher API.
    • Added a command-line benchmark tool for measuring Keccak performance.
  • Performance

    • Faster Keccak-256 via architecture-aware backends (AVX2/AVX-512/NEON/plain64) and fast-path sponge routines.
  • Chores

    • Build integration updates to register and link the new backends and install the benchmark executable.

@vercel

vercel Bot commented Mar 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
eth-zig Ready Ready Preview, Comment Mar 3, 2026 3:22am

@coderabbitai

coderabbitai Bot commented Mar 3, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR integrates the XKCP Keccak Code Package: adds CPU-optimized KeccakP-1600 backends (AVX‑512, AVX2, ARMv8‑A NEON, plain64), high-level sponge/hash C code, Zig FFI bindings, build wiring to select per-CPU sources, and a Zig benchmark CLI to exercise backends.

Changes

Cohort / File(s) Summary
Benchmark & CLI
bench/keccak_bench_cli.zig
Adds a standalone Zig benchmark CLI with optional args (size, iters, backend) that runs Keccak hashes via XKCP FFI or stdlib.
Build System
build.zig
Adds addXkcp() and per-CPU XKCP source selection, sets .link_libc = true for modules, and registers the keccak-bench-zig executable.
Zig FFI & Keccak API
src/keccak_xkcp.zig, src/keccak.zig
Adds XKCP-backed Zig module and switches runtime hashing to XKCP while retaining a stdlib Keccak256 alias for comptime/compatibility.
XKCP High-level (sponge/hash)
src/crypto/xkcp/high/*
Adds KeccakSponge.inc/.h/.c and KeccakHash.h/.c implementing the sponge state machine and Keccak_Hash APIs (initialize/update/final/squeeze).
XKCP Common Headers
src/crypto/xkcp/common/*
Adds core headers: SnP-common.h, align.h, brg_endian.h, config.h, SnP-Relaned.h (feature flags, alignment, endianness, re-laning macros).
Plain64 Backend
src/crypto/xkcp/plain64/*
Adds plain64 KeccakP-1600 implementation, macros, unrolling support, headers and C implementation (full API, fast loops, duplexing).
AVX2 Backend
src/crypto/xkcp/avx2/*
Adds AVX2 header and assembly implementation plus SnP wrapper header mapping AVX2 symbols to the KeccakP1600 API.
AVX512 Backend
src/crypto/xkcp/avx512/*
Adds AVX‑512 header and assembly implementation plus SnP wrapper header mapping AVX512 symbols to KeccakP1600 API.
ARMv8‑A NEON Backend
src/crypto/xkcp/armv8a/*
Adds ARMv8‑A NEON assembly implementation and SnP header exposing the KeccakP1600 NEON API.
Integration glue & headers
src/crypto/xkcp/.../KeccakP-1600-*-SnP.h
Adds per-backend SnP shim headers to present a unified KeccakP1600_state API to higher-level code.

Sequence Diagram(s)

sequenceDiagram
    participant User as Benchmark CLI
    participant Zig as Zig Keccak Module
    participant XKCP as XKCP C API
    participant Backend as CPU Backend

    User->>Zig: run benchmark(hash data, backend)
    activate Zig
    alt backend == "xkcp"
        Zig->>XKCP: KeccakWidth1600_Sponge / Keccak_HashInitialize/Update/Final
        activate XKCP
        XKCP->>Backend: permutation/absorb/extract (arch-optimized)
        Backend-->>XKCP: processed blocks (hash state)
        deactivate XKCP
        XKCP-->>Zig: 32-byte hash
    else backend == "stdlib"
        Zig->>Zig: std.crypto.hash.sha3.Keccak256.hash(data)
        Zig-->>Zig: 32-byte hash
    end
    Zig-->>User: return 32-byte hash
    deactivate Zig
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped in code with padding and pace,
AVX, NEON, plain64 in my chase,
Sponge in C and bindings in Zig,
Benchmarks hum — tails wag, circuits jig,
A small rabbit, pleased with the hash race.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Replace stdlib keccak with XKCP for ~1.4x speedup' clearly and concisely summarizes the main change: replacing the standard library Keccak implementation with XKCP for performance improvement.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch keccak-xkcp-optimization

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (4)
src/crypto/xkcp/avx512/KeccakP-1600-AVX512.s (1)

454-704: Add CI coverage for AVX512/AVX2 backend execution paths.

Given this backend is assembly-heavy and x86_64 Linux validation is still pending, a dedicated CI matrix for AVX2, AVX512, and fallback would reduce regression risk.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/crypto/xkcp/avx512/KeccakP-1600-AVX512.s` around lines 454 - 704, Add CI
jobs to exercise the AVX512/AVX2/fallback assembly paths by adding a Linux
x86_64 matrix (entries: baseline/fallback, avx2, avx512) that builds and runs
the test suite with appropriate compiler/assembler flags; for avx2 set
CFLAGS/ASFLAGS to include -mavx2 and for avx512 include -mavx512f/-mavx512dq and
use a runner or Docker image on x86_64. Ensure each job builds the target that
includes the KeccakP1600_AVX512_Permute_24rounds,
KeccakP1600_AVX512_Permute_12rounds and KeccakP1600_AVX512_Permute_Nrounds
symbols and runs the unit/integration tests that exercise those symbols (or a
small smoke test invoking the permute functions). Add conditional skipping or
feature-detection (grep /proc/cpuinfo or use gcc -march checks) so jobs fail
fast when the host lacks required ISA support, and document the matrix entries
in CI config.
src/keccak.zig (1)

12-18: API design clarification: direct Keccak256 usage won't benefit from XKCP speedup.

The hash() function correctly uses the XKCP backend, but Keccak256 is aliased to StdlibKeccak256 for backward compatibility. Code that directly instantiates Keccak256 for incremental hashing will use the slower stdlib implementation.

Consider adding a doc comment clarifying this tradeoff, or exposing xkcp.Hasher as a recommended alternative for incremental hashing:

📝 Proposed documentation enhancement
-/// For API compatibility: expose the stdlib type for code that uses the hasher directly.
+/// For API compatibility: expose the stdlib type for code that uses the hasher directly.
+/// Note: For best performance with incremental hashing, use `hashConcat()` or access
+/// `xkcp.Hasher` directly. Direct `Keccak256` usage falls back to the stdlib implementation.
 pub const Keccak256 = StdlibKeccak256;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/keccak.zig` around lines 12 - 18, The Keccak256 alias points to
StdlibKeccak256 so callers that instantiate Keccak256 for incremental hashing
won't get XKCP's faster implementation; update the API by either (a) adding a
concise doc comment on the Keccak256 declaration explaining that hash() uses the
XKCP backend and that incremental usage of Keccak256 will use the slower
StdlibKeccak256, or (b) export the XKCP incremental hasher (e.g., expose
xkcp.Hasher under a public name) and add a doc comment on Keccak256 pointing
users to xkcp.Hasher for high-performance incremental hashing; reference the
symbols Keccak256, StdlibKeccak256, hash(), xkcp, and xkcp.Hasher when making
the changes.
src/crypto/xkcp/high/KeccakHash.c (1)

17-18: Static analysis false positive: string.h is a standard C header.

The Clang error is due to the analyzer running without the system include paths. This is a standard C header that will be available when building with link_libc = true.

Note: The string.h include appears unused in this file (no memcpy, memset, etc. calls visible). It may be a remnant from the upstream XKCP source or included for consistency. Consider removing if build testing confirms it's unnecessary, but this is low priority.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/crypto/xkcp/high/KeccakHash.c` around lines 17 - 18, Remove the
unnecessary `#include` <string.h> from KeccakHash.c (it appears unused relative to
the functions implemented in that file, e.g., those declared in KeccakHash.h);
delete the include line and run full build/tests (with link_libc = true) to
confirm nothing breaks, and if any string functions are actually needed later,
re-add the include or add a brief comment explaining why it's retained.
src/keccak_xkcp.zig (1)

40-45: Consider handling empty input explicitly.

When data is an empty slice, data.ptr behavior in Zig is well-defined (returns a non-null but invalid pointer), but passing it to C code that may dereference it could be problematic depending on the XKCP implementation.

While XKCP likely handles zero-length inputs correctly, consider an explicit check for safety:

🔧 Optional: Add explicit empty input handling
 pub fn hash(data: []const u8) Hash {
     var result: Hash = undefined;
+    if (data.len == 0) {
+        // Handle empty input explicitly
+        const ret = KeccakWidth1600_Sponge(rate, capacity, `@as`([*]const u8, &[_]u8{}), 0, delimited_suffix, &result, 32);
+        std.debug.assert(ret == 0);
+        return result;
+    }
     const ret = KeccakWidth1600_Sponge(rate, capacity, data.ptr, data.len, delimited_suffix, &result, 32);
     std.debug.assert(ret == 0);
     return result;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/keccak_xkcp.zig` around lines 40 - 45, The hash function should
explicitly handle empty input before calling KeccakWidth1600_Sponge: detect when
data.len == 0 and pass a safe null pointer (or short-circuit to produce the
known zero-length hash) instead of blindly using data.ptr, then call
KeccakWidth1600_Sponge with that safe pointer and the same parameters (rate,
capacity, delimited_suffix, &result, 32) and assert ret == 0; update pub fn hash
and reference KeccakWidth1600_Sponge, data.ptr, data.len, delimited_suffix, and
result when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bench/keccak_bench_cli.zig`:
- Around line 21-34: The benchmark currently ignores invalid backend_str values
(only "xkcp" and "stdlib" handled) which yields silent success; update the
control flow in bench/keccak_bench_cli.zig so that after the existing if/else-if
branches you add an else branch that reports an error and exits non‑zero (e.g.,
print an error mentioning backend_str and valid options) so users are informed
when an unknown backend is passed—refer to backend_str, the eth.keccak.hash
branch and std.crypto.hash.sha3.Keccak256.hash branch to locate the decision
point and add the error handling there.

In `@bench/rust-keccak/src/lib.rs`:
- Around line 5-9: The FFI functions (rust_keccak_asm and rust_tiny_keccak)
currently create slices from raw pointers without null/validity checks and risk
undefined behavior and aliasing; fix by first validating pointers with
ptr::is_null() and input_len bounds, returning early (or doing nothing) on
invalid inputs, then only call slice::from_raw_parts/from_raw_parts_mut after
those checks; also validate the output pointer is non-null and that output
buffer is at least 32 bytes; to prevent aliasing between input and output,
detect overlap (compare pointer ranges) and if they overlap, copy the digest
into a local 32-byte temporary buffer (or use ptr::copy_nonoverlapping from a
non-overlapping source) before writing to the output pointer; apply the same
pattern to both rust_keccak_asm and rust_tiny_keccak.

In `@src/crypto/xkcp/armv8a/KeccakP-1600-armv8a-neon.s`:
- Around line 496-501: KeccakP1600_Permute_Nrounds can compute a pointer before
KeccakP1600_Permute_RoundConstants0 when nrounds > 24; clamp the rounds value
before doing the pointer math by bounding the rounds in x2 to a maximum of 24
(e.g., compute x2 = min(x1, `#24`) or otherwise limit the value used for lsl `#3`)
and then use that clamped x2 for lsl x3 and the subsequent sub against
KeccakP1600_Permute_RoundConstants0 so the pointer never moves before the table;
keep the rest of the flow (branch to KeccakP1600_Permute) unchanged and only
modify how x2/x3 are derived.

In `@src/crypto/xkcp/avx512/KeccakP-1600-AVX512.s`:
- Around line 619-624: nrounds is not being bounded before computing the iota
table pointer (iotas_end - nrounds*8), allowing reads before iotas for
nrounds>24; clamp %esi (nrounds) to a maximum of 24 before the lea/shl/sub
sequence (i.e., insert a compare/cap: cmp $24,%esi; cmovae or use ja/ mov
$24,%esi) so the following shl $3,%rsi and sub %rsi,%r10 cannot underflow the
iotas pointer; keep the following labels/regs intact (iotas_end, %r10, %rsi/%rax
used for nrounds) and perform the clamp immediately after moving nrounds into
%rax/%rsi and before computing the pointer.

In `@src/crypto/xkcp/common/brg_endian.h`:
- Around line 133-139: The code currently forces PLATFORM_BYTE_ORDER to
IS_LITTLE_ENDIAN via the unconditional '#elif 1' in brg_endian.h, making the
fail-fast error unreachable and risking wrong behavior on big-endian targets;
replace that unconditional branch with a proper detection or explicit error:
change the '#elif 1' branch to test real compiler macros (for example use checks
against __BYTE_ORDER__ / __ORDER_LITTLE_ENDIAN__ or vendor macros already used
earlier in the file) and only define PLATFORM_BYTE_ORDER = IS_LITTLE_ENDIAN when
those macros prove the target is little-endian; otherwise leave the '#elif 0'
branch or trigger the '#error' so PLATFORM_BYTE_ORDER is not silently
assumed—ensure references to PLATFORM_BYTE_ORDER, IS_LITTLE_ENDIAN and
IS_BIG_ENDIAN remain consistent.

In `@src/keccak_xkcp.zig`:
- Around line 58-61: The update function may overflow when computing data.len *
8; modify Hasher.update to check for multiplication overflow on data.len before
calling Keccak_HashUpdate (e.g., use std.math.mulWithOverflow or
std.mem.checkedMul to compute bit_len = data.len * 8 and return/assert on
overflow), or cast to a larger integer type (u128/u64) before multiplying and
ensure the resulting bit_len fits the parameter type expected by
Keccak_HashUpdate; then pass the safe bit_len to Keccak_HashUpdate and keep the
existing assert on the return value.
- Around line 43-44: Replace the std.debug.assert-based checks for XKCP return
values with explicit error unions: change the free function hash to return !Hash
and check KeccakWidth1600_Sponge's return value, returning a new error (e.g.
error.HashFailed) when non-zero; similarly change Hasher.init(),
Hasher.update(), and Hasher.final() to return an error union (e.g. !void or a
specific error union) and map non-zero Keccak/HashReturn results to appropriate
errors instead of asserting, ensuring each function returns success only when
the underlying C call returns the expected success code.

---

Nitpick comments:
In `@src/crypto/xkcp/avx512/KeccakP-1600-AVX512.s`:
- Around line 454-704: Add CI jobs to exercise the AVX512/AVX2/fallback assembly
paths by adding a Linux x86_64 matrix (entries: baseline/fallback, avx2, avx512)
that builds and runs the test suite with appropriate compiler/assembler flags;
for avx2 set CFLAGS/ASFLAGS to include -mavx2 and for avx512 include
-mavx512f/-mavx512dq and use a runner or Docker image on x86_64. Ensure each job
builds the target that includes the KeccakP1600_AVX512_Permute_24rounds,
KeccakP1600_AVX512_Permute_12rounds and KeccakP1600_AVX512_Permute_Nrounds
symbols and runs the unit/integration tests that exercise those symbols (or a
small smoke test invoking the permute functions). Add conditional skipping or
feature-detection (grep /proc/cpuinfo or use gcc -march checks) so jobs fail
fast when the host lacks required ISA support, and document the matrix entries
in CI config.

In `@src/crypto/xkcp/high/KeccakHash.c`:
- Around line 17-18: Remove the unnecessary `#include` <string.h> from
KeccakHash.c (it appears unused relative to the functions implemented in that
file, e.g., those declared in KeccakHash.h); delete the include line and run
full build/tests (with link_libc = true) to confirm nothing breaks, and if any
string functions are actually needed later, re-add the include or add a brief
comment explaining why it's retained.

In `@src/keccak_xkcp.zig`:
- Around line 40-45: The hash function should explicitly handle empty input
before calling KeccakWidth1600_Sponge: detect when data.len == 0 and pass a safe
null pointer (or short-circuit to produce the known zero-length hash) instead of
blindly using data.ptr, then call KeccakWidth1600_Sponge with that safe pointer
and the same parameters (rate, capacity, delimited_suffix, &result, 32) and
assert ret == 0; update pub fn hash and reference KeccakWidth1600_Sponge,
data.ptr, data.len, delimited_suffix, and result when making this change.

In `@src/keccak.zig`:
- Around line 12-18: The Keccak256 alias points to StdlibKeccak256 so callers
that instantiate Keccak256 for incremental hashing won't get XKCP's faster
implementation; update the API by either (a) adding a concise doc comment on the
Keccak256 declaration explaining that hash() uses the XKCP backend and that
incremental usage of Keccak256 will use the slower StdlibKeccak256, or (b)
export the XKCP incremental hasher (e.g., expose xkcp.Hasher under a public
name) and add a doc comment on Keccak256 pointing users to xkcp.Hasher for
high-performance incremental hashing; reference the symbols Keccak256,
StdlibKeccak256, hash(), xkcp, and xkcp.Hasher when making the changes.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dca9144 and 32b64b4.

⛔ Files ignored due to path filters (1)
  • bench/rust-keccak/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (31)
  • bench/keccak_bench_cli.zig
  • bench/rust-keccak/.gitignore
  • bench/rust-keccak/Cargo.toml
  • bench/rust-keccak/src/lib.rs
  • bench/rust-keccak/src/main.rs
  • build.zig
  • src/crypto/xkcp/armv8a/KeccakP-1600-SnP.h
  • src/crypto/xkcp/armv8a/KeccakP-1600-armv8a-neon.s
  • src/crypto/xkcp/avx2/KeccakP-1600-AVX2.h
  • src/crypto/xkcp/avx2/KeccakP-1600-AVX2.s
  • src/crypto/xkcp/avx2/KeccakP-1600-SnP.h
  • src/crypto/xkcp/avx512/KeccakP-1600-AVX512.h
  • src/crypto/xkcp/avx512/KeccakP-1600-AVX512.s
  • src/crypto/xkcp/avx512/KeccakP-1600-SnP.h
  • src/crypto/xkcp/common/SnP-Relaned.h
  • src/crypto/xkcp/common/SnP-common.h
  • src/crypto/xkcp/common/align.h
  • src/crypto/xkcp/common/brg_endian.h
  • src/crypto/xkcp/common/config.h
  • src/crypto/xkcp/high/KeccakHash.c
  • src/crypto/xkcp/high/KeccakHash.h
  • src/crypto/xkcp/high/KeccakSponge.c
  • src/crypto/xkcp/high/KeccakSponge.h
  • src/crypto/xkcp/high/KeccakSponge.inc
  • src/crypto/xkcp/plain64/KeccakP-1600-64.macros
  • src/crypto/xkcp/plain64/KeccakP-1600-SnP.h
  • src/crypto/xkcp/plain64/KeccakP-1600-opt64.c
  • src/crypto/xkcp/plain64/KeccakP-1600-plain64.h
  • src/crypto/xkcp/plain64/KeccakP-1600-unrolling.macros
  • src/keccak.zig
  • src/keccak_xkcp.zig

Comment thread bench/keccak_bench_cli.zig
Comment thread bench/rust-keccak/src/lib.rs Outdated
Comment thread src/crypto/xkcp/armv8a/KeccakP-1600-armv8a-neon.s
Comment thread src/crypto/xkcp/avx512/KeccakP-1600-AVX512.s
Comment thread src/crypto/xkcp/common/brg_endian.h
Comment thread src/keccak_xkcp.zig
Comment thread src/keccak_xkcp.zig
…all inputs

Vendors the XKCP (eXtended Keccak Code Package) optimized C/assembly
implementations and integrates them via Zig's build system with comptime
CPU feature detection. Selects AVX2/AVX-512 assembly on x86_64 and
optimized C on aarch64. Benchmarks show parity with Voltaire's Rust
keccak-asm FFI approach while avoiding a Rust toolchain dependency.

Includes hyperfine-compatible CLI benchmarks and a Rust keccak-asm
comparison harness.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/crypto/xkcp/high/KeccakSponge.h`:
- Line 23: Replace the header's libc include: in KeccakSponge.h change the
`#include` <string.h> to `#include` <stddef.h> so only size_t is exposed from the
header, and then add an explicit `#include` <string.h> to KeccakSponge.c
(immediately after the existing includes around line 17) to satisfy the
implementation's use of memset in KeccakSponge.inc; update these includes
only—no other code changes.

In `@src/crypto/xkcp/plain64/KeccakP-1600-unrolling.macros`:
- Around line 294-305: The roundsN macro calculates i = 24 - (__nrounds) with no
validation, so KeccakP1600_plain64_Permute_Nrounds (which forwards its unsigned
int nr) can cause negative/invalid indices when nr > 24; add a bounds check in
KeccakP1600_plain64_Permute_Nrounds to ensure nr is within [0,24] before calling
roundsN(nr) (e.g., clamp or return/error on out-of-range), and if rejecting
out-of-range values, document/handle the error path so callers cannot pass
invalid nrounds to roundsN; keep the macro unchanged but ensure only validated
nrounds reach roundsN.

In `@src/keccak.zig`:
- Around line 28-34: The function hashConcat currently constructs xkcp.Hasher (a
C-FFI type) unguarded and will fail if invoked at comptime; add the same
comptime protection as hash() by checking `@inComptime`() at the start of
hashConcat and emitting a compile-time error (e.g., `@compileError` with a clear
message) when true, so callers get a clear diagnostic instead of a cryptic FFI
failure; reference the hashConcat function and the use of xkcp.Hasher in your
change.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32b64b4 and c396285.

📒 Files selected for processing (27)
  • bench/keccak_bench_cli.zig
  • build.zig
  • src/crypto/xkcp/armv8a/KeccakP-1600-SnP.h
  • src/crypto/xkcp/armv8a/KeccakP-1600-armv8a-neon.s
  • src/crypto/xkcp/avx2/KeccakP-1600-AVX2.h
  • src/crypto/xkcp/avx2/KeccakP-1600-AVX2.s
  • src/crypto/xkcp/avx2/KeccakP-1600-SnP.h
  • src/crypto/xkcp/avx512/KeccakP-1600-AVX512.h
  • src/crypto/xkcp/avx512/KeccakP-1600-AVX512.s
  • src/crypto/xkcp/avx512/KeccakP-1600-SnP.h
  • src/crypto/xkcp/common/SnP-Relaned.h
  • src/crypto/xkcp/common/SnP-common.h
  • src/crypto/xkcp/common/align.h
  • src/crypto/xkcp/common/brg_endian.h
  • src/crypto/xkcp/common/config.h
  • src/crypto/xkcp/high/KeccakHash.c
  • src/crypto/xkcp/high/KeccakHash.h
  • src/crypto/xkcp/high/KeccakSponge.c
  • src/crypto/xkcp/high/KeccakSponge.h
  • src/crypto/xkcp/high/KeccakSponge.inc
  • src/crypto/xkcp/plain64/KeccakP-1600-64.macros
  • src/crypto/xkcp/plain64/KeccakP-1600-SnP.h
  • src/crypto/xkcp/plain64/KeccakP-1600-opt64.c
  • src/crypto/xkcp/plain64/KeccakP-1600-plain64.h
  • src/crypto/xkcp/plain64/KeccakP-1600-unrolling.macros
  • src/keccak.zig
  • src/keccak_xkcp.zig
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/keccak_xkcp.zig
  • src/crypto/xkcp/high/KeccakSponge.inc
  • src/crypto/xkcp/common/SnP-Relaned.h
  • src/crypto/xkcp/common/config.h
  • src/crypto/xkcp/plain64/KeccakP-1600-SnP.h
  • src/crypto/xkcp/armv8a/KeccakP-1600-armv8a-neon.s
  • src/crypto/xkcp/common/SnP-common.h
  • src/crypto/xkcp/avx512/KeccakP-1600-AVX512.s
  • src/crypto/xkcp/avx2/KeccakP-1600-SnP.h

Comment thread src/crypto/xkcp/high/KeccakSponge.h
Comment thread src/crypto/xkcp/plain64/KeccakP-1600-unrolling.macros
Comment thread src/keccak.zig

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
build.zig (1)

119-149: Add CI coverage for backend-selection branches.

Given this function now routes multiple architecture/CPU paths, add matrix builds/tests for at least: baseline x86_64, x86_64+AVX2, x86_64+AVX-512F, and aarch64. This will catch selection regressions early.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build.zig` around lines 119 - 149, Add CI matrix builds to exercise the new
backend-selection branches by adding jobs that run the build/test for arch
detection and CPU feature gating used in build.zig (variables/functions to
target: target.result.cpu.arch, target.result.cpu.features, features.isEnabled,
module.addAssemblyFile, module.addCSourceFile). Create matrix entries for:
baseline x86_64 (no extra features), x86_64 with AVX2 enabled, x86_64 with
AVX-512F enabled, and aarch64; ensure runners use compilers/flags that expose
those features (or set env/clang flags to simulate feature bits) and run the
same build command from CI so module.addIncludePath and the plain64/avx2/avx512
branches are exercised. Ensure each job fails the workflow if its build
selection path errors so selection regressions are detected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@build.zig`:
- Around line 145-149: The fallback that adds KeccakP-1600-opt64.c via
module.addCSourceFile lacks the plain64 include directory; update the same
fallback block that calls module.addCSourceFile({ .file =
b.path("src/crypto/xkcp/plain64/KeccakP-1600-opt64.c"), .flags = c_flags }) to
also add the include directory for the plain64 headers (e.g. call
module.addIncludeDir or equivalent with b.path("src/crypto/xkcp/plain64") so the
high-level XKCP C sources can resolve their headers in x86_64 non-AVX builds).
- Around line 134-143: The AVX assembly backends are being added unconditionally
when features.isEnabled(avx512f) or features.isEnabled(avx2) even for non-ELF
x86_64 targets; update the logic in the block using
features.isEnabled(avx512f)/features.isEnabled(avx2) to also check
target.result.ofmt == .elf before calling module.addAssemblyFile for
KeccakP-1600-AVX512.s or KeccakP-1600-AVX2.s, and if the object format is not
.elf fall back to using the plain64 C backend (e.g. ensure the existing plain64
include and KeccakP-1600-opt64.c are used instead of adding the .s assembly
file) so non-ELF x86_64 (macOS/Windows) build targets do not import ELF-specific
assembly.

---

Nitpick comments:
In `@build.zig`:
- Around line 119-149: Add CI matrix builds to exercise the new
backend-selection branches by adding jobs that run the build/test for arch
detection and CPU feature gating used in build.zig (variables/functions to
target: target.result.cpu.arch, target.result.cpu.features, features.isEnabled,
module.addAssemblyFile, module.addCSourceFile). Create matrix entries for:
baseline x86_64 (no extra features), x86_64 with AVX2 enabled, x86_64 with
AVX-512F enabled, and aarch64; ensure runners use compilers/flags that expose
those features (or set env/clang flags to simulate feature bits) and run the
same build command from CI so module.addIncludePath and the plain64/avx2/avx512
branches are exercised. Ensure each job fails the workflow if its build
selection path errors so selection regressions are detected.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c396285 and c93597e.

📒 Files selected for processing (1)
  • build.zig

Comment thread build.zig
Comment thread build.zig

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
build.zig (1)

134-143: ⚠️ Potential issue | 🟠 Major

Gate AVX assembly backends to ELF targets only.

Line [134] through Line [143] still selects AVX .s backends only by CPU feature bits. That can break non-ELF x86_64 targets (e.g., Mach-O/COFF) when ELF/GAS-specific directives are present. Please add an object-format gate and fall back to existing plain64 C otherwise.

Proposed fix
     } else if (arch == .x86_64) {
         const features = target.result.cpu.features;
         const avx512f = `@intFromEnum`(std.Target.x86.Feature.avx512f);
         const avx2 = `@intFromEnum`(std.Target.x86.Feature.avx2);
+        const is_elf = target.result.ofmt == .elf;

-        if (features.isEnabled(avx512f)) {
+        if (is_elf and features.isEnabled(avx512f)) {
             // AVX512 SnP header must come before plain64 to shadow KeccakP-1600-SnP.h
             module.addIncludePath(b.path("src/crypto/xkcp/avx512"));
             module.addIncludePath(b.path("src/crypto/xkcp/plain64"));
             module.addAssemblyFile(b.path("src/crypto/xkcp/avx512/KeccakP-1600-AVX512.s"));
-        } else if (features.isEnabled(avx2)) {
+        } else if (is_elf and features.isEnabled(avx2)) {
             // AVX2 SnP header must come before plain64 to shadow KeccakP-1600-SnP.h
             module.addIncludePath(b.path("src/crypto/xkcp/avx2"));
             module.addIncludePath(b.path("src/crypto/xkcp/plain64"));
             module.addAssemblyFile(b.path("src/crypto/xkcp/avx2/KeccakP-1600-AVX2.s"));
         } else {
             module.addIncludePath(b.path("src/crypto/xkcp/plain64"));
             module.addCSourceFile(.{
                 .file = b.path("src/crypto/xkcp/plain64/KeccakP-1600-opt64.c"),
                 .flags = c_flags,
             });
         }
#!/bin/bash
set -euo pipefail

echo "== x86_64 backend selection block =="
sed -n '129,151p' build.zig

echo
echo "== Search for object-format guard usage =="
rg -n -C2 'target\.result\.ofmt|features\.isEnabled\(avx512f\)|features\.isEnabled\(avx2\)' build.zig

echo
echo "== Check AVX assembly for ELF-specific directives =="
for f in $(fd 'KeccakP-1600-AVX(2|512)\.s$' src/crypto/xkcp); do
  echo "-- $f"
  rg -n '^\s*\.type\b|^\s*\.size\b|\.note\.GNU-stack|@function' "$f" || true
done

Expected confirmation: no ofmt == .elf guard in build.zig AVX branches, while AVX assembly files contain ELF-oriented directives.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build.zig` around lines 134 - 143, The AVX assembly backends are currently
selected solely by CPU feature checks (features.isEnabled(avx512f) /
features.isEnabled(avx2') via the block that calls module.addAssemblyFile),
which can pull ELF/GAS-specific .s files into non-ELF targets; update that
selection to also check the target object format (target.result.ofmt) and only
addAssemblyFile for the AVX .s files when target.result.ofmt == .elf, otherwise
fall back to adding only the plain64 include path
(module.addIncludePath(b.path("src/crypto/xkcp/plain64"))) and do not call
module.addAssemblyFile; make this change around the existing
features.isEnabled(avx512f) and features.isEnabled(avx2) branches so the code
paths using module.addIncludePath and module.addAssemblyFile are gated by the
object-format check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@build.zig`:
- Around line 134-143: The AVX assembly backends are currently selected solely
by CPU feature checks (features.isEnabled(avx512f) / features.isEnabled(avx2')
via the block that calls module.addAssemblyFile), which can pull
ELF/GAS-specific .s files into non-ELF targets; update that selection to also
check the target object format (target.result.ofmt) and only addAssemblyFile for
the AVX .s files when target.result.ofmt == .elf, otherwise fall back to adding
only the plain64 include path
(module.addIncludePath(b.path("src/crypto/xkcp/plain64"))) and do not call
module.addAssemblyFile; make this change around the existing
features.isEnabled(avx512f) and features.isEnabled(avx2) branches so the code
paths using module.addIncludePath and module.addAssemblyFile are gated by the
object-format check.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c93597e and 442f611.

📒 Files selected for processing (1)
  • build.zig

@koko1123
koko1123 merged commit f7fdc66 into main Mar 3, 2026
10 checks passed
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.

1 participant