Skip to content

SIMD kernels performance enhancements#135

Merged
Xor-el merged 20 commits into
masterfrom
enhancements/aes-ni-fused-simd-improvements
Jul 7, 2026
Merged

SIMD kernels performance enhancements#135
Xor-el merged 20 commits into
masterfrom
enhancements/aes-ni-fused-simd-improvements

Conversation

@Xor-el

@Xor-el Xor-el commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Adds fused AES-NI kernels that interleave the AES round chain with each
mode's auxiliary operation, cutting per-message overhead across the AEAD
suite. Every kernel ships for x86_64 and i386, gated on CPU detection with
a scalar fallback (behaviour identical across the fused/scalar paths).

Fused kernels

  • CTR — interleaved AES-NI fast path
  • GCM — in-asm CTR+GHASH batch loop
  • OCBntz computed in-kernel, one-shot span dispatch
  • CCM — fused CTR + CBC-MAC
  • GCM-SIV — fused POLYVAL (Horner-by-8)
  • CBC — fused encrypt kernel, made direction-agnostic (decrypt-ready)

Correctness

  • Fix GCM-SIV fused POLYVAL producing wrong output for ≥128-byte spans

Refactors & utilities

  • CCM & GCM-SIV: reused TBytes accumulators replace per-op TMemoryStream
    (drops the readback copy); shared AppendTo/EnsureCapacity ingestion
  • TArrayUtilities.Fill: memset fast path + typed Byte/UInt32/UInt64
    overloads
  • Byte-XOR enhancements + TByteXorX86Backend

Xor-el added 20 commits July 5, 2026 00:03
AES-CTR ran several times slower than the raw AES-NI core it shares with ECB:
the SIC mode drove the 8-wide engine through three Pascal passes per 128 bytes
(scalar counter generation into a stack buffer, AES on the scratch, then a
separate wide XOR). Replace it with a single register-resident asm kernel:
counter blocks built in-register (MOVQ/PINSRQ), 8-wide AES via the shared
AesNiEightRoundsOnly chain, PXOR straight to output, looping internally.

The kernel joins the fused-kernel family (IFusedCtrKernel + TAesNiCtrKernel,
registered in TFusedKernelRegistry, force-linked via ClpFusedKernelDefaults);
TSicBlockCipher acquires it via TryAcquireCtr like the AEAD modes. The AES
engine stays a pure block transform - CTR borrows the encrypt key schedule
through the existing TryGetEncKeysPtr. Rename the shared engine resolver
TAesFusedAeadX86Backend -> TAesNiFusedX86Backend (now used by CTR too) with a
CTR CPU gate (AES-NI + SSE4.1, no PCLMULQDQ).

CTR throughput rises several-fold to near the raw 8-wide AES-NI rate (multiple
GB/s, close to ECB). x86-64 only; i386 falls back to the existing bulk path. A
new AESSICTests large-buffer case cross-checks the kernel against an independent
ECB keystream reference across all key sizes and batch/tail sizes.
The fused AES-NI + PCLMULQDQ GCM kernel was invoked one 128-byte batch
per call from Pascal, so counter generation, context-struct packing, and
the xmm save/restore ran ~8192x per MB. Push the batch loop into the
kernel: it now generates the GCM counters in-register, rotates the
GHASH-lags-by-one pipeline internally, and advances the buffer/counter
state in place across the whole run, leaving only the prime and drain to
the mode. One kernel call replaces the per-batch orchestration.

Both arches build the counter blocks in-register from the running 32-bit
counter and the J0 template (x86_64 into xmm0..7; i386, with no spare GP
registers, four-per-half into xmm0..3 via PINSRD), so no counter scratch
buffer or memory round-trip is needed and the shared context record is
identical in shape across arches.

Collapse the now-redundant single-batch path: one context record
(TGcmFusedCtx), one proc set, an unconditional-loop include on both
arches, and ProcessCtrGhashBatch dropped from IFusedGcmKernel. Remove
FillNextCtrBlocks8Raw, which the new driver orphaned.
…pans

The fused POLYVAL fast path passed plaintext/AAD blocks to the GHASH-based
kernel in natural byte order, but POLYVAL requires each block byte-reversed
before the GHASH fold (RFC 8452 Appendix A) -- which the scalar path supplies
via FillReverse. As a result any hashed span >= 128 bytes (>= 8 blocks, where
the fused Horner-by-8 kernel engages) produced an incorrect tag and ciphertext
on CPUs with the fused kernel, non-interoperable with other GCM-SIV
implementations. Below 8 blocks the scalar path runs and output was correct.

The defect was untested on two counts: every RFC 8452 known-answer vector is
<= 64 bytes and so never reaches the 8-block batch, and the randomised
round-trip cannot detect it -- encrypt and decrypt recompute the same wrong
tag and still round-trip cleanly.

Fix: byte-reverse each block into the batch scratch before invoking the fused
kernel, mirroring the scalar FillReverse.

Add GcmSivBulkParityTests, which cross-compares fused vs scalar ciphertext+tag
across AAD/plaintext lengths straddling the 8-block boundary -- the comparison
a round-trip cannot make.
…verse

The GCM-SIV POLYVAL fast path reversed each block in Pascal before the shared
GHASH batch kernel, which then reversed it again on load via PSHUFB -- a redundant
round-trip.

Give the shared GHASH batch body an optional GCM_GHASH_RAW_INPUT mode that skips
its per-block input byte-reverse (the running-state load and final store stay
byte-reversed), and have the GCM-SIV POLYVAL kernel select it and pass blocks in
natural memory order. The kernel consumes them directly as field-form operands --
the same element the scalar path reaches -- with no Pascal pre-reverse and 8 fewer
PSHUFB in the hot loop.
Rename the CBC encrypt kernel unit, interface, class, factory, and
registry entry points to drop "Encrypt" so a decrypt body can be added
later without another rename, mirroring the OCB and CCM kernels.

  ClpIFusedCbcEncryptKernel -> ClpIFusedCbcKernel
  ClpAesNiCbcEncryptKernel  -> ClpAesNiCbcKernel
  IFusedCbcEncryptKernel[Factory], TAesNiCbcEncryptKernel[Factory],
  ProcessCbcEncryptBlocks, and the Register/Unregister/Snapshot/
  GetRegistered/TryAcquire registry surface lose the "Encrypt" infix.

The factory now takes a TFusedModeDirection and returns False for any
direction other than Encrypt, so a decrypt request falls back to the
per-block path until a decrypt kernel exists. The encrypt implementation
itself is unchanged: the serial-chain include files, the fused context
record, and the mode's bulk path stay direction-specific.
CCM and GCM-SIV each carried an identical private EnsureCapacity and
open-coded the same grow + Move + advance-length triple at every buffer
append. Lift the growth into TArrayUtilities.EnsureCapacity and add
AppendTo overloads (single byte / source span) that grow, write at the
used length, and advance it. Both modes now append in one call and drop
their local EnsureCapacity; CCM's decrypt staging reuses the shared
EnsureCapacity too. No behaviour change - same doubling growth, same
never-shrink reuse.
@Xor-el Xor-el merged commit 98c6320 into master Jul 7, 2026
39 of 48 checks passed
@Xor-el Xor-el deleted the enhancements/aes-ni-fused-simd-improvements branch July 7, 2026 17:11
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