From ad3ae21667c2c13f894ed96a064525e68df1e577 Mon Sep 17 00:00:00 2001 From: Calvin Prewitt Date: Fri, 24 Jul 2026 07:51:46 -0500 Subject: [PATCH 1/5] Only define Z_SOLO on wasm32-unknown-unknown, not the wasi targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- build.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 60cabf0a..1a1f37a7 100644 --- a/build.rs +++ b/build.rs @@ -116,7 +116,13 @@ fn build_zlib(cfg: &mut cc::Build, target: &str) { .file("src/zlib/uncompr.c") .file("src/zlib/zutil.c"); - if target.starts_with("wasm32") { + // Z_SOLO is zlib's "no C library at all" mode: it removes the default + // 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 + // library, so they get the standard build. + if target == "wasm32-unknown-unknown" { cfg.define("Z_SOLO", None); // zlib 1.3.2 uses `NULL` directly in compress.c/uncompr.c, but the // Z_SOLO config path doesn't pull in headers that always define it. From 505aa1767d2e3d95122fa003a923136159637a85 Mon Sep 17 00:00:00 2001 From: "GPT 5.6" Date: Fri, 24 Jul 2026 21:29:35 +0200 Subject: [PATCH 2/5] Test wasm32-wasip1 in CI 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 --- .github/workflows/ci.yml | 17 +++++++++++++++++ tests/wasm.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 tests/wasm.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86058aac..b750b7cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,23 @@ jobs: matrix: platform: [linux-musl] + wasm: + runs-on: ubuntu-latest + env: + CC_wasm32_wasip1: /tmp/wasi-sdk-33.0-x86_64-linux/bin/clang + AR_wasm32_wasip1: /tmp/wasi-sdk-33.0-x86_64-linux/bin/llvm-ar + CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + submodules: true + - uses: bytecodealliance/actions/wasmtime/setup@9152e710e9f7182e4c29ad218e4f335a7b203613 # v1 + with: + version: 46.0.1 + - run: rustup target add wasm32-wasip1 + - run: curl -fsSL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-33/wasi-sdk-33.0-x86_64-linux.tar.gz | tar xz -C /tmp + - run: cargo test --target wasm32-wasip1 --test wasm + windows: runs-on: ${{ matrix.runner || 'windows-latest' }} # Windows technically doesn't need this, but if we don't block windows on it diff --git a/tests/wasm.rs b/tests/wasm.rs new file mode 100644 index 00000000..7422c1f0 --- /dev/null +++ b/tests/wasm.rs @@ -0,0 +1,35 @@ +#![cfg(target_os = "wasi")] + +#[test] +fn compress_roundtrip() { + let input = b"libz-sys on WebAssembly"; + let mut compressed = vec![0; unsafe { libz_sys::compressBound(input.len() as _) } as usize]; + let mut compressed_len = compressed.len() as _; + + assert_eq!( + unsafe { + libz_sys::compress( + compressed.as_mut_ptr(), + &mut compressed_len, + input.as_ptr(), + input.len() as _, + ) + }, + libz_sys::Z_OK + ); + + let mut output = vec![0; input.len()]; + let mut output_len = output.len() as _; + assert_eq!( + unsafe { + libz_sys::uncompress( + output.as_mut_ptr(), + &mut output_len, + compressed.as_ptr(), + compressed_len, + ) + }, + libz_sys::Z_OK + ); + assert_eq!(&output[..output_len as usize], input); +} From b250b93faa1b1575d08da50f50179ef9b8bfd34c Mon Sep 17 00:00:00 2001 From: "GPT 5.6" Date: Sat, 25 Jul 2026 06:32:26 +0200 Subject: [PATCH 3/5] Check the Z_SOLO WebAssembly build in CI 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 --- .github/workflows/ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b750b7cb..a69cf68e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,24 @@ jobs: matrix: platform: [linux-musl] + wasm-no-libc: + runs-on: ubuntu-latest + env: + CRATE_CC_NO_DEFAULTS: 1 + CC_wasm32_unknown_unknown: /tmp/wasi-sdk-33.0-x86_64-linux/bin/clang --target=wasm32-unknown-unknown + AR_wasm32_unknown_unknown: /tmp/wasi-sdk-33.0-x86_64-linux/bin/llvm-ar + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + submodules: true + - run: rustup target add wasm32-unknown-unknown + - run: curl -fsSL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-33/wasi-sdk-33.0-x86_64-linux.tar.gz | tar xz -C /tmp + - run: cargo build --target wasm32-unknown-unknown --no-default-features --features stock-zlib + - run: | + archive=$(find target/wasm32-unknown-unknown/debug/build -path '*/out/lib/libz.a' -print -quit) + test -n "$archive" + ! /tmp/wasi-sdk-33.0-x86_64-linux/bin/llvm-nm -u "$archive" | grep -Eq ' U (malloc|free)$' + wasm: runs-on: ubuntu-latest env: From 47408442e421c0af678886a5b0d1cc32fd49ce42 Mon Sep 17 00:00:00 2001 From: "GPT 5.6" Date: Sat, 25 Jul 2026 06:34:28 +0200 Subject: [PATCH 4/5] Check libc-backed WASI targets after the Z_SOLO split 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 --- .github/workflows/ci.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a69cf68e..6123e1a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,12 +43,17 @@ jobs: test -n "$archive" ! /tmp/wasi-sdk-33.0-x86_64-linux/bin/llvm-nm -u "$archive" | grep -Eq ' U (malloc|free)$' - wasm: + wasm-wasi: runs-on: ubuntu-latest env: CC_wasm32_wasip1: /tmp/wasi-sdk-33.0-x86_64-linux/bin/clang AR_wasm32_wasip1: /tmp/wasi-sdk-33.0-x86_64-linux/bin/llvm-ar CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime + CC_wasm32_wasip1_threads: /tmp/wasi-sdk-33.0-x86_64-linux/bin/clang + AR_wasm32_wasip1_threads: /tmp/wasi-sdk-33.0-x86_64-linux/bin/llvm-ar + CC_wasm32_wasip2: /tmp/wasi-sdk-33.0-x86_64-linux/bin/clang + AR_wasm32_wasip2: /tmp/wasi-sdk-33.0-x86_64-linux/bin/llvm-ar + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: @@ -56,9 +61,12 @@ jobs: - uses: bytecodealliance/actions/wasmtime/setup@9152e710e9f7182e4c29ad218e4f335a7b203613 # v1 with: version: 46.0.1 - - run: rustup target add wasm32-wasip1 + - run: rustup target add wasm32-wasip1 wasm32-wasip1-threads wasm32-wasip2 - run: curl -fsSL https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-33/wasi-sdk-33.0-x86_64-linux.tar.gz | tar xz -C /tmp - run: cargo test --target wasm32-wasip1 --test wasm + - run: cargo test --target wasm32-wasip2 --test wasm + # The threads ABI imports shared memory, which needs a custom host. + - run: cargo test --target wasm32-wasip1-threads --test wasm --no-run windows: runs-on: ${{ matrix.runner || 'windows-latest' }} From 1a2d5353acf3d0d7db1e2030c84ec4589efa6a17 Mon Sep 17 00:00:00 2001 From: "GPT 5.6" Date: Sat, 25 Jul 2026 06:35:54 +0200 Subject: [PATCH 5/5] Run Emscripten after the Z_SOLO split 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 --- .github/workflows/ci.yml | 14 ++++++++++++++ tests/wasm.rs | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6123e1a2..64a7b49e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,20 @@ jobs: # The threads ABI imports shared memory, which needs a custom host. - run: cargo test --target wasm32-wasip1-threads --test wasm --no-run + wasm-emscripten: + runs-on: ubuntu-latest + env: + CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER: node + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + submodules: true + - uses: emscripten-core/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16 + with: + version: 6.0.4 + - run: rustup target add wasm32-unknown-emscripten + - run: cargo test --target wasm32-unknown-emscripten --test wasm --features static + windows: runs-on: ${{ matrix.runner || 'windows-latest' }} # Windows technically doesn't need this, but if we don't block windows on it diff --git a/tests/wasm.rs b/tests/wasm.rs index 7422c1f0..1a5dc4b9 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -1,4 +1,4 @@ -#![cfg(target_os = "wasi")] +#![cfg(any(target_os = "wasi", target_os = "emscripten"))] #[test] fn compress_roundtrip() {