From: Herbert Xu <herbert@gondor.apana.org.au>
To: Fabian Blatter <fabianblatter09@gmail.com>
Cc: lukas@wunner.de, ignat@linux.win, davem@davemloft.net,
stefanb@linux.ibm.com, linux-crypto@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH] crypto: ecc - Optimize vli additive operations using compiler builtins
Date: Sun, 5 Jul 2026 13:32:50 +0800 [thread overview]
Message-ID: <aknsgpnZsjmLtcT_@gondor.apana.org.au> (raw)
In-Reply-To: <20260607112435.42804-1-fabianblatter09@gmail.com>
On Sun, Jun 07, 2026 at 01:24:35PM +0200, Fabian Blatter wrote:
> Replace the software carry flag emulation with compiler builtins.
>
> Even the newest compilers struggle with taking advantage of the
> hardware carry flag. Compiler builtins allow the compiler to
> much more easily achieve this while still remaining constant-time.
>
> This yields an approximately 6-7% performance improvement
> on the ecc_gen_privkey, ecc_make_pub_key and crypto_ecdh_shared_secret
> functions on x86_64 on all curve sizes.
>
> Additionally, the code becomes much more readable.
>
> Signed-off-by: Fabian Blatter <fabianblatter09@gmail.com>
> ---
>
> Hi,
>
> I'd like to expand on the benchmarks, compare the generated assembly,
> and clarify some things.
>
>
> Use of compiler builtins:
>
> This patch uses __builtin_addcll, __builtin_subcll when available and
> otherwise __builtin_uaddll_overflow, __builtin_usubll_overflow. the
> latter have existed since ancient gcc versions, so no third fallback
> is needed.
>
> I have put the add_carry and sub_borrow inline functions with the
> preprocessor logic for builtin selection directly in crypto/ecc.c.
> Please let me know if you would like them to be somewhere else.
>
> They do not emit data-dependent branches, and so remain constant-time.
>
>
> Benchmarks:
>
> All benchmarks were run single-threaded on my AMD 7700X CPU limited to
> 5.6Ghz. I have measured both nanoseconds and clock cycles, since their
> combination can hint at downclocking issues and allows calculation of
> the clock speed during the benchmark.
>
> I have omitted the raw output from the benchmarking code, as they much
> exceed the 72 character limit.
>
> I have calculated the percent differences, included clock speed
> calculations and relevant summaries.
>
>
> Macro benchmarks:
>
> These were run in a virtualized environment using virtme-ng on the
> compiled linux kernel image compiled with default flags.
>
> (the first value is the original time per operation, the second the
> patched one. cc is short for clock cycles)
>
> Curve keypair generation (ecc_gen_privkey + ecc_make_pub_key):
>
> P256:
> - 646963ns/op -> 600632ns/op = -7.71%
> - 2911300cc/op -> 2702854cc/op = -7.71%
> - 4.4999Ghz -> 4.5000Ghz = no difference
>
> P384:
> - 1239160ns/op -> 1153940ns/op = -7.38%
> - 5576250cc/op -> 5192749cc/op = -7.38%
> - 4.5000Ghz -> 4.5000Ghz = no difference
>
> Shared secret generation (crypto_ecdh_shared_secret):
>
> P256:
> - 320114ns/op -> 297548ns/op = -7.58%
> - 1440521cc/op -> 1338972cc/op = -7.58%
> - 4.5000Ghz -> 4.5000Ghz = no difference
>
> P384:
> - 620768ns/op -> 582560ns/op = -6.55%
> - 2793467cc/op -> 2621529cc/op = -6.55%
> - 4.5000Ghz -> 4.5000Ghz = no difference
>
> The benchmarks clearly indicate a roughly 6-7% performance increase on
> the public API functions. It also appears that virtme-ng limited the
> clock speed to 4.5Ghz
>
>
> Micro benchmarks:
>
> Since the vli additive functions only rely on u64 being defined, these
> were run without virtualization and with varying compilers and
> compiler flags.
>
> The microbenchmarks show much more mixed results, depending
> heavily on the compiler and optimization level used.
>
> For instance, on gcc and O2, the vli_add present in the
> patch is actually 25.3% slower than the original one. I have tracked
> this down to gcc using a weird way to restore the carry flag after
> each iteration, causing way more dependent instructions, preventing
> ILP from executing multiple at once.
>
> This is quite interesting, since, as far as I know, the kernel compiles
> with gcc and O2 by default, yet the macro-level benchmarks still show a
> performance increase. The effect seems to be reversed when crypto/ecc.c
> gets compiled. Or maybe the linux kernel uses some additional
> optimization flags, I am unsure.
>
> However, most of the time, the patched version outperforms the original
> one by a wide margin:
> - On clang -O2 or -O3, vli_add and vli_uadd show a 4.074x and 5.384x
> speedup.
> - On gcc, vli_uadd shows a 74% performance increase at O2,
> and a 2.07x speedup at O3.
>
> The performance profile of vli_sub and vli_usub is almost identical to
> that of vli_add and vli_uadd.
>
>
> Assembly comparison:
>
> I have put together a piece of code on Compiler explorer, to make sure
> it compiles on old gcc versions, view instructions and play around with
> compiler settings.
>
> If you would like, you can play around yourself here:
> https://godbolt.org/z/1jT5zesz8
>
> When using clang 22.1 at -O3 -march=lunarlake, the difference between
> the patched and original version is particularly clear. The patched
> version produces this assembly in the unrolled vli_add loop:
>
> mov rax, qword ptr [rsi + 8*rcx + 16]
> adc rax, qword ptr [rdx + 8*rcx + 16]
> mov qword ptr [rdi + 8*rcx + 16], rax
> mov rax, qword ptr [rsi + 8*rcx + 24]
> adc rax, qword ptr [rdx + 8*rcx + 24]
> mov qword ptr [rdi + 8*rcx + 24], rax
> mov rax, qword ptr [rsi + 8*rcx + 32]
> adc rax, qword ptr [rdx + 8*rcx + 32]
> mov qword ptr [rdi + 8*rcx + 32], rax
> mov rax, qword ptr [rsi + 8*rcx + 40]
> adc rax, qword ptr [rdx + 8*rcx + 40]
> mov qword ptr [rdi + 8*rcx + 40], rax
> mov rax, qword ptr [rsi + 8*rcx + 48]
> adc rax, qword ptr [rdx + 8*rcx + 48]
>
> This is basically optimal for an inner loop. It's pure adc and mov
> instructions. The loop counting part is still nowhere near perfect,
> and still uses setc instructions. But it is still better than what
> the original version produces with the same compiler and flags:
>
> mov r10, qword ptr [rsi + 8*rcx]
> lea r11, [r10 + rax]
> add r11, qword ptr [rdx + 8*rcx]
> xor ebx, ebx
> cmp r11, r10
> setb bl
> cmove rbx, rax
> mov qword ptr [rdi + 8*rcx], r11
> mov rax, qword ptr [rsi + 8*rcx + 8]
> lea r10, [rax + rbx]
> add r10, qword ptr [rdx + 8*rcx + 8]
> xor r11d, r11d
> cmp r10, rax
> setb r11b
> cmove r11, rbx
> mov qword ptr [rdi + 8*rcx + 8], r10
> mov rax, qword ptr [rsi + 8*rcx + 16]
> lea r10, [rax + r11]
> add r10, qword ptr [rdx + 8*rcx + 16]
> xor ebx, ebx
> cmp r10, rax
> setb bl
> cmove rbx, r11
> mov qword ptr [rdi + 8*rcx + 16], r10
> mov rax, qword ptr [rsi + 8*rcx + 24]
> lea r10, [rax + rbx]
> add r10, qword ptr [rdx + 8*rcx + 24]
> xor r11d, r11d
> cmp r10, rax
> setb r11b
> cmove r11, rbx
>
> This is downright horrendous. that entire block of processes only 4
> limbs, thats 8 instructions per limb! The add instructions
> are also not adc instructions, showing that the carry flag is
> being fully emulated. This demonstrates how even on the newest
> compilers and at the highest optimization level, still cannot
> generate hardware carry chains without explicit use of builtins.
>
> I should note that not just clang 22.1.0 with -O3 -march=lunarlake
> does this. Gcc and clang show this behaviour on every version i have
> tested, regardless of target architecture.
>
> I am not very familiar with ARM or RISC-V assembly, but looking at
> compiler explorer, the effect clearly persists, and in the case of
> RISC-V actually gets much worse.
>
> This affects all architectures across all compilers and compiler
> flags.
>
>
> If you have gotten this far, thank you for reading this and I am looking
> forward to any feedback! If you would like any changes to this patch,
> I am very happy to send a v2.
>
> crypto/ecc.c | 98 ++++++++++++++++++++++++++++++++--------------------
> 1 file changed, 60 insertions(+), 38 deletions(-)
Patch applied. Thanks.
--
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
prev parent reply other threads:[~2026-07-05 5:32 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-07 11:24 [PATCH] crypto: ecc - Optimize vli additive operations using compiler builtins Fabian Blatter
2026-06-09 18:58 ` Stefan Berger
2026-06-09 20:51 ` Fabian
2026-06-10 14:52 ` Stefan Berger
2026-06-10 16:57 ` Fabian
2026-06-10 17:25 ` Stefan Berger
2026-06-23 13:37 ` Lukas Wunner
2026-06-23 19:10 ` Fabian
2026-07-05 5:32 ` Herbert Xu [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aknsgpnZsjmLtcT_@gondor.apana.org.au \
--to=herbert@gondor.apana.org.au \
--cc=davem@davemloft.net \
--cc=fabianblatter09@gmail.com \
--cc=ignat@linux.win \
--cc=linux-crypto@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lukas@wunner.de \
--cc=stefanb@linux.ibm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox