You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As of #44, polyval will compile to VPCLMULQDQ instructions on new enough CPU architectures.
We might be able to use a trick similar to RustCrypto/password-hashes#440 where we detect the relevant CPU features and call a special function annotated with target_feature to ensure it's always used where available.
POLYVAL/GHASH can be broken down into a parallelizable portion and a sequential portion... there's an accumulation of the output that is inherently sequential, but multiplication of the inputs can be performed in parallel.
I am not sure I understand. In our implementation we XOR input block x with inner state y, multiply the XOR result with h, and store the multiplication result in y. I don't see where we can process 4 input blocks at once, which can be done with _mm512_clmulepi64_epi128
We already implement POLYVAL in parallel using ILP. It could use VPCLMULQDQ instead (automatically, when available, as opposed to requiring special RUSTFLAGS)
We process one block at a time. ILP is used only for the 3 _mm_clmulepi64_si128 calls, only 2 of which use the same immediate argument. Here is generated assembly for our current implementation: https://rust.godbolt.org/z/zs1acTozM In my understanding, at most we can explicitly merge 2 CLMUL calls with 0x00 immediate into one _mm256_clmulepi64_epi128 call.
The optimization I wanted to explore in this particular issue is to find a way to enable VPCLMULQDQ optimizations without the user having to pass -C target-cpu=skylake as RUSTFLAGS, i.e. by enabling the required features via target_feature(enable = "...")
I don't see where we can process 4 input blocks at once, which can be done with _mm512_clmulepi64_epi128
I think that instruction in particular could line up quite nicely with #290, and would be able to process 4 message blocks in parallel.
There's also _mm256_clmulepi64_epi128, though it seems like CPUs that support that probably also support _mm512_clmulepi64_epi128? I think the latter probably better aligns with our problem, and would reduce amortized CLMULs per block to 1 AFAICT.
There's still a reduction to work out, but that should be 1 CLMUL.
I'm also curious if target_feature(enable = "vpclmulqdq") if LLVM would be able to simply promote code like that to using 512-bit ops. I swear I saw it do that in Godbolt one time.
Okay, after a lot of confusion I think I finally understand what's going on here.
So investigating #316 I observed the behavior I was poorly recalling in my previous comment.
The R/F algorithm implementation we adapted describes itself as "AVX2" and enables the avx2 target feature, but @newpavlov observed it wasn't actually using any AVX2 instructions, despite the original implementation being heavily described as an AVX2 implementation.
I observed there was one effect of adding avx2 to target_features: the compiler emitted VPCLMULQDQ instead of PCLMULQDQ, which is what this issue is all about achieving.
However, it's using VPCLMULQDQ with XMM registers, so again no actual AVX2 dependency.
Sooo... enabling avx2 indirectly enables avx. And that's all we actually need to get rustc/LLVM to emit VPCLMULQDQ:
I think we can just change the CPU feature detection to look for a combination of avx and pclmulqdq, which should be widely supported on x86 hardware, and drop all mention of "AVX2" (for now).
As noted in other comments in this issue there is opportunity for AVX2 (_mm256_clmulepi64_epi128) and AVX-512 (_mm512_clmulepi64_epi128) support, namely those are the VPCLMULQDQ instructions that act on YMM and ZMM registers respectively, but I think we can open separate issues for those if there's interest.
We could just switch to the vpclmulqdq target feature since it implies both avx and pclmulqdq. Wider intrinsics are gated on AVX2 or AVX-512 in addition to vpclmulqdq.
Activity
newpavlov commentedon Jul 27, 2023
This is reply to this comment.
I am not sure I understand. In our implementation we XOR input block
xwith inner statey, multiply the XOR result withh, and store the multiplication result iny. I don't see where we can process 4 input blocks at once, which can be done with_mm512_clmulepi64_epi128Maybe you had Poly1305 in mind?
tarcieri commentedon Jul 27, 2023
We already implement POLYVAL in parallel using ILP. It could use VPCLMULQDQ instead (automatically, when available, as opposed to requiring special RUSTFLAGS)
newpavlov commentedon Jul 27, 2023
We process one block at a time. ILP is used only for the 3
_mm_clmulepi64_si128calls, only 2 of which use the same immediate argument. Here is generated assembly for our current implementation: https://rust.godbolt.org/z/zs1acTozM In my understanding, at most we can explicitly merge 2 CLMUL calls with 0x00 immediate into one_mm256_clmulepi64_epi128call.tarcieri commentedon Jul 27, 2023
The optimization I wanted to explore in this particular issue is to find a way to enable VPCLMULQDQ optimizations without the user having to pass
-C target-cpu=skylakeas RUSTFLAGS, i.e. by enabling the required features viatarget_feature(enable = "...")tarcieri commentedon Feb 8, 2026
I think that instruction in particular could line up quite nicely with #290, and would be able to process 4 message blocks in parallel.
There's also
_mm256_clmulepi64_epi128, though it seems like CPUs that support that probably also support_mm512_clmulepi64_epi128? I think the latter probably better aligns with our problem, and would reduce amortized CLMULs per block to 1 AFAICT.There's still a reduction to work out, but that should be 1 CLMUL.
tarcieri commentedon Feb 8, 2026
FWIW the relevant code that could use AVX-512 looks like:
Particularly the "R/F multiply all 4 blocks (16 CLMULs)" step could be reduced to 4 512-bit CLMULs that operate over 4 blocks in parallel.
tarcieri commentedon Feb 8, 2026
I'm also curious if
target_feature(enable = "vpclmulqdq")if LLVM would be able to simply promote code like that to using 512-bit ops. I swear I saw it do that in Godbolt one time.tarcieri commentedon Feb 28, 2026
Okay, after a lot of confusion I think I finally understand what's going on here.
So investigating #316 I observed the behavior I was poorly recalling in my previous comment.
The R/F algorithm implementation we adapted describes itself as "AVX2" and enables the
avx2target feature, but @newpavlov observed it wasn't actually using any AVX2 instructions, despite the original implementation being heavily described as an AVX2 implementation.I observed there was one effect of adding
avx2totarget_features: the compiler emitted VPCLMULQDQ instead of PCLMULQDQ, which is what this issue is all about achieving.However, it's using VPCLMULQDQ with XMM registers, so again no actual AVX2 dependency.
Sooo... enabling
avx2indirectly enablesavx. And that's all we actually need to get rustc/LLVM to emit VPCLMULQDQ:https://godbolt.org/z/d3PEM9ec8
I think we can just change the CPU feature detection to look for a combination of
avxandpclmulqdq, which should be widely supported on x86 hardware, and drop all mention of "AVX2" (for now).As noted in other comments in this issue there is opportunity for AVX2 (
_mm256_clmulepi64_epi128) and AVX-512 (_mm512_clmulepi64_epi128) support, namely those are the VPCLMULQDQ instructions that act on YMM and ZMM registers respectively, but I think we can open separate issues for those if there's interest.newpavlov commentedon Feb 28, 2026
We could just switch to the
vpclmulqdqtarget feature since it implies bothavxandpclmulqdq. Wider intrinsics are gated on AVX2 or AVX-512 in addition tovpclmulqdq.tarcieri commentedon Feb 28, 2026
Nice, yeah that sounds like the actual correct solution.
Edit: MSRV 1.89+
polyval: use `vpclmulqdq` feature on `x86`/`x86_64`
polyval: use `avx`+`pclmulqdq` feature on `x86`/`x86_64`
polyval: use `avx`+`pclmulqdq` features on `x86`/`x86_64` (RustCrypto…