Skip to content

Precompiled WASM 2.5.0 fails in stable browsers due to compact import encoding #252

Closed

Description

@matbech

Precompiled WASM 2.5.0 fails in stable browsers due to compact import encoding

Description

The precompiled WASM bundle from:

sqlite3mc-2.5.0-sqlite-3.53.4-wasm.zip

cannot be loaded by stable Chromium or Firefox because its sqlite3.wasm
uses the experimental WebAssembly compact-import encoding.

Reproduction

Extract sqlite3.wasm from the release archive and run:

const response = await fetch("./sqlite3.wasm");
const bytes = await response.arrayBuffer();
await WebAssembly.compile(bytes);

Actual result

Chromium reports:

CompileError: WebAssembly.compile(): unknown import kind 0x7f

Firefox reports an equivalent error:

CompileError: unsupported import kind

The encoding is not enabled by default in current stable Chromium or Firefox.

Expected result

The precompiled WASM bundle should use the standard WebAssembly import-section
encoding and load without experimental browser flags.

Investigation

The generated sqlite3.mjs identifies the build toolchain as:

Emscripten SDK: 6.0.5

This appears to be caused by a short-lived Emscripten/Binaryen toolchain
regression:

SQLite3MC's WASM release workflow currently installs version: latest through
setup-emsdk, so the release appears to have picked up the affected toolchain
combination:

https://github.com/utelle/SQLite3MultipleCiphers/blob/main/.github/workflows/build-reusable.yml

Other projects have encountered the same unknown import kind 0x7f error with
Binaryen 132:

Verification

As a local workaround, I expanded the compact import groups into semantically
equivalent standard import entries. After that transformation, the same
SQLite3MC module loaded successfully in both Chromium and Firefox and reported:

sqlite_version() = 3.53.4
sqlite3mc_version() = SQLite3 Multiple Ciphers 2.5.0

This indicates that the SQLite3MC code itself is working and that only the
binary import-section encoding is incompatible.

Suggested fix

  1. Rebuild the WASM release with a corrected Emscripten toolchain, or explicitly
    disable compact imports.
  2. Pin the Emscripten/Binaryen toolchain used for published releases instead of
    using version: latest.
  3. Add a release smoke test that runs WebAssembly.compile() or initializes
    sqlite3.mjs in stable Chromium and Firefox before publishing the archive.

Activity

  1. utelle commented on Aug 26, 2026

    @utelle
    Owner

    Thank you for reporting this issue in such detail.

    In the meantime I added a wasm smoke test to the build workflow and pinned the emscripten version. At first I used version 6.0.2 instead of latest, but I had to find out that 6.0.2 produced a different failure. Now I tested version 6.0.3 - and that version seems to work properly.

    However, the "official" SQLite wasm distribution is based on version 5.0.1. And the previous SQLite3MC-wasm-build was based on version 6.0.0. So, I'm a bit undecided to which version I should pin emscripten.

  2. matbech commented on Aug 26, 2026

    @matbech
    ContributorAuthor

    The following patch is required when using Emscripten 6.x:

    Subject: [PATCH] Keep SQLite WASM imports in the standard encoding
    
    SQLite's generated post-link wasm-opt command enables every Binaryen feature,
    including the experimental compact-import encoding. Disable compact imports so
    the resulting module uses the standard WebAssembly import section accepted by
    browsers. 
    --- a/ext/wasm/mkwasmbuilds.c
    +++ b/ext/wasm/mkwasmbuilds.c
    @@ -497,7 +497,7 @@ static void mk_prologue(void){
           ** here, only wasm file size.
           */
           "--enable-bulk-memory-opt " /* required */
    -      "--all-features "           /* required */
    +      "--all-features --disable-compact-imports "           /* required */
           "--post-emscripten "        /* Saves roughly 12kb */
           "--strip-debug "            /* We already wasm-strip, but in
                                       ** case this environment has no
    
  3. utelle commented on Aug 27, 2026

    @utelle
    Owner

    The following patch is required when using Emscripten 6.x:

    Thanks for the hint. I will apply the patch and will pin to version 6.0.3.

  4. utelle commented on Aug 27, 2026

    @utelle
    Owner

    The patch is now applied in the release workflow. According to my own tests the WASM build should now work as expected. I released version 2.5.1. Therefore closing this issue. (Reopen if necessary.)

  5. matbech commented on Aug 27, 2026

    @matbech
    ContributorAuthor

    Thank you. I ended up with a custom WASM build. It includes chacha20 SIMD, optimizations for speed (-O2 etc) and stripped legacy crypto and other unnecessary SQLite features like multiple vfs backends etc.

  6. utelle commented on Aug 27, 2026

    @utelle
    Owner

    I ended up with a custom WASM build. It includes chacha20 SIMD, optimizations for speed (-O2 etc) and stripped legacy crypto and other unnecessary SQLite features like multiple vfs backends etc.

    The requirements in the WASM environment are certainly different from those in other areas of application. In that regard, it’s certainly not a bad idea to exclude encryption methods that are no longer recommended for use in new applications anyway, thereby reducing the size of the binaries.

    If you have specific recommendations on what should and shouldn’t be included, please let me know.

    I find your mention of chacha20 SIMD interesting. Are you using an implementation that’s available under a license similar to MIT’s? If so, which one? Other users could also benefit from improved runtime performance.

  7. matbech commented on Aug 29, 2026

    @matbech
    ContributorAuthor

    Thanks for following up. Regarding the ChaCha20 SIMD code: because it was heavily LLM-assisted, the licensing and copyright status is ambiguous, so it likely isn't suitable for upstreaming or standard open-source licensing.

    However, if you're interested in writing a clean, permissively licensed version yourself, here is how the implementation was structured and what produced the gains:

    Implementation Details:

    • Parallel Block Processing: Processes four independent 64-byte blocks in parallel.
    • In-Register Transposition & XOR: Transposes and XORs the four-block result entirely in SIMD registers.
    • Fast Rotations: Uses byte shuffles for 16-bit and 8-bit rotations.
    • Row-Vector Handling: Uses a row-vector SIMD implementation for remaining complete blocks.
    • Scalar Tail: Retains the upstream scalar implementation for a final partial block.

    Benchmark Takeaways:

    • ChaCha20 SIMD: Achieved a 2.5x performance speedup over the scalar baseline:
      ChaCha20: 2.545× throughput, 624.48 MiB/s → 1.55 GiB/s
      Complete 4 KiB page encryption: 1.804×, 456.61 → 823.83 MiB/s
    • Poly1305 SIMD: Showed no measurable improvement over scalar, so it can safely remain scalar to avoid extra complexity.

    Hopefully, those implementation details and benchmark points help guide your optimization efforts for the WASM build.

  8. utelle commented on Aug 29, 2026

    @utelle
    Owner

    Thanks for following up. Regarding the ChaCha20 SIMD code: because it was heavily LLM-assisted, the licensing and copyright status is ambiguous, so it likely isn't suitable for upstreaming or standard open-source licensing.

    Thank you for the clear explanation. In fact, I would need verified license and copyright information.

    However, if you're interested in writing a clean, permissively licensed version yourself, here is how the implementation was structured and what produced the gains:

    I’d really like to avoid developing everything from scratch. So I did a little research to see what established ChaCha20 implementations are out there. In the process, I took a look at libsodium. Since this is a well-known project licensed under a license compatible with the MIT License, adopting its ChaCha20 implementation would be a realistic option.

    The implementation in libsodium is available in four variants, each based on a different level of hardware support: SSSE3, AVX2, AVX512, and NEON.

    In the meantime, I've already extracted the relevant code and adapted it for SQLite3MC. But it will still take some time to test what performance gains can be achieved with it.

    Benchmark Takeaways:

    • ChaCha20 SIMD: Achieved a 2.5x performance speedup over the scalar baseline:
      ChaCha20: 2.545× throughput, 624.48 MiB/s → 1.55 GiB/s
      Complete 4 KiB page encryption: 1.804×, 456.61 → 823.83 MiB/s

    I'm curious to see how the libsodium implementation will measure up in comparison.

    • Poly1305 SIMD: Showed no measurable improvement over scalar, so it can safely remain scalar to avoid extra complexity.

    Thank you for sharing your insights. This reinforces my decision not to make any changes to the Poly1305 implementation. While libsodium also has a Poly1305 implementation with hardware acceleration, it is only supported for SSSE3 and, out of the box, can only be compiled with GCC or clang, since it uses 128-bit integers that are not supported by Visual C++.

    Hopefully, those implementation details and benchmark points help guide your optimization efforts for the WASM build.

    Thank you very much for the detailed and helpful feedback.

    If my experiments with the libsodium implementation are successful, I will include this option in one of the next SQLite3MC releases.

  9. utelle commented on Sep 2, 2026

    @utelle
    Owner

    In branch hwaccel I have added the ChaCha20 implementation with hardware acceleration from libsodium. AFAICT this implementation shows a performance comparable to the numbers you showed from your implementation. Maybe you could give it a try and compare results with your implementation.

    There are 2 additional pragma commands, you can use to specify which hardware feature you want to use.

    PRAGMA mc_cpu_info;

    displays the CPU features detected.

    PRAGMA mc_chacha20_hwaccel;

    displays the currently used CPU feature used by the libsodium implementation.

    PRAGMA mc_chacha20_hwaccel=<requested feature>;

    allows to select the feature you'd like to use, and returns the feature actually selected.

    Supported values are off, ssse3, avx2, avx512f, neon, auto, max.

    off - no hardware acceleration
    ssse3, avx2, avx512f - corresponding CPU feature on x86 systems
    neon - corresponding feature on ARM systems
    auto - highest feature officially supported by libsodium (currrently avx2 (x86) resp off (ARM))
    max - highest feature available (currently avx512f (x86), neon (ARM))

  10. matbech commented on Sep 2, 2026

    @matbech
    ContributorAuthor

    Thanks for the update.
    I wasn't able to review the libsodium integration, but Emscripten provides drop-in replacement headers for x86 SSE intrinsics that compile directly down to WebAssembly SIMD opcodes. So the SSSE3 implementation in libsodium can be used for WASM SIMD.
    What I'm curious though is why you pulled in all the stream abstraction (crypto_stream_chacha20_implementation) and not just the implementation for chacha20_xor?

    Do you use the following Emscripten flags?

    emcc -O2 -msimd128 -mssse3 ...
    

    If you plan to do a native translation from SSSE3:

    // Helper to emulate _mm_set_epi8 for rot8 and rot16 constants
    // (Note: _mm_set_epi8 takes arguments in reverse order, high byte to low byte)
    #define WASM_SET_EPI8(b15,b14,b13,b12,b11,b10,b9,b8,b7,b6,b5,b4,b3,b2,b1,b0) \
        wasm_i8x16_make(b0,b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11,b12,b13,b14,b15)
    
    // Helper to emulate _mm_set_epi64x
    #define WASM_SET_EPI64X(e1, e0) \
        wasm_i64x2_make((int64_t)(e0), (int64_t)(e1))
    
    SSSE3 / SSE2 Function Native WASM SIMD Function (wasm_simd128.h)
    _mm_set1_epi32(val) wasm_i32x4_splat(val)
    _mm_set1_epi64x(val) wasm_i64x2_splat(val)
    _mm_add_epi32(a, b) wasm_i32x4_add(a, b)
    _mm_add_epi64(a, b) wasm_i64x2_add(a, b)
    _mm_xor_si128(a, b) wasm_v128_xor(a, b)
    _mm_or_si128(a, b) wasm_v128_or(a, b)
    _mm_slli_epi32(a, imm) wasm_i32x4_shl(a, imm)
    _mm_srli_epi32(a, imm) wasm_i32x4_shr(a, imm)
    _mm_shuffle_epi8(a, mask) wasm_i8x16_swizzle(a, mask)
    _mm_unpacklo_epi32(a, b) wasm_i32x4_shuffle(a, b, 0, 4, 1, 5)
    _mm_unpackhi_epi32(a, b) wasm_i32x4_shuffle(a, b, 2, 6, 3, 7)
    _mm_unpacklo_epi64(a, b) wasm_i64x2_shuffle(a, b, 0, 2)
    _mm_unpackhi_epi64(a, b) wasm_i64x2_shuffle(a, b, 1, 3)
    _mm_loadu_si128(ptr) wasm_v128_load(ptr)
    _mm_storeu_si128(ptr, v) wasm_v128_store(ptr, v)

    I assume this is the exact translation the Emscripten header already does.

  11. utelle commented on Sep 3, 2026

    @utelle
    Owner

    Thanks for the update. I wasn't able to review the libsodium integration, but Emscripten provides drop-in replacement headers for x86 SSE intrinsics that compile directly down to WebAssembly SIMD opcodes. So the SSSE3 implementation in libsodium can be used for WASM SIMD.

    Yes, that was/is the intention. However, I haven't tested to build the WASM variant yet.

    What I'm curious though is why you pulled in all the stream abstraction (crypto_stream_chacha20_implementation) and not just the implementation for chacha20_xor?

    The first step was to adapt the libsodium code more or less "as is", so that it could compiled within the source amalgamation. The second step was to test that it works properly. And that's where I stand at the moment. A third step will be to remove unused parts. However, in that course I will check what parts may be useful in the future, because I consider to add a XChaCha20-based encryption scheme in the future.

    Do you use the following Emscripten flags?

    emcc -O2 -msimd128 -mssse3 ...
    

    I learned that it is not possible in WASM to detect and select hardware support at runtime. So, it has to be decided whether a single build supporting SIMD is enough, or whether 2 different builds would be required - with and without SIMD support.

    If you plan to do a native translation from SSSE3:

    I'm not sure whether it's worth the effort, if Emscripten can transform SSSE3 functions automatically.

    BTW, I saw your PRs #254, #255, #256, and #257. Please allow a few days while I look into these in detail, as I'm currently dealing with a few personal matters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions