Skip to content

fix: Emit one zlib stream per compressed debug section - #2162

Merged
lapla-cogito merged 2 commits into
wild-linker:mainfrom
lapla-cogito:compress_shard
Jul 1, 2026
Merged

lapla-cogito merged 2 commits into
wild-linker:mainfrom
lapla-cogito:compress_shard

Conversation

@lapla-cogito

@lapla-cogito lapla-cogito commented Jul 1, 2026

Copy link
Copy Markdown
Member

Previously, large compressed debug sections could be emitted incorrectly once a section was split into multiple compression chunks (chunk size is at least 64 KiB). Consumers then saw truncated or corrupt DWARF. (Also see #2119 (comment))

For example, with a buggy wild-linked binary:

(▰╹◡╹)❯  llvm-dwarfdump --statistics wild/tests/build/elf/x86_64/compressed-debug-gdb-scripts/zlib/compressed-debug-gdb-scripts.rs.wild|head
warning: DWARF unit at offset 0x000c8b80 has unsupported version 0, supported are 2-5
warning: unable to decode LEB128 at offset 0x0004a0fe: malformed uleb128, extends past end
warning: last sequence in debug line table at offset 0x000003fd is not terminated
{
  "version": 9,
  "file": "wild/tests/build/elf/x86_64/compressed-debug-gdb-scripts/zlib/compressed-debug-gdb-scripts.rs.wild",
  "format": "elf64-x86-64",
  "#functions": 50,
  "#functions with location": 49,
  "#out-of-line functions": 103,
  "#inlined functions": 1798,
  "#inlined functions with abstract origins": 1798,
  "#unique source variables": 13,

whereas GNU ld on the same inputs (note that there is no warning and the number of functions is very different from wild's case):

(▰╹◡╹)❯  llvm-dwarfdump --statistics wild/tests/build/elf/x86_64/compressed-debug-gdb-scripts/zlib/compressed-debug-gdb-scripts.rs.ld|head
{
  "version": 9,
  "file": "wild/tests/build/elf/x86_64/compressed-debug-gdb-scripts/zlib/compressed-debug-gdb-scripts.rs.ld",
  "format": "elf64-x86-64",
  "#functions": 9370,
  "#functions with location": 9370,
  "#out-of-line functions": 1925,
  "#inlined functions": 44899,
  "#inlined functions with abstract origins": 44899,

The root cause was concatenating complete zlib streams from ZlibEncoder::finish() per chunk. We now compress raw deflate shards in parallel with Z_SYNC_FLUSH boundaries, then emit a single zlib stream (header + shards + empty stored block + combined Adler-32).

@lapla-cogito

Copy link
Copy Markdown
Member Author

The output of the llvm-dwarfdump command after this change:

(▰╹◡╹)❯  llvm-dwarfdump --statistics wild/tests/build/elf/x86_64/compressed-debug-gdb-scripts/zlib/compressed-debug-gdb-scripts.rs.wild|head
{
  "version": 9,
  "file": "wild/tests/build/elf/x86_64/compressed-debug-gdb-scripts/zlib/compressed-debug-gdb-scripts.rs.wild",
  "format": "elf64-x86-64",
  "#functions": 9370,
  "#functions with location": 9370,
  "#out-of-line functions": 1925,
  "#inlined functions": 44899,
  "#inlined functions with abstract origins": 44899,
  "#unique source variables": 17,

@davidlattimore davidlattimore left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for fixing this!

Do you think it'd be worth adding a unit test that creates some data, zlib compresses it, concatenates the chunks, decompresses it and then checks that we get back to what we started with?

Comment thread libwild/src/compression.rs Outdated
let mut output = Vec::new();
zstd::stream::copy_encode(chunk, &mut output, ZSTD_COMPRESSION_LEVEL)?;
Ok(output)
impl SectionCompressor for ZstdCompressor {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are tools that consume zstd OK with multiple, independently compressed chunks? i.e. do we need to (in a separate PR) look to make a similar fix for zstd, or is it already OK?

@lapla-cogito lapla-cogito Jul 1, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

In zstd, a sequence of concatenated frames is a valid format (yes, I should have mentioned this from the beginning 😅):

Zstandard compressed data is made up of one or more frames. Each frame is independent and can be decompressed independently of other frames. The decompressed content of multiple concatenated frames is the concatenation of each frame's decompressed content.

https://www.rfc-editor.org/rfc/rfc8878.html#section-3.1

@lapla-cogito
lapla-cogito merged commit c96a3b4 into wild-linker:main Jul 1, 2026
22 checks passed
@lapla-cogito
lapla-cogito deleted the compress_shard branch July 1, 2026 23:19
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.

2 participants