Skip to content

Use AES hardware support on Apple arm64 and with GCC on aarch64 - #262

Open
mtrossbach wants to merge 1 commit into
utelle:mainfrom
SchwarzDigits:fix/aes-hw-detection
Open

mtrossbach wants to merge 1 commit into
utelle:mainfrom
SchwarzDigits:fix/aes-hw-detection

Conversation

@mtrossbach

Copy link
Copy Markdown
Contributor

Hi Ulrich,

first of all, thank you for SQLite3 Multiple Ciphers! It's a great piece of work, and the modular cipher design made it easy to find my way around the code.

While measuring AES performance on Apple Silicon, I noticed two gaps in src/aes_hardware.c:

  • The existing ARMv8 AES code is compiled on Apple devices but never used.
  • GCC builds on aarch64 don't include it at all.

This small PR closes both. The encryption output doesn't change in any way; the only difference is that the much faster hardware path is now picked wherever it's available.

What changes (only src/aes_hardware.c, +23 lines)

  • Apple arm64 (macOS, iOS, iPadOS, tvOS, watchOS, visionOS): aesHardwareAvailableOnPlatform() had no Apple branch and fell through to return 0. As a result, aes128cbc, aes256cbc and sqlcipher used the table-based software AES. All 64-bit ARM processors Apple uses (A7 and later, M1 and later) implement the ARMv8 cryptographic extensions, so the new branch simply returns 1. OpenSSL and Botan make the same assumption, and the bundled libaegis already relies on it (src/aegis/common/cpu.c).
  • GCC on aarch64: the compiler detection only covered GCC on x86, so without -march=...+crypto no hardware path was compiled. For GCC ≥ 6, the NEON code path is now enabled with the function attribute target("+crypto"), just like the existing clang handling. The runtime check (getauxval(AT_HWCAP) & HWCAP_AES) was already in place.
  • Everything else is untouched: x86, MSVC, clang, Windows on ARM, and the CBC functions themselves.

A nice side effect: the existing CI jobs on macos-latest and ubuntu-24.04-arm (gcc) will now really exercise the hardware code paths. At the moment they quietly test the software fallback.

Performance (Apple M4 Max)

  • AES-256-CBC, 4 KiB blocks: encrypt 314 → 2145 MB/s, decrypt 386 → 16392 MB/s.
  • aes256cbc database, 100,000 rows × 1,000 bytes:
    • Write: 0.59 s → 0.30 s.
    • Read: 0.31 s → 0.06 s.
    • On Linux aarch64 with GCC 13, read time goes from 0.33 s → 0.08 s.
How I tested it
  • macOS (Apple M4 Max, Apple clang 21):
    • The CI test scripts (test1test4, sqlciphertest) produce identical output before and after the change.
    • The NIST SP 800-38A F.2.5/F.2.6 CBC-AES256 vector passes through the hardware path.
    • Databases written with the previous build open with the new one and vice versa, for aes128cbc, aes256cbc and sqlcipher.
  • iOS simulator (iPhone 17, iOS 26.5, arm64): aesHardwareAvailable() goes from 0 to 1, and the known-answer test passes.
  • Apple SDKs (Xcode 26.5):
    • On arm64 iOS, tvOS, watchOS (arm64_32 and arm64) and visionOS, the platform check now returns 1.
    • The x86_64 simulator and 32-bit armv7k watchOS are unchanged.
    • The amalgamation compiles for all of them without new warnings.
  • Linux aarch64 (Ubuntu 24.04 container, CI recipe autoreconf; ./configure; make):
    • GCC 13.3: previously no hardware path, now active. The known-answer test passes, CI output is identical, and databases are cross-compatible.
    • clang 18.1 (as a control): unchanged.
  • GCC version gate:
    • GCC 6.5, 7.5 and 8.5: hardware path active, known-answer test passes, and the amalgamation compiles without warnings.
    • GCC 5.5: stays on the software path and compiles cleanly.
  • Android (NDK 28.2):
    • arm64-v8a already used the hardware path and is unchanged. A static arm64 binary passes the known-answer test.
    • x86_64 and armeabi-v7a are unchanged.

ARM64 machines aren't always at hand, so I tried to test as broadly as I could. I'm happy to run anything else you'd like to see. If you prefer a different approach, I'll gladly adjust it. One example would be a sysctlbyname()-based check on Apple instead of the constant.

While digging into the AES code, I came across a few more small things around AES and random number generation. I'd like to propose them separately, each as its own small PR or issue, so they stay easy to review.

Thanks a lot for taking a look!

Best regards,
Markus

On 64-bit Apple ARM platforms the NEON AES code was compiled, but the
runtime check always reported that no hardware support is available, so
the table-based software implementation was used. With GCC on aarch64 no
hardware code path was compiled at all, unless the crypto extension was
enabled via compiler flags.

- Report AES hardware support on 64-bit Apple ARM platforms (macOS, iOS,
  iPadOS, tvOS, watchOS, visionOS)
- Enable the NEON AES code path for GCC >= 6 on aarch64 via a function
  target attribute, analogous to the existing clang handling
@utelle

utelle commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Hi Markus,

While measuring AES performance on Apple Silicon, I noticed two gaps in src/aes_hardware.c:

  • The existing ARMv8 AES code is compiled on Apple devices but never used.
  • GCC builds on aarch64 don't include it at all.

Yes, that is unfortunately true, and I'm really sorry I didn't notice that myself sooner. And unfortunately, no one else has brought this to my attention so far. So thank you very much for taking the time not only to report the problem, but also to provide a fix for it right away.

In fact, I came across this issue myself recently when I started working on improving hardware support for the ChaCha20-Poly1305 algorithm. This work is being done in the hwaccel branch and is not yet complete. I hope to complete that task within the next couple of weeks and to merge it into the main branch, before the next release is due.

Hardware detection is now done in hwcheck.c. That is, I removed the hardware detection part from aes_hardware.c.

That's why I don't want to merge your PR into the main branch right now.

I've implemented additional pragmas that allow us to check for existing hardware support at runtime. And the CI runs confirm that the AES hardware is now correctly detected on Apple systems.

The target attribute +crypto for GCC has not yet been implemented in the hwaccel branch. I'll add it myself later today.

Thereafter it would be great if you could run your tests again in the hwaccel branch to see whether it already works as expected. TIA.

Performance (Apple M4 Max)

  • AES-256-CBC, 4 KiB blocks: encrypt 314 → 2145 MB/s, decrypt 386 → 16392 MB/s.

  • aes256cbc database, 100,000 rows × 1,000 bytes:

    • Write: 0.59 s → 0.30 s.
    • Read: 0.31 s → 0.06 s.
    • On Linux aarch64 with GCC 13, read time goes from 0.33 s → 0.08 s.

That's a remarkable improvement.

ARM64 machines aren't always at hand,

That's exactly my problem. For development I use a x64 machine. ARM64 tests are only done through the GitHub CI runs.

so I tried to test as broadly as I could. I'm happy to run anything else you'd like to see. If you prefer a different approach, I'll gladly adjust it. One example would be a sysctlbyname()-based check on Apple instead of the constant.

Not sure whether we need that. IMHO such checks should only be added if the current detection methods are not enough.

While digging into the AES code, I came across a few more small things around AES and random number generation. I'd like to propose them separately, each as its own small PR or issue, so they stay easy to review.

I haven't touched the AES code for quite a long time. So, if you detected issues with the AES code please report them, and I'll be happy to address them.

utelle added a commit that referenced this pull request Sep 11, 2026
The target attribute "+crypto" was not set even if aarch64 hardware was detected. That is, AES hardware support was not used on aarch64 platforms (see PR #262).
@mtrossbach

Copy link
Copy Markdown
Contributor Author

Hi Ulrich,

thanks a lot for the quick and kind reply! No need to apologize: the fallback is silent, so it's easy to miss without ARM hardware at hand.

Moving the detection into hwcheck.c makes sense, and the hwaccel work sounds great. So I'm fine with not merging this PR. Feel free to close it whenever it suits you.

As soon as the GCC +crypto change is in the hwaccel branch, I'll run the same tests there and report back in this thread. That includes the new mc_aes_info and mc_cpu_info pragmas:

  • macOS on Apple Silicon
  • the iOS simulator
  • an Android arm64 build (NDK)
  • Linux aarch64 with GCC 5, 6, 7 and 13 as well as clang, including the timings

One detail for the GCC part: the __GNUC__ >= 6 gate in my PR was more conservative than necessary. GCC 5.5 (the Ubuntu/Linaro build) accepts target("+crypto") on aarch64 as well. The hardware path compiles without warnings and passes the NIST test vectors there, just like with GCC 6.5, 7.5, 8.5 and 13.3.

Agreed on sysctlbyname(): the constant is enough.

And thanks for the offer regarding the AES code. I'll report the other findings as separate issues, one at a time, so they don't get in the way of hwaccel.

Best regards,
Markus

@utelle

utelle commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Hi Markus,

thanks a lot for the quick and kind reply!

You are welcome.

No need to apologize: the fallback is silent, so it's easy to miss without ARM hardware at hand.

Yes, indeed. Nevertheless it is at least now easy to catch some ARM issues with GitHub providing access to ARM CI runners.

Moving the detection into hwcheck.c makes sense, and the hwaccel work sounds great.

The chacha20 cipher scheme will have a much better performance with hardware support.

So I'm fine with not merging this PR. Feel free to close it whenever it suits you.

Ok, thanks.

As soon as the GCC +crypto change is in the hwaccel branch,

Commit 6cee36e in the hwaccel branch introduces the required changes (hopefully).

I'll run the same tests there and report back in this thread.

Great. TIA.

That includes the new mc_aes_info and mc_cpu_info pragmas:

These pragmas show the available CPU features, but as we saw it's no proof that they are actually used. However, after your tests we will know whether the fix was successful.

  • macOS on Apple Silicon
  • the iOS simulator
  • an Android arm64 build (NDK)
  • Linux aarch64 with GCC 5, 6, 7 and 13 as well as clang, including the timings

That's really fantastic, because I myself have only very limited access to Apple hardware.

One detail for the GCC part: the __GNUC__ >= 6 gate in my PR was more conservative than necessary. GCC 5.5 (the Ubuntu/Linaro build) accepts target("+crypto") on aarch64 as well. The hardware path compiles without warnings and passes the NIST test vectors there, just like with GCC 6.5, 7.5, 8.5 and 13.3.

Ok. The check can be changed to check for GCC >= 5.5.

And thanks for the offer regarding the AES code. I'll report the other findings as separate issues, one at a time, so they don't get in the way of hwaccel.

I'm looking forward to inspect your issues and/or PRs.

@mtrossbach

Copy link
Copy Markdown
Contributor Author

Hi Ulrich,

thanks for adding the GCC part so quickly! As promised, I've run the tests on hwaccel (6cee36e) and compared it with main (7dd52e9).

In short: with hwaccel, AES hardware is now used on every platform I could test. The NIST SP 800-38A CBC-AES256 test vectors pass on the hardware path everywhere.

Platform main hwaccel aes256cbc, 50,000 rows × 1,000 bytes, write / read
macOS, Apple M4 Max, Apple clang 21 software hardware 0.316 / 0.154 s → 0.167 / 0.028 s
iOS simulator (iPhone 17), Apple clang 21 software hardware 0.342 / 0.157 s → 0.158 / 0.027 s
Linux aarch64, GCC 13.3 no hardware path hardware 0.289 / 0.169 s → 0.156 / 0.033 s
Linux aarch64, GCC 7.5 no hardware path hardware 0.279 / 0.157 s → 0.174 / 0.034 s
Linux aarch64, GCC 6.5 no hardware path hardware 0.356 / 0.190 s → 0.174 / 0.029 s
Linux aarch64, clang 18 hardware hardware 0.160 / 0.033 s → 0.151 / 0.030 s
Android arm64, NDK 28 (clang 19), API 30 / 34 hardware hardware 0.227 / 0.032 s → 0.189 / 0.030 s (API 30)

In every configuration, databases with aes128cbc, aes256cbc, sqlcipher (legacy=4) and chacha20 could be written, reopened and read, and integrity_check returned "ok". With GCC 13, the CI recipe also runs without errors on hwaccel. The new pragmas report hardware and neon, armcrypto.

One small observation: PRAGMA mc_aes_info reflects the CPU features at runtime, not whether the hardware path has been compiled in. For example, with SQLITE3MC_OMIT_AES_HARDWARE_SUPPORT it still reports hardware, although the software path is used. Perhaps it could take the compile-time availability into account as well.

How I tested
  • Test program: a small program that includes the amalgamation. It prints the compile-time and runtime detection and the new pragmas, runs the NIST test vectors directly against aesEncryptCBC/aesDecryptCBC, round-trips the databases, and times aes256cbc. It used the same compile flags as the CI build.
  • Linux: the Linux aarch64 builds ran in Docker on the M4 (Ubuntu 24.04 for GCC 13 and clang 18, Ubuntu 18.04 for GCC 6 and 7).
  • Android: the binaries were linked statically and run in the same arm64 Linux environment, not on a physical device. For these builds I applied the fix from the separate issue mentioned below.
  • iOS: the iOS build ran in the simulator on the M4.

I also ran into an Android build error in main that is unrelated to hwaccel. I'll open a separate issue for that.

Best regards,
Markus

@utelle

utelle commented Sep 11, 2026

Copy link
Copy Markdown
Owner

One small observation: PRAGMA mc_aes_info reflects the CPU features at runtime, not whether the hardware path has been compiled in. For example, with SQLITE3MC_OMIT_AES_HARDWARE_SUPPORT it still reports hardware, although the software path is used. Perhaps it could take the compile-time availability into account as well.

Commit 6b13f6e changes the behaviour of pragma mc_aes_info (in branch hwaccel). It will now report whether AES hardware support is actually used. Whether the CPU supports AES instructions is (already) reported by pragma mc_cpu_info.

@utelle

utelle commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Hi Markus,

in the meantime I cleaned up a bit the new code for chacha20 hardware support, and added code for hardware support of poly1305. All changes happened in the hwaccel branch.

If you can find the time, it would be great, if you could re-run your test-suite. TIA.

BTW, it wouldn't be a bad idea to have a slightly more thorough functional test available in GitHub's CI environment. Do you think it's possible to use a GitHub workflow to integrate part of your test suite (for example, NIST test vectors, and so on)?

Best regards,
Ulrich

@mtrossbach

Copy link
Copy Markdown
Contributor Author

Hi Ulrich,

I re-ran the tests on the hwaccel branch (commit 8794b10). This time I used a test program that checks more than the database round trips I used before:

  • Every ChaCha20 and Poly1305 implementation that is compiled for the platform and supported by the processor is checked against the test vectors from RFC 8439.
  • All implementations are compared with each other on pseudo-random input of many different lengths.
  • The tag comparison functions are checked with equal tags and with tags that differ in a single bit.
  • For AES, the CBC test vectors from NIST SP 800-38A are checked against the software implementation, the Rijndael CBC functions used by the cipher schemes, and the hardware implementation.

Results

The good news first: every implementation computes correct results on all platforms I tested. The problems below only affect the build and the speed.

  • ARM64 (macOS, iOS simulator, Android, Linux with GCC and Clang): all checks pass. The NEON implementation of ChaCha20 is about twice as fast as the sqleet implementation (2110 instead of 990 MB/s on an Apple M4). On ARM, the option auto currently selects off, so it isn't used by default.
  • x86_64 (Linux with GCC 13 and Clang 18): with your int128 fix, all checks pass, including the SSSE3 and AVX2 implementations of ChaCha20 and the SSE2 implementation of Poly1305. I couldn't test AVX-512.
  • WebAssembly (Emscripten 6.0.9): the branch doesn't compile at the moment. With the two fixes below, all checks pass, with and without SIMD (-msimd128).

Problems with WebAssembly

  1. In src/chacha20poly1305.c, line 344, the JavaScript code in EM_JS reads typeof crypto == = 'undefined'. This is a syntax error in JavaScript, so Emscripten stops the build. It has to be ===, as on main. It came in with commit 0354e1b; maybe a code formatter split the operator.
  2. In src/chacha20/src/stream_chacha20.c, line 177, crypto_stream_chacha20_pick_best_implementation() also uses the SSSE3 implementation for WebAssembly, but chacha20_dolbeau-ssse3.c is only included for x86. Since mcCpuFeaturesWasm() never reports SSSE3, this branch can't be selected on WebAssembly anyway, so I limited it to x86.

Leftover test code? poly1305() (lines 746 to 757) calls the selected implementation and then also sqleet_poly1305() and donna_poly1305(), whose results aren't used. It looks like test code that was left in. Because of it, poly1305() is currently about three times slower than on main (1145 instead of 3396 MB/s on an Apple M4).

The two fixes as a diff
diff --git a/src/chacha20/src/stream_chacha20.c b/src/chacha20/src/stream_chacha20.c
--- a/src/chacha20/src/stream_chacha20.c
+++ b/src/chacha20/src/stream_chacha20.c
@@ -174,7 +174,7 @@ sqlite3mcChaCha20HwConfig(const char* option)
 SODIUM_EXPORT int
 crypto_stream_chacha20_pick_best_implementation(void)
 {
-#if defined(SQLITE3MC_TARGET_X86) || defined(SQLITE3MC_TARGET_WASM)
+#if defined(SQLITE3MC_TARGET_X86)
 #if defined(SQLITE3MC_TARGET_X86)
   if (gChaCha20HwAccelRequest >= SQLITE3MC_CHACHA20_HWACCL_AVX512 &&
       sqlite3mcCpuFeatures() & SQLITE3MC_CPU_AVX512F)
diff --git a/src/chacha20poly1305.c b/src/chacha20poly1305.c
--- a/src/chacha20poly1305.c
+++ b/src/chacha20poly1305.c
@@ -341,7 +341,7 @@ int poly1305_tagcmp_scalar(const uint8_t tag1[16], const uint8_t tag2[16])
   * for correctness if entropy() is ever called with a larger buffer. */
 EM_JS(int, wasm_crypto_getrandom, (uint8_t* buf, size_t n),
 {
-  if (typeof crypto == = 'undefined' || !crypto.getRandomValues)
+  if (typeof crypto === 'undefined' || !crypto.getRandomValues)
     return -1;
   try
   {

CI: Yes, that's possible. I've prepared a pull request that adds the test program as test/cryptotest.c and runs it in ci4sqlite3mc.yml. I'll open it after this comment. It's based on main, where it can only check the default implementations, and it applies to hwaccel without changes.

Best regards,
Markus

@utelle

utelle commented Sep 13, 2026

Copy link
Copy Markdown
Owner

I re-ran the tests on the hwaccel branch (commit 8794b10).

Thanks a lot! This definitely strengthens confidence in the correctness of the implemented cryptographic algorithms.

Results

The good news first: every implementation computes correct results on all platforms I tested. The problems below only affect the build and the speed.

  • ARM64 (macOS, iOS simulator, Android, Linux with GCC and Clang): all checks pass. The NEON implementation of ChaCha20 is about twice as fast as the sqleet implementation (2110 instead of 990 MB/s on an Apple M4). On ARM, the option auto currently selects off, so it isn't used by default.

The default value off is chosen on purpose, because the NEON implementation is not yet officially included in libsodium. However, by choosing the option value neon (or max) it can be selected. As soon as it is an official part of libsodium I will change the default value.

  • x86_64 (Linux with GCC 13 and Clang 18): with your int128 fix, all checks pass, including the SSSE3 and AVX2 implementations of ChaCha20 and the SSE2 implementation of Poly1305. I couldn't test AVX-512.

Again, the default value for x86-64 architecture is avx2, because the avx512f implementation is not yet an official part of libsodium. Besides that it is unclear whether avx512f really increases performance in general due to CPU throttling effects at least on some CPU models.

  • WebAssembly (Emscripten 6.0.9): the branch doesn't compile at the moment. With the two fixes below, all checks pass, with and without SIMD (-msimd128).

Currently, no special SIMD code is used in WASM builds, but that may change in future versions.

Problems with WebAssembly

  1. In src/chacha20poly1305.c, line 344, the JavaScript code in EM_JS reads typeof crypto == = 'undefined'. This is a syntax error in JavaScript, so Emscripten stops the build. It has to be ===, as on main. It came in with commit 0354e1b; maybe a code formatter split the operator.

Arrgggh... I fixed it on main but obviously forgot to do the same on hwaccel.

  1. In src/chacha20/src/stream_chacha20.c, line 177, crypto_stream_chacha20_pick_best_implementation() also uses the SSSE3 implementation for WebAssembly, but chacha20_dolbeau-ssse3.c is only included for x86. Since mcCpuFeaturesWasm() never reports SSSE3, this branch can't be selected on WebAssembly anyway, so I limited it to x86.

If the WASM build is done with -msimd128 code based on SSSE3 should be compilable and operational. Therefore I adjusted the code, so that the SSSE3-based version of ChaCha20 is compiled in that case, and the CPU feature SSSE3 is set in addition to SSE2. (Unfortunately, I can't test that myself at the moment.)

Leftover test code? poly1305() (lines 746 to 757) calls the selected implementation and then also sqleet_poly1305() and donna_poly1305(), whose results aren't used. It looks like test code that was left in. Because of it, poly1305() is currently about three times slower than on main (1145 instead of 3396 MB/s on an Apple M4).

Yes, that was test code - which is now removed.

CI: Yes, that's possible. I've prepared a pull request that adds the test program as test/cryptotest.c and runs it in ci4sqlite3mc.yml. I'll open it after this comment. It's based on main, where it can only check the default implementations, and it applies to hwaccel without changes.

Thank you very much. Your PR is already merged on main.

@mtrossbach

Copy link
Copy Markdown
Contributor Author

Hi Ulrich,

thanks for the quick fixes. I tested commit 951fd7c:

  • ARM64 and x86_64: all checks pass.
  • WebAssembly without SIMD: all checks pass.
  • WebAssembly with -msimd128 -mssse3: the SSSE3 implementation is selected (mc_chacha20_hwaccel reports ssse3) and all checks pass. ChaCha20 is about 1.8 times as fast as without it (about 1800 instead of 1000 MB/s in Node.js on an Apple M4).
  • WebAssembly with only -msimd128: the build fails, because Emscripten's SSE headers also require -mssse3 (error: "SSSE3 instruction set not enabled"). The official WebAssembly package is built without SIMD, so it isn't affected.

Checking __SSSE3__ in addition to __wasm_simd128__ fixes this. With only -msimd128, the SSSE3 code isn't compiled and ChaCha20 uses the default implementation. I tested all three cases: no SIMD, -msimd128, and -msimd128 -mssse3.

Diff
diff --git a/src/chacha20/src/stream_chacha20.c b/src/chacha20/src/stream_chacha20.c
--- a/src/chacha20/src/stream_chacha20.c
+++ b/src/chacha20/src/stream_chacha20.c
@@ -10,7 +10,7 @@
 /* libsodium reference implementation */
 #include "ref/chacha20_ref.c"
 
-#if defined(SQLITE3MC_TARGET_X86) || (defined(SQLITE3MC_TARGET_WASM) && defined(__wasm_simd128__))
+#if defined(SQLITE3MC_TARGET_X86) || (defined(SQLITE3MC_TARGET_WASM) && defined(__wasm_simd128__) && defined(__SSSE3__))
 /* Original order: avx512, avx2, ssse3 */
 # include "dolbeau/chacha20_dolbeau-ssse3.c"
 #if defined(SQLITE3MC_TARGET_X86)
@@ -192,7 +192,7 @@ crypto_stream_chacha20_pick_best_implementation(void)
     return 0;
   }
 #endif
-#if defined(SQLITE3MC_TARGET_X86) || (defined(SQLITE3MC_TARGET_WASM) && defined(__wasm_simd128__))
+#if defined(SQLITE3MC_TARGET_X86) || (defined(SQLITE3MC_TARGET_WASM) && defined(__wasm_simd128__) && defined(__SSSE3__))
   if (gChaCha20HwAccelRequest >= SQLITE3MC_CHACHA20_HWACCL_SSSE3 &&
       sqlite3mcCpuFeatures() & SQLITE3MC_CPU_SSSE3)
   {
diff --git a/src/hwcheck.c b/src/hwcheck.c
--- a/src/hwcheck.c
+++ b/src/hwcheck.c
@@ -364,7 +364,11 @@ static unsigned int
 mcCpuFeaturesWasm(void)
 {
 #if defined(__wasm_simd128__)
+#if defined(__SSSE3__)
   return SQLITE3MC_CPU_SSE2 | SQLITE3MC_CPU_SSSE3; /* funktional aequivalente Ebene: 128-Bit-Generic-SIMD */
+#else
+  return SQLITE3MC_CPU_SSE2; /* funktional aequivalente Ebene: 128-Bit-Generic-SIMD */
+#endif
 #else
   return SQLITE3MC_CPU_NONE;
 #endif

Best regards,
Markus

@utelle

utelle commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Hi Markus,

thanks for the quick fixes. I tested commit 951fd7c:

Great. Thank you!

  • ARM64 and x86_64: all checks pass.
  • WebAssembly without SIMD: all checks pass.
  • WebAssembly with -msimd128 -mssse3: the SSSE3 implementation is selected (mc_chacha20_hwaccel reports ssse3) and all checks pass. ChaCha20 is about 1.8 times as fast as without it (about 1800 instead of 1000 MB/s in Node.js on an Apple M4).

Very good news!

Regarding WebAssembly builds I currently provide with SQLite3MC releases only a pre-built package corresponding to the WASM package distributed by SQLite itself (32 bits, without SIMD) - actually using the original SQLite build procedure.

I'm not sure, whether this should be changed somehow in the future.

  • WebAssembly with only -msimd128: the build fails, because Emscripten's SSE headers also require -mssse3 (error: "SSSE3 instruction set not enabled").

Ok, good to know.

Checking __SSSE3__ in addition to __wasm_simd128__ fixes this. With only -msimd128, the SSSE3 code isn't compiled and ChaCha20 uses the default implementation. I tested all three cases: no SIMD, -msimd128, and -msimd128 -mssse3.

Thanks for testing all variants. I will add the check for __SSSE3__ later today.

@utelle

utelle commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Commit 5d473fe adds the necessary checks to detect available hardware support properly for WASM builds.

@mtrossbach

Copy link
Copy Markdown
Contributor Author

Hi Ulrich,

thanks for merging #267 and for looking at it in detail! Moving the random number generation into its own source file sounds good.

I tested hwaccel at 5fbe256. It builds for ARM64, x86_64, 32-bit ARM (armeabi-v7a, with and without NEON) and PowerPC, and all checks pass on macOS, Android arm64, Linux x86_64 and WebAssembly.

Two small things are left:

  1. Typo: the WebAssembly checks test __SSS3E__ instead of __SSSE3__ (twice in src/chacha20/src/stream_chacha20.c, once in src/hwcheck.c). Because of that, the SSSE3 implementation is never used, even with -msimd128 -mssse3. With the correct spelling, it's selected, all checks pass, and ChaCha20 is about 1.75 times as fast.
  2. test/cryptotest.c from main needs the ARM64 restriction for the NEON functions as well. Otherwise it doesn't compile for armeabi-v7a with NEON after you merge main into hwaccel.

Both changes are in the diff below.

Diff
diff --git a/src/chacha20/src/stream_chacha20.c b/src/chacha20/src/stream_chacha20.c
--- a/src/chacha20/src/stream_chacha20.c
+++ b/src/chacha20/src/stream_chacha20.c
@@ -10,7 +10,7 @@
 /* libsodium reference implementation */
 #include "ref/chacha20_ref.c"
 
-#if defined(SQLITE3MC_TARGET_X86) || (defined(SQLITE3MC_TARGET_WASM) && defined(__wasm_simd128__) && defined(__SSS3E__))
+#if defined(SQLITE3MC_TARGET_X86) || (defined(SQLITE3MC_TARGET_WASM) && defined(__wasm_simd128__) && defined(__SSSE3__))
 /* Original order: avx512, avx2, ssse3 */
 # include "dolbeau/chacha20_dolbeau-ssse3.c"
 #if defined(SQLITE3MC_TARGET_X86)
@@ -196,7 +196,7 @@ crypto_stream_chacha20_pick_best_implementation(void)
     return 0;
   }
 #endif
-#if defined(SQLITE3MC_TARGET_X86) || (defined(SQLITE3MC_TARGET_WASM) && defined(__wasm_simd128__) && defined(__SSS3E__))
+#if defined(SQLITE3MC_TARGET_X86) || (defined(SQLITE3MC_TARGET_WASM) && defined(__wasm_simd128__) && defined(__SSSE3__))
   if (gChaCha20HwAccelRequest >= SQLITE3MC_CHACHA20_HWACCL_SSSE3 &&
       sqlite3mcCpuFeatures() & SQLITE3MC_CPU_SSSE3)
   {
diff --git a/src/hwcheck.c b/src/hwcheck.c
--- a/src/hwcheck.c
+++ b/src/hwcheck.c
@@ -369,7 +369,7 @@ mcCpuFeaturesPpc(void)
 static unsigned int
 mcCpuFeaturesWasm(void)
 {
-#if defined(__wasm_simd128__) && defined(__SSS3E__)
+#if defined(__wasm_simd128__) && defined(__SSSE3__)
   return SQLITE3MC_CPU_SSE2 | SQLITE3MC_CPU_SSSE3; /* functional equivalent level: 128-bit generic SIMD */
 #else
   return SQLITE3MC_CPU_NONE;
diff --git a/test/cryptotest.c b/test/cryptotest.c
--- a/test/cryptotest.c
+++ b/test/cryptotest.c
@@ -444,7 +444,7 @@ LIBSODIUM_CHACHA20(chacha20LibsodiumSsse3, crypto_stream_chacha20_dolbeau_ssse3_
 LIBSODIUM_CHACHA20(chacha20LibsodiumAvx2, crypto_stream_chacha20_dolbeau_avx2_implementation)
 LIBSODIUM_CHACHA20(chacha20LibsodiumAvx512, crypto_stream_chacha20_dolbeau_avx512_implementation)
 #endif
-#if defined(SQLITE3MC_TARGET_ARM) && defined(__ARM_NEON)
+#if defined(SQLITE3MC_TARGET_ARM) && defined(__ARM_NEON) && (defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC))
 LIBSODIUM_CHACHA20(chacha20LibsodiumNeon, crypto_stream_chacha20_dolbeau_neon_implementation)
 #endif
 
@@ -460,7 +460,7 @@ static const ChaCha20Implementation chacha20List[] =
   { "libsodium avx2",   chacha20LibsodiumAvx2,   SQLITE3MC_CPU_AVX2 },
   { "libsodium avx512", chacha20LibsodiumAvx512, SQLITE3MC_CPU_AVX512F },
 #endif
-#if defined(SQLITE3MC_TARGET_ARM) && defined(__ARM_NEON)
+#if defined(SQLITE3MC_TARGET_ARM) && defined(__ARM_NEON) && (defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC))
   { "libsodium neon",   chacha20LibsodiumNeon,   SQLITE3MC_CPU_NEON },
 #endif
 #endif
@@ -623,7 +623,7 @@ static const TagCompareImplementation tagCompareList[] =
 #if defined(SQLITE3MC_TARGET_X86)
   { "sse2",            poly1305_tagcmp_sse2,      SQLITE3MC_CPU_SSE2 },
   { "sse41",           poly1305_tagcmp_sse41,     SQLITE3MC_CPU_SSE41 },
-#elif defined(SQLITE3MC_TARGET_ARM) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC))
+#elif defined(SQLITE3MC_TARGET_ARM) && (defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC))
   { "neon",            poly1305_tagcmp_neon,      SQLITE3MC_CPU_NEON },
 #elif defined(SQLITE3MC_TARGET_WASM) && defined(__wasm_simd128__)
   { "wasm simd",       poly1305_tagcmp_wasm_simd, 0 },

Best regards,
Markus

@mtrossbach

Copy link
Copy Markdown
Contributor Author

Hi Ulrich,

thanks for fixing both so quickly, and for merging #268. I tested hwaccel at 2093d6e with the cryptotest from the branch:

  • All checks pass on macOS, Android arm64, Linux x86_64 and ARM64 (GCC and Clang, also with the CI steps autoreconf, configure, make cryptotest) and WebAssembly.
  • WebAssembly with -msimd128 -mssse3 now selects the SSSE3 implementation.
  • It builds for 32-bit ARM (with and without NEON) and PowerPC.
  • The random number generation in src/csprng_entropy.c is identical to main.

Nothing left from my side.

Best regards,
Markus

@utelle

utelle commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Hi Markus,

thanks for fixing both so quickly,

Well, I'm working on SQLite3MC anyway these days, in preparation for the next release, and hoping I'll manage to include all enhancements in it.

I tested hwaccel at 2093d6e with the cryptotest from the branch:

  • All checks pass on macOS, Android arm64, Linux x86_64 and ARM64 (GCC and Clang, also with the CI steps autoreconf, configure, make cryptotest) and WebAssembly.
  • WebAssembly with -msimd128 -mssse3 now selects the SSSE3 implementation.
  • It builds for 32-bit ARM (with and without NEON) and PowerPC.

For 32-bit ARM there will not be much performance improvement, because most NEON implementations use some instructions that were introduced with ARM64 only. However, I don't think that that imposes a big problem, because most devices have ARM64 processors nowadays.

  • The random number generation in src/csprng_entropy.c is identical to main.

Thanks for confirming.

I plan to merge branch hwaccel back to main within this week - if all goes well.

Nothing left from my side.

Don't hesitate to propose changes and/or additions you may find useful for SQLite3MC.

@utelle

utelle commented Sep 14, 2026

Copy link
Copy Markdown
Owner

@mtrossbach
Hi Markus,

I decided to merge branch hwaccel already today. There remain only some minor things on my to-do list, not directly related to hardware acceleration.

@mtrossbach

Copy link
Copy Markdown
Contributor Author

Hi Ulrich,

thanks for merging hwaccel so quickly. I tested main at 8392d70 before the 2.6.0 release, including the amalgamation built the way your release workflow does it: all tests pass on macOS, Linux, Android and WebAssembly, and it builds for 32-bit ARM, PowerPC and with MinGW-w64.

Best regards,
Markus

@utelle

utelle commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Hi Markus,

thanks for merging hwaccel so quickly.

That is also to your credit. Your intensive testing and your contributions played a significant role in establishing the new features.

I tested main at 8392d70 before the 2.6.0 release, including the amalgamation built the way your release workflow does it: all tests pass on macOS, Linux, Android and WebAssembly, and it builds for 32-bit ARM, PowerPC and with MinGW-w64.

Thanks a lot. The acid test will come after the 2.6.0 release, when the binaries for the NuGet packages are built.

However, there is still time until then, as it will likely take a few more weeks for SQLite 3.54.0 to be released.

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.

2 participants