Skip to content

-sSINGLE_FILE: embedded wasm string decodes to 1 extra byte (0x20), producing invalid wasm (large binaries) #27600

Description

@ronen-blumberg

Version

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 6.0.6 (ce75e06)
clang version 24.0.0git (llvm-project ff6d537b14d737719d6377789784d04ff9565f65)
Target: wasm32-unknown-emscripten
Installed via emsdk "latest" on Linux x86_64 (Devuan).

Problem

Building with -sSINGLE_FILE produces an .html whose embedded wasm fails to
instantiate in Firefox 140 ESR:

CompileError: wasm validation error: at offset 8421340: invalid data initializer-kind field

The same project built without -sSINGLE_FILE produces a .wasm that
wasm-opt --all-features validates and that runs correctly in the same browser.

This emcc version embeds the wasm as a raw JS string literal decoded by:

function binaryDecode(f){for(var b,v=0,n=f.length,t=new Uint8Array(n);v<n;++v)b=f.charCodeAt(v),t[v]=~b>>8&b;return t}

(not base64 as older versions did).

What I found

  • Reference (multi-file) wasm: 15,022,632 bytes, valid.

  • Saving the result of findWasmBinary() from the browser on the single-file
    build gives 15,022,633 bytes — one extra byte.

  • Byte-level comparison: a single 0x20 (space) is present at decoded offset
    8,417,950 that does not exist in the reference wasm. Every byte before it
    is identical; every byte after is identical but shifted by one. The validation
    error at offset 8,421,340 is the parser failing on the shifted data a few KB later.

  • The spurious space physically exists in the emitted .html: inside the embedded
    string literal, between a literal > character and a \f escape sequence.
    The surrounding emitted text (escapes shown as written in the file):

    2\r%> \f%3
    

    decodes to reference bytes 32 0d 25 3e | 0c 25 33 — the space between >
    and \f corresponds to nothing in the reference binary. So this is an
    encode-side defect (the emitted string already contains the extra byte),
    not a browser/decoding problem.

  • Possibly relevant observation: escaping in the emitted string is inconsistent —
    e.g. byte 0x0C appears in some places as a raw control character and in other
    places as \f — which may suggest a post-processing pass (HTML/JS
    minification?) rewriting whitespace-class characters inside the string literal
    after emission.

  • Counter-test: embedding the identical wasm bytes as a base64 data: URI
    in the same .html loads and runs perfectly.

  • Size dependence: small programs (hello-world size) built with -sSINGLE_FILE
    work fine. Two separate large projects fail the same way: a ~15 MB wasm
    (extra byte at 8,417,950) and a ~7.5 MB wasm (validation error at 7,490,125).

Link flags used

-sSINGLE_FILE -sASYNCIFY -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=1073741824
-sEXPORTED_RUNTIME_METHODS=stringToNewUTF8 --shell-file <custom shell> -Oz

The C source is generated by the FreeBASIC compiler's emscripten target
(fbc -target js-asmjs), which drives emcc for compile and link.

Reproduction

Happens consistently for me with multi-MB wasm binaries. The failing .html is
~18 MB — happy to provide it, the matching reference .wasm, and exact byte
offsets on request, or to test candidate fixes / run with EMCC_DEBUG=1.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions