Only define Z_SOLO on wasm32-unknown-unknown, not the wasi/emscripten targets - #273
Conversation
build_zlib() currently defines Z_SOLO for every wasm32* target. Z_SOLO is zlib's "no C library at all" configuration: it drops the default zalloc/zfree, so the one-shot helpers compress()/uncompress() (and any deflateInit/inflateInit call that leaves zalloc NULL) permanently fail with Z_STREAM_ERROR at runtime, and the gz* file API is not built. That configuration is only correct for wasm32-unknown-unknown, which really has no libc. The wasi targets (wasm32-wasip1, wasm32-wasip2, the old wasm32-wasi) link wasi-libc — a complete C library with a working malloc/free and fd-based I/O — and wasm32-unknown-emscripten likewise ships full musl. On those targets the blanket Z_SOLO needlessly removes working API surface: code calling compress()/uncompress() through libz-sys compiles fine and then always gets Z_STREAM_ERROR (-2) back at runtime. Scope the define to wasm32-unknown-unknown. The wasi and emscripten targets now take the standard path: default allocators (the one-shot helpers work) and the gz* sources, which compile cleanly against wasi-libc's open/read/write/lseek. Observed on wasm32-wasip1 with libz-sys 1.1.29 (default features, cc path, vendored zlib 1.3.2): before this change compress() returns -2 where the same call succeeds on every non-wasm target; after it, the one-shot roundtrip works. Users of the streaming API with caller-supplied allocators (e.g. flate2) never see the failure, which is why it has gone unnoticed.
There was a problem hiding this comment.
Pull request overview
This PR fixes an incorrect zlib build configuration for WebAssembly targets by scoping Z_SOLO to only wasm32-unknown-unknown, avoiding runtime breakage of compress()/uncompress() (and gz*) on wasi/emscripten where a C library is available.
Changes:
- Change
build_zlib()to defineZ_SOLOonly forwasm32-unknown-unknown(instead of allwasm32*targets). - Ensure non-
wasm32-unknown-unknownwasm targets build the standard zlib source set (includinggz*sources). - Add clarifying comments explaining the rationale and behavioral impact of
Z_SOLO.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // zalloc/zfree (so the one-shot compress()/uncompress() helpers return | ||
| // Z_STREAM_ERROR unless the caller wires up allocators by hand) and the | ||
| // gz* file API. Of the wasm32 targets only wasm32-unknown-unknown has no | ||
| // libc; the wasi targets (wasi-libc) and emscripten ship a complete C |
|
Thanks a lot for contributing! This seems to be good, but it also doesn't look like we have any WASM tests here. @jongiddy do you think it's feasible to try to add WASM test coverage? If not, or if you don't know, do you think it's safe to merge this PR as is? Thanks you. (PS: I didn't investigate this with an agent yet). |
|
In zlib-rs we test with custom cargo runners with a wide range of target architectures, mostly with qemu but we also include wasmtime for webassembly. |
|
Thanks for chiming in @folkertdev! |
|
This seems to work - I will have the other WASM runtimes added as well so we can test all environments that are affected by this change. |
Run a compress/uncompress roundtrip under Wasmtime so the wasm32-wasip1 build exercises zlib's default allocators. The test fails with Z_STREAM_ERROR when the old blanket wasm32 Z_SOLO condition is restored and passes with the scoped condition in this PR. Use WASI SDK 33 to compile the vendored C sources and a pinned Wasmtime setup action for execution. Co-authored-by: Sebastian Thiel <sebastian.thiel@icloud.com>
Build wasm32-unknown-unknown with stock zlib and without default features. This pins the no-libc target to the Z_SOLO configuration while the WASI and Emscripten targets move to their libc-backed builds. Use WASI SDK clang with the bare WebAssembly C target because Rust and clang use different spellings for this environment. Validated with: - cargo build --target wasm32-unknown-unknown --no-default-features --features stock-zlib Co-authored-by: Sebastian Thiel <sebastian.thiel@icloud.com>
Run the compression roundtrip on wasm32-wasip1 and wasm32-wasip2, where libc provides the allocator now that Z_SOLO is limited to wasm32-unknown-unknown. Compile the same test for wasm32-wasip1-threads; running it requires a host that supplies its imported shared memory. Co-authored-by: Sebastian Thiel <sebastian.thiel@icloud.com>
Run the compression roundtrip under Node for wasm32-unknown-emscripten. This pins the Emscripten target to the libc allocator path now that Z_SOLO is limited to wasm32-unknown-unknown. Co-authored-by: Sebastian Thiel <sebastian.thiel@icloud.com>
|
@folkertdev Thanks again! There are now tests for a bunch of WASM runtimes, which validate the claims that were originally made here. Thus it appears like the PR is truly an improvements, and thanks to CI it should stay that way. |
build_zlib()definesZ_SOLOfor everywasm32*target.Z_SOLOiszlib's "no C library" mode: no default
zalloc/zfree— so the one-shothelpers
compress()/uncompress()always returnZ_STREAM_ERRORatruntime — and no
gz*API.Only
wasm32-unknown-unknownactually lacks a libc. The wasi targetslink wasi-libc (full malloc/free and fd-based I/O) and emscripten ships
musl, so there the blanket define just breaks working API: on
wasm32-wasip1,compiles fine and fails at runtime on every call, while the identical
program works on native targets. flate2 doesn't notice because it always
supplies its own allocators through the streaming API; direct libz-sys
users on wasi hit it with no workaround inside libz-sys.
Minimal repro (any wasi runtime, e.g. wasmtime):
This PR scopes the define to
wasm32-unknown-unknown. wasi/emscriptennow take the standard source list, including the
gz*files, whichcompile cleanly against wasi-libc (verified on wasm32-wasip1: the crate
builds, and a one-shot compress/uncompress roundtrip passes where it
returned -2 before).
src/lib.rsneeds no change — it already declaresthe one-shot functions unconditionally.