keccak-fast.c

Minimal SIMD keccak implementation
git clone git://git.finwo.net/lib/keccak-fast.c
Log | Files | Refs | README | LICENSE

commit 721000be4b2df7f3d084e9dce86bb229036393dc
parent 812b92a5f8c0598e3ede2652ddfe7bf0ea3e5798
Author: finwo <finwo@pm.me>
Date:   Sat, 10 Oct 2026 16:12:19 +0200

Add scalar+bmi backend

Diffstat:
MREADME.md | 20++++++++++++--------
Mexport.mk | 1+
Asrc/backend/scalar-impl.h | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backend/scalar.c | 137+++++--------------------------------------------------------------------------
Asrc/backend/scalar_bmi.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/keccak-fast-internal.h | 1+
6 files changed, 223 insertions(+), 137 deletions(-)

diff --git a/README.md b/README.md @@ -28,18 +28,22 @@ dep add finwo/keccak-fast ## Backends -| backend | one-shot | batch | -| ------- | -------- | ----- | -| scalar | yes | — | -| AVX2 | no | x4 | +| backend | one-shot | batch | +| ---------- | -------- | ----- | +| scalar | yes | — | +| scalar+bmi | yes | — | +| AVX2 | no | x4 | Backends register from constructors; the highest available priority wins -(scalar 0, avx2 20, avx512 30). `kf_backend_name()` and `kf_batch_name()` -report the active ones. `KECCAK_FAST_NO_AVX2` drops the AVX2 backend. +(scalar 0, scalar+bmi 10, avx2 20, avx512 30). `kf_backend_name()` and +`kf_batch_name()` report the active ones. `KECCAK_FAST_NO_BMI` and +`KECCAK_FAST_NO_AVX2` drop those backends. The scalar backend is the donor, pinned to the x86-64 baseline -(`no-avx,no-avx2,no-avx512f`) so a consumer's `-march=native` cannot vectorize -it (clang runs 1.9x slower when it does). +(`no-avx,no-avx2,no-avx512f,no-bmi,no-bmi2`) so a consumer's `-march=native` +cannot vectorize it or emit BMI. `scalar+bmi` is the same permutation with +`bmi` enabled, which turns chi's `not`+`and` into `andn`; gcc also gets `rorx` +for the rotates. About 11% faster than the donor. ## Dev diff --git a/export.mk b/export.mk @@ -1,3 +1,4 @@ SRC+={{module.dirname}}/src/keccak-fast.c SRC+={{module.dirname}}/src/backend/scalar.c +SRC+={{module.dirname}}/src/backend/scalar_bmi.c SRC+={{module.dirname}}/src/backend/avx2.c diff --git a/src/backend/scalar-impl.h b/src/backend/scalar-impl.h @@ -0,0 +1,140 @@ +#ifndef FINWO_SCALAR_IMPL_H +#define FINWO_SCALAR_IMPL_H + +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +#define KF_CAT2(a, b) a##b +#define KF_CAT(a, b) KF_CAT2(a, b) +#define KF_FN(name) KF_CAT(KF_PREFIX, name) + +#if !defined(__STDC_LIB_EXT1__) +static inline int memset_s(void *dest, size_t destsz, int ch, size_t count) { + (void)destsz; + memset(dest, ch, count); + return 0; +} +#endif + +static const uint8_t rho[24] = { + 1, 3, 6, 10, 15, 21, + 28, 36, 45, 55, 2, 14, + 27, 41, 56, 8, 25, 43, + 62, 18, 39, 61, 20, 44}; +static const uint8_t pi[24] = { + 10, 7, 11, 17, 18, 3, + 5, 16, 8, 21, 24, 4, + 15, 23, 19, 13, 12, 2, + 20, 14, 22, 9, 6, 1}; +static const uint64_t RC[24] = { + 1ULL, 0x8082ULL, 0x800000000000808aULL, 0x8000000080008000ULL, + 0x808bULL, 0x80000001ULL, 0x8000000080008081ULL, 0x8000000000008009ULL, + 0x8aULL, 0x88ULL, 0x80008009ULL, 0x8000000aULL, + 0x8000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL, 0x8000000000008003ULL, + 0x8000000000008002ULL, 0x8000000000000080ULL, 0x800aULL, 0x800000008000000aULL, + 0x8000000080008081ULL, 0x8000000000008080ULL, 0x80000001ULL, 0x8000000080008008ULL}; + +#define rol(x, s) (((x) << s) | ((x) >> (64 - s))) +#define REPEAT6(e) e e e e e e +#define REPEAT24(e) REPEAT6(e e e e) +#define REPEAT5(e) e e e e e +#define FOR5(v, s, e) \ + v = 0; \ + REPEAT5(e; v += s;) + +static inline void keccakf(void *state) { + uint64_t *a = (uint64_t *)state; + uint64_t b[5] = {0}; + uint64_t t = 0; + uint8_t x, y; + + for (int i = 0; i < 24; i++) { + /* theta */ + FOR5(x, 1, b[x] = 0; FOR5(y, 5, b[x] ^= a[x + y];)) + FOR5(x, 1, FOR5(y, 5, a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1);)) + /* rho and pi */ + t = a[1]; + x = 0; + REPEAT24(b[0] = a[pi[x]]; + a[pi[x]] = rol(t, rho[x]); + t = b[0]; + x++;) + /* chi */ + FOR5(y, + 5, + FOR5(x, 1, b[x] = a[y + x];) + FOR5(x, 1, a[y + x] = b[x] ^ ((~b[(x + 1) % 5]) & b[(x + 2) % 5]);)) + /* iota */ + a[0] ^= RC[i]; + } +} + +#define _(S) do { S } while (0) +#define FOR(i, ST, L, S) \ + _(for (size_t i = 0; i < L; i += ST) { S; }) +#define mkapply_ds(NAME, S) \ + static inline void NAME(uint8_t *dst, const uint8_t *src, size_t len) { \ + FOR(i, 1, len, S); \ + } +#define mkapply_sd(NAME, S) \ + static inline void NAME(const uint8_t *src, uint8_t *dst, size_t len) { \ + FOR(i, 1, len, S); \ + } + +mkapply_ds(xorin, dst[i] ^= src[i]) +mkapply_sd(setout, dst[i] = src[i]) + +#define Plen 200 + +#define foldP(I, L, F) \ + while (L >= rate) { \ + F(a, I, rate); \ + keccakf(a); \ + I += rate; \ + L -= rate; \ + } + +static inline int kf_hash_impl(uint8_t *out, size_t outlen, + const uint8_t *in, size_t inlen, + size_t rate, uint8_t delim) { + if ((out == NULL) || ((in == NULL) && inlen != 0) || (rate >= Plen)) { + return -1; + } + uint8_t a[Plen] = {0}; + foldP(in, inlen, xorin); + a[inlen] ^= delim; + a[rate - 1] ^= 0x80; + xorin(a, in, inlen); + keccakf(a); + foldP(out, outlen, setout); + setout(a, out, outlen); + memset_s(a, 200, 0, 200); + return 0; +} + +#define defshake(bits) \ + KF_LINKAGE int KF_FN(shake##bits)(uint8_t *out, size_t outlen, \ + const uint8_t *in, size_t inlen) { \ + return kf_hash_impl(out, outlen, in, inlen, 200 - (bits / 4), 0x1f); \ + } +#define defsha3(bits) \ + KF_LINKAGE int KF_FN(sha3_##bits)(uint8_t *out, size_t outlen, \ + const uint8_t *in, size_t inlen) { \ + if (outlen > (bits / 8)) { \ + return -1; \ + } \ + return kf_hash_impl(out, outlen, in, inlen, 200 - (bits / 4), 0x06); \ + } + +defshake(128) +defshake(256) +defsha3(224) +defsha3(256) +defsha3(384) +defsha3(512) + +#undef defshake +#undef defsha3 + +#endif /* FINWO_SCALAR_IMPL_H */ diff --git a/src/backend/scalar.c b/src/backend/scalar.c @@ -11,140 +11,19 @@ #include "../keccak-fast-internal.h" -/* The fallback must run on any x86-64; clang vectorizes it under -march=native - * and runs 1.9x slower. */ +/* The fallback must run on any x86-64: no AVX (clang vectorizes it under + * -march=native and runs 1.9x slower) and no BMI (that is scalar_bmi.c). */ #if defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) #pragma clang attribute push( \ - __attribute__((target("no-avx,no-avx2,no-avx512f"))), apply_to=function) + __attribute__((target("no-avx,no-avx2,no-avx512f,no-bmi,no-bmi2"))), \ + apply_to=function) #elif defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) -#pragma GCC target("no-avx,no-avx2,no-avx512f") +#pragma GCC target("no-avx,no-avx2,no-avx512f,no-bmi,no-bmi2") #endif -#if !defined(__STDC_LIB_EXT1__) -static inline int memset_s(void *dest, size_t destsz, int ch, size_t count) { - (void)destsz; - memset(dest, ch, count); - return 0; -} -#endif - -static const uint8_t rho[24] = { - 1, 3, 6, 10, 15, 21, - 28, 36, 45, 55, 2, 14, - 27, 41, 56, 8, 25, 43, - 62, 18, 39, 61, 20, 44}; -static const uint8_t pi[24] = { - 10, 7, 11, 17, 18, 3, - 5, 16, 8, 21, 24, 4, - 15, 23, 19, 13, 12, 2, - 20, 14, 22, 9, 6, 1}; -static const uint64_t RC[24] = { - 1ULL, 0x8082ULL, 0x800000000000808aULL, 0x8000000080008000ULL, - 0x808bULL, 0x80000001ULL, 0x8000000080008081ULL, 0x8000000000008009ULL, - 0x8aULL, 0x88ULL, 0x80008009ULL, 0x8000000aULL, - 0x8000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL, 0x8000000000008003ULL, - 0x8000000000008002ULL, 0x8000000000000080ULL, 0x800aULL, 0x800000008000000aULL, - 0x8000000080008081ULL, 0x8000000000008080ULL, 0x80000001ULL, 0x8000000080008008ULL}; - -#define rol(x, s) (((x) << s) | ((x) >> (64 - s))) -#define REPEAT6(e) e e e e e e -#define REPEAT24(e) REPEAT6(e e e e) -#define REPEAT5(e) e e e e e -#define FOR5(v, s, e) \ - v = 0; \ - REPEAT5(e; v += s;) - -static inline void keccakf(void *state) { - uint64_t *a = (uint64_t *)state; - uint64_t b[5] = {0}; - uint64_t t = 0; - uint8_t x, y; - - for (int i = 0; i < 24; i++) { - /* theta */ - FOR5(x, 1, b[x] = 0; FOR5(y, 5, b[x] ^= a[x + y];)) - FOR5(x, 1, FOR5(y, 5, a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1);)) - /* rho and pi */ - t = a[1]; - x = 0; - REPEAT24(b[0] = a[pi[x]]; - a[pi[x]] = rol(t, rho[x]); - t = b[0]; - x++;) - /* chi */ - FOR5(y, - 5, - FOR5(x, 1, b[x] = a[y + x];) - FOR5(x, 1, a[y + x] = b[x] ^ ((~b[(x + 1) % 5]) & b[(x + 2) % 5]);)) - /* iota */ - a[0] ^= RC[i]; - } -} - -#define _(S) do { S } while (0) -#define FOR(i, ST, L, S) \ - _(for (size_t i = 0; i < L; i += ST) { S; }) -#define mkapply_ds(NAME, S) \ - static inline void NAME(uint8_t *dst, const uint8_t *src, size_t len) { \ - FOR(i, 1, len, S); \ - } -#define mkapply_sd(NAME, S) \ - static inline void NAME(const uint8_t *src, uint8_t *dst, size_t len) { \ - FOR(i, 1, len, S); \ - } - -mkapply_ds(xorin, dst[i] ^= src[i]) -mkapply_sd(setout, dst[i] = src[i]) - -#define P keccakf -#define Plen 200 - -#define foldP(I, L, F) \ - while (L >= rate) { \ - F(a, I, rate); \ - P(a); \ - I += rate; \ - L -= rate; \ - } - -static inline int kf_scalar_hash(uint8_t *out, size_t outlen, - const uint8_t *in, size_t inlen, - size_t rate, uint8_t delim) { - if ((out == NULL) || ((in == NULL) && inlen != 0) || (rate >= Plen)) { - return -1; - } - uint8_t a[Plen] = {0}; - foldP(in, inlen, xorin); - a[inlen] ^= delim; - a[rate - 1] ^= 0x80; - xorin(a, in, inlen); - P(a); - foldP(out, outlen, setout); - setout(a, out, outlen); - memset_s(a, 200, 0, 200); - return 0; -} - -#define defshake(bits) \ - int kf_scalar_shake##bits(uint8_t *out, size_t outlen, const uint8_t *in, \ - size_t inlen) { \ - return kf_scalar_hash(out, outlen, in, inlen, 200 - (bits / 4), 0x1f); \ - } -#define defsha3(bits) \ - int kf_scalar_sha3_##bits(uint8_t *out, size_t outlen, const uint8_t *in, \ - size_t inlen) { \ - if (outlen > (bits / 8)) { \ - return -1; \ - } \ - return kf_scalar_hash(out, outlen, in, inlen, 200 - (bits / 4), 0x06); \ - } - -defshake(128) -defshake(256) -defsha3(224) -defsha3(256) -defsha3(384) -defsha3(512) +#define KF_PREFIX kf_scalar_ +#define KF_LINKAGE +#include "scalar-impl.h" void kf_scalar_permute(uint64_t state[25]) { keccakf(state); diff --git a/src/backend/scalar_bmi.c b/src/backend/scalar_bmi.c @@ -0,0 +1,61 @@ +/* Runtime BMI1/BMI2 variant of the scalar backend. Same source as scalar.c, + * but the target enables `andn` (replaces `not`+`and` in chi) and `rorx`. */ + +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +#include "../keccak-fast-internal.h" + +#if (defined(__x86_64__) || defined(__i386__)) && !defined(KECCAK_FAST_NO_BMI) + +/* clang schedules `rorx` worse than `rol`; gcc is faster with it. */ +#if defined(__clang__) +#pragma clang attribute push( \ + __attribute__((target("no-avx,no-avx2,no-avx512f,bmi,no-bmi2"))), \ + apply_to=function) +#elif defined(__GNUC__) +#pragma GCC target("no-avx,no-avx2,no-avx512f,bmi,bmi2") +#endif + +#define KF_PREFIX kf_bmi_ +#define KF_LINKAGE static +#include "scalar-impl.h" + +static void kf_bmi_batch(uint64_t states[][25], size_t count) { + for (size_t i = 0; i < count; i++) { + keccakf(states[i]); + } +} + +__attribute__((constructor)) static void kf_bmi_register(void) { +#if defined(__clang__) + if (!__builtin_cpu_supports("bmi")) { +#else + if (!__builtin_cpu_supports("bmi") || !__builtin_cpu_supports("bmi2")) { +#endif + return; + } + if (kf_backend_priority <= KF_PRIO_BMI) { + kf_shake128 = kf_bmi_shake128; + kf_shake256 = kf_bmi_shake256; + kf_sha3_224 = kf_bmi_sha3_224; + kf_sha3_256 = kf_bmi_sha3_256; + kf_sha3_384 = kf_bmi_sha3_384; + kf_sha3_512 = kf_bmi_sha3_512; + kf_backend_priority = KF_PRIO_BMI; + kf_backend_label = "scalar+bmi"; + } + if (kf_batch_priority <= KF_PRIO_BMI) { + kf_batch_perm = kf_bmi_batch; + kf_batch_priority = KF_PRIO_BMI; + kf_batch_lanes = 1; + kf_batch_label = "scalar+bmi"; + } +} + +#if defined(__clang__) +#pragma clang attribute pop +#endif + +#endif diff --git a/src/keccak-fast-internal.h b/src/keccak-fast-internal.h @@ -11,6 +11,7 @@ typedef void (*kf_batch_perm_fn)(uint64_t states[][25], size_t count); #define KF_LANES_MAX 8 #define KF_PRIO_SCALAR 0 +#define KF_PRIO_BMI 10 #define KF_PRIO_AVX2 20 #define KF_PRIO_AVX512 30