Add ARM64 crypto as NEON C intrinsics for MSVC (WOLFSSL_ARMASM_INTRINSICS) - #11035
Draft
coneco-cy wants to merge 1 commit into
Draft
Add ARM64 crypto as NEON C intrinsics for MSVC (WOLFSSL_ARMASM_INTRINSICS)#11035coneco-cy wants to merge 1 commit into
coneco-cy wants to merge 1 commit into
Conversation
…SICS)
wolfSSL's ARM64 crypto kernels ship in two forms: GNU/GAS assembly with GCC
inline asm (armv8-*-asm.S / -asm_c.c), and Microsoft-syntax assembly assembled
by armasm64.exe (armv8-*-asm.asm). The armasm64 route covers MSVC ARM64 and is
wired up in wolfssl.vcxproj via WolfSSLAarch64Asm.
That route needs a separate assembler invocation, which in practice is only
wired up through MSBuild CustomBuild rules. Projects consuming wolfSSL through
CMake - or any build without an ARM64 assembler step - cannot use it as-is, and
fall back to portable C for AES, AES-GCM (including table-based GHASH),
SHA-256, Poly1305 and ChaCha20.
This adds a third form: the same kernels as portable NEON C intrinsics, which
cl.exe compiles as ordinary C. No assembler step, no generated .asm, and no
per-source target-feature flag (MSVC enables NEON crypto unconditionally on
ARM64, unlike clang which needs -march=armv8-a+crypto).
The four new files supply the same *_AARCH64 symbols as the assembly, so they
are an alternative to it rather than an addition. They are opt-in behind
WOLFSSL_ARMASM_INTRINSICS and each is self-guarding on
_MSC_VER && !__clang__ && _M_ARM64, so every existing configuration and
toolchain is unaffected.
armv8-aes-intrinsics-msvc.c 19 AES/AES-GCM/AES-CBC *_AARCH64 kernels
plus 7 base-layer fallback symbols
armv8-sha-intrinsics-msvc.c Transform_Sha256_Len_crypto
armv8-poly1305-intrinsics-msvc.c 4 Poly1305 kernels
armv8-chacha-intrinsics-msvc.c 4 ChaCha20 kernels, 4-block-parallel
No existing crypto source is modified: settings.h already bridges
_M_ARM64 to __aarch64__ under WOLFSSL_ARMASM, and cpuid.c already has a
Windows ARM64 IsProcessorFeaturePresent() path, so the gates and the runtime
feature probe work as-is.
Each kernel was verified against three independent references: the original
inline asm extracted verbatim and built with clang-cl, independent pure-C
oracles, and published KATs (FIPS-197, FIPS 180-4, SP800-38A CBC/CTR, NIST
GCM, RFC 8439). Every test's failure path was proven first with an injected
negative control.
Not yet ported: AES_XTS_* (WOLFSSL_AES_XTS) and the _EOR3 variants
(WOLFSSL_ARMASM_CRYPTO_SHA3). A verified SHA-512 kernel is included but left
inert pending a wired-up feature gate.
|
Can one of the admins verify this patch? |
Member
|
Hi @coneco-cy , thank you for submitting this work. I've asked @SparkiDev to review it and see if this is something we'd consider accepting. Can you tell us more about your project and interest in wolfSSL? If we are to accept this we will need to get a signed contributor agreement in place. Thanks, David Garske, wolfSSL |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
wolfSSL's ARM64 crypto kernels ship in two forms today:
armv8-*-asm.S/armv8-*-asm_c.c— GNU/GAS assembly and GCC inline asmarmv8-*-asm.asm— Microsoft syntax, assembled byarmasm64.exeThe armasm64 form already covers MSVC on Windows ARM64 and is wired up in
wolfssl.vcxproj(WolfSSLAarch64Asm=true). This PR does not replace it.What it adds is a third form of the same kernels — portable NEON C
intrinsics — because the armasm64 route requires a separate assembler
invocation, and in practice that is only wired up through MSBuild
CustomBuildrules. Projects that consume wolfSSL through CMake, or anybuild system without an ARM64 assembler step, cannot use it as-is and silently
fall back to portable C for AES, AES-GCM (including the table-based GHASH),
SHA-256, Poly1305 and ChaCha20.
Intrinsics are ordinary C, so
cl.execompiles them directly: no assemblerstep, no generated
.asm, and no per-source target-feature flag (MSVC enablesNEON crypto unconditionally on ARM64, unlike clang which needs
-march=armv8-a+crypto).This came out of profiling MariaDB — which bundles wolfSSL and builds it with
CMake — on a native Windows ARM64 machine, where GHASH and AES showed up as the
top crypto hot spots purely because the hardware path was unreachable from that
build system.
What is added
Four new sibling files under
wolfcrypt/src/port/arm/, plus 44 lines of buildwiring. No existing crypto source is modified.
armv8-aes-intrinsics-msvc.c*_AARCH64kernels, plus the 7 base-layer symbolsaes.clinks unconditionallyarmv8-sha-intrinsics-msvc.cTransform_Sha256_Len_cryptoarmv8-poly1305-intrinsics-msvc.cpoly1305_set_key,poly1305_arm64_block_16,poly1305_arm64_blocks,poly1305_finalarmv8-chacha-intrinsics-msvc.cwc_chacha_setkey,wc_chacha_setiv,wc_chacha_use_over,wc_chacha_crypt_bytes— 4-block-parallelThese supply the same
*_AARCH64symbols as the assembly, so they are analternative to it, not an addition — hence an explicit opt-in rather than an
automatic fallback. Two independent layers keep existing builds untouched:
WOLFSSL_ARMASM_INTRINSICSoption, default off, whichFATAL_ERRORsif requested on a non-MSVC toolchain.
WOLFSSL_ARMASM && WOLFSSL_ARMASM_INTRINSICS && _MSC_VER && !__clang__ && (_M_ARM64 || _M_ARM64EC),so it compiles to nothing otherwise. (
!__clang__matters:clang-cldefines_MSC_VERbut ships clang'sarm_neon.h, wherevmull_p64's low form takes ascalar
poly64_trather than MSVC's__n64.)Why no changes to existing sources were needed
Worth noting explicitly, since an earlier version of this work did need them —
current master already solves both prerequisites:
settings.h:388maps
_M_ARM64→__aarch64__underWOLFSSL_ARMASM, so the ~104__aarch64__gates inaes.cselect correctly under MSVC.cpuid.c:473has a
_WIN32branch usingIsProcessorFeaturePresent(), soCheck_CPU_support_HwCrypto()works — no GCC-onlymrsprobe needed.Because
HAVE_CPUID_AARCH64derives from__aarch64__ && WOLFSSL_ARMASM, thatbridge also enables the Windows cpuid path automatically.
Testing
All on a native Windows ARM64 machine (Snapdragon X2 Elite / Oryon),
MSVC 14.44.35207, SDK 10.0.26100.0.
Each kernel was validated against three independent references, not one:
armv8-*-asm_c.cbodies were extracted verbatim by script (never retyped) into a reference TU
built with
clang-cl(cl.execannot compile GCC inline asm), then comparedover randomized inputs and edge cases — AES 100k random blocks × all three key
sizes; CBC over block counts 1/2/3/7/16/255; ChaCha over a ragged multi-call
sequence (5,1,10,64,3,100,63,65,200,7 bytes), which is the only shape that
exercises the
over/leftleftover-keystream contract.GHASH, FIPS 180-4 SHA-256/512, RFC 8439 Poly1305/ChaCha20).
(F.2.1) and AES-128-CTR (F.5.1, including a 20-byte partial tail), NIST GCM
case 4 (ciphertext + tag + valid-decrypt + corrupt-tag reject), FIPS 180-4
SHA-256/512, RFC 8439 §2.4.2 and §2.5.2.
Every test's failure path was proven before its pass was trusted. For each
kernel a negative control was injected and confirmed to turn the suite red
(exit 1): GHASH reduction constant
0x87→0x86, an extraaesmcin the finalAES round, removing the CBC chaining XOR, 128-bit→32-bit CTR increment, ChaCha
rotation 7→6, not stashing leftover keystream, advancing the ChaCha counter by 1
instead of 4, Poly1305's wrong
*5mod-2^130-5 fold multiplier.Library-level validation through the public API (
wc_AesCbcEncrypt/Decrypt,wc_AesCtrEncrypt,wc_AesGcmEncrypt/Decrypt) against the real MSVC-ARM64wolfssl.lib, including ragged chunk sizes exercisingaes->left/aes->tmpcarryover.
dumpbin -disasmconfirms MSVC emits genuine hardware instructions(
pmull/pmull2,aese/aesd/aesmc/aesimc,sha256h*,rbit); all fourfiles compile with zero warnings under real
cl.exe.The CMake wiring was verified both ways in this tree: configures clean with the
option off (files absent from
LIB_SOURCES) and on (all four present), and thenon-MSVC guard was negative-controlled to confirm it actually aborts.
Two bugs this harness caught — offered as evidence it has teeth
Transform_Sha256_Len_crypto: the asm's round 2 issha256su0 v4,v5 / add v24,v5,v9— the register updated is not the one fedto the add. Modelling it the obvious way yields a self-consistent but wrong
digest. Only the published FIPS vector caught it; an asm-vs-intrinsics
comparison would have agreed if both sides shared the misconception.
__rev(),a clang builtin that is not a declared MSVC ARM64 intrinsic — real
cl.exeemitted
warning C4013: '__rev' undefined; assuming extern returning int.Fixed to
_byteswap_ulong. A separate pure-C KAT harness built bycl.exeitself and linked against the production objects now covers the shipping
compiler's view.
Performance
Paired A/B, two builds differing only in this option; same source, flags,
data and iteration counts; warm-up pass; anti-hoist checksum printed and
verified identical between builds, so both provably did the same work.
256 MiB per case in 64 KiB chunks. Baseline is wolfSSL's real portable-C
fallback (4-bit GMULT tables), not a strawman.
In-process profiles, symbol swap visible in-trace:
For ChaCha20 a control makes it credible: an unrelated unported symbol in the
same trace moved only +6.7%, within the noise band, so the delta is not a global
shift.
To be explicit about what these are not: they are primitive/kernel-level
figures. End-to-end in the host application the effect was measurable (1.36x,
non-overlapping ranges) only for byte-bound encrypted workloads; for
latency-bound OLTP microqueries crypto was ~0.2% of runtime and the difference
was indistinguishable from run-to-run noise.
Not measured: intrinsics vs. the armasm64 assembly path. Both derive from
the same kernels so I would expect rough parity, but I have not run that
comparison and will not claim it. The case for this PR is build-system
reachability, not beating the assembly.
One counter-intuitive result worth reporting
The first ChaCha20 version did one block per iteration — correct on every test,
but measurably slower than portable C (299 ms vs 245 ms in-server). One
block is a ~16-op NEON dependency chain with no ILP, while MSVC
/O2schedulesthe 8
QUARTERROUNDexpansions well. Rewriting to 4 blocks in parallel (eachstate word broadcast across a vector, lane i = block i, which also removes
the
vextqlane rotations the diagonal rounds otherwise need) gave 2.0x. Afaithful-but-narrow intrinsic port can lose to optimized scalar C, and it is
only visible if you profile against the real fallback.
Questions for maintainers
ARM64, so this is additive convenience for non-MSBuild consumers. If you would
rather see CMake taught to invoke
armasm64.exeinstead, that is a reasonablealternative and I am happy to pursue it — I would just want your call before
investing further either way.
WOLFSSL_ARMASM_INTRINSICSthe right name and shape,or would you prefer it derived automatically when
MSVC AND ARM64 AND NOTMSBuild, or folded into the existing
WOLFSSL_ARMASM_INLINEselector as athird value?
AES_XTS_*and the_EOR3variants are absent (the configurationthis was developed against enables neither, so
aes.cnever referenced them).Enabling either with this option will not link. Should they be completed before
merge, or is the guarded partial set acceptable with the armasm64 path as the
answer for those configurations?
WOLFSSL_ARM64_SHA512_HW_MSVC(defined nowhere). When it was written there wasno usable MSVC feature gate; master's
cpuid.cnow queriesPF_ARM_SHA512_INSTRUCTIONS_AVAILABLEunderWOLFSSL_ARMASM_CRYPTO_SHA512, sowiring it up looks straightforward — but I have not done or tested that, and did
not want to ship an untested path for an optional architectural extension.
Would you like it wired up here or dropped from this PR?
and a
cl.exeKAT harness, not by wolfSSL's own suite — I could not linkwolfcrypt_test()in the host project (its config omits ~51ed25519/fe_*symbols
test.cwants). Guidance on the canonical way to runtestwolfcryptfor an MSVC ARM64 build would be welcome, and I will add coverage there.
rather than the asm's 4-way
r^1..r^4blocking. That is safe because the limbfields are private to these kernels (grep-verified: no other TU reads
ctx->r64/r1..r4/r4321), so only the MAC is observable. It iscorrectness-first; parity with the asm's throughput would need another pass.
Checklist
controls; not yet integrated into wolfSSL's own suite, see question 5