Skip to content

Only define Z_SOLO on wasm32-unknown-unknown, not the wasi/emscripten targets - #273

Merged
Byron merged 5 commits into
rust-lang:mainfrom
calvinrp:z-solo-wasi-scope
Jul 25, 2026
Merged

Only define Z_SOLO on wasm32-unknown-unknown, not the wasi/emscripten targets#273
Byron merged 5 commits into
rust-lang:mainfrom
calvinrp:z-solo-wasi-scope

Conversation

@calvinrp

Copy link
Copy Markdown
Contributor

build_zlib() defines Z_SOLO for every wasm32* target. Z_SOLO is
zlib's "no C library" mode: no default zalloc/zfree — so the one-shot
helpers compress()/uncompress() always return Z_STREAM_ERROR at
runtime — and no gz* API.

Only wasm32-unknown-unknown actually lacks a libc. The wasi targets
link 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,

let rc = libz_sys::compress(dst.as_mut_ptr(), &mut dst_len,
                            src.as_ptr(), src.len() as _);
assert_eq!(rc, libz_sys::Z_OK); // fails today: rc == -2 (Z_STREAM_ERROR)

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):

cargo new zsolo && cd zsolo && cargo add libz-sys@1.1.29
# main.rs: call libz_sys::compress() on any buffer and print the rc
cargo build --target wasm32-wasip1 && wasmtime target/wasm32-wasip1/debug/zsolo.wasm
# prints rc = -2 (Z_STREAM_ERROR); the same program prints 0 on native

This PR scopes the define to wasm32-unknown-unknown. wasi/emscripten
now take the standard source list, including the gz* files, which
compile 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.rs needs no change — it already declares
the one-shot functions unconditionally.

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 define Z_SOLO only for wasm32-unknown-unknown (instead of all wasm32* targets).
  • Ensure non-wasm32-unknown-unknown wasm targets build the standard zlib source set (including gz* 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.

Comment thread build.rs
// 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
@Byron

Byron commented Jul 24, 2026

Copy link
Copy Markdown
Member

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).

@folkertdev

Copy link
Copy Markdown
Contributor

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.

https://github.com/trifectatechfoundation/zlib-rs/blob/5a82af8a944d3b2d23e861c5438bac35bc40d16d/.github/workflows/checks.yaml#L22-L115

@Byron Byron self-assigned this Jul 24, 2026
@Byron

Byron commented Jul 24, 2026

Copy link
Copy Markdown
Member

Thanks for chiming in @folkertdev!
I will give it a shot.

@Byron

Byron commented Jul 25, 2026

Copy link
Copy Markdown
Member

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.

@Byron
Byron force-pushed the z-solo-wasi-scope branch from cbb6eda to 371aa7a Compare July 25, 2026 04:41
codex and others added 4 commits July 25, 2026 11:44
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>
@Byron
Byron force-pushed the z-solo-wasi-scope branch from 371aa7a to 1a2d535 Compare July 25, 2026 09:48
@Byron

Byron commented Jul 25, 2026

Copy link
Copy Markdown
Member

@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.

@Byron
Byron merged commit 86e55f8 into rust-lang:main Jul 25, 2026
54 checks passed
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.

5 participants