Skip to content

Commit 6695f13

Browse files
rui314claude
andcommitted
Add --compress-debug-sections=zlib:N for configurable zlib compression level
Generalize the zstd:N syntax added in the previous commit so that the zlib compression level can also be specified. zlib levels 0 through 9 are accepted, where 0 means no compression. Plain `zlib` remains equivalent to `zlib:1` (the previous hardcoded default). Also drop the objcopy/zstdcat round-trip from the zstd-level test in favor of a simpler readelf check, matching the new zlib-level test, and add error-message coverage for out-of-range levels. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ab704cf commit 6695f13

8 files changed

Lines changed: 62 additions & 36 deletions

File tree

docs/mold.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -506,12 +506,14 @@ but as `-o magic`.
506506
* `--no-build-id`:
507507
Synonym for `--build-id=none`.
508508

509-
* `--compress-debug-sections`=[ `zlib` | `zlib-gabi` | `zstd` | `zstd:`_N_ | `none` ]:
509+
* `--compress-debug-sections`=[ `zlib` | `zlib-gabi` | `zlib:`_N_ | `zstd` | `zstd:`_N_ | `none` ]:
510510
Compress DWARF debug info (`.debug_*` sections) using the zlib or zstd
511-
compression algorithm. `zlib-gabi` is an alias for `zlib`. `zstd:`_N_
512-
selects the zstd compression level, where _N_ is between 1 and 22.
513-
Higher levels achieve better compression ratios but are slower.
514-
`zstd` is equivalent to `zstd:3`.
511+
compression algorithm. `zlib-gabi` is an alias for `zlib`. `zlib:`_N_
512+
selects the zlib compression level, where _N_ is between 0 and 9 (0
513+
means no compression), and `zstd:`_N_ selects the zstd compression
514+
level, where _N_ is between 1 and 22. Higher levels achieve better
515+
compression ratios but are slower. `zlib` is equivalent to `zlib:1`
516+
and `zstd` is equivalent to `zstd:3`.
515517

516518
* `--defsym`=_symbol_=_value_:
517519
Define _symbol_ as an alias for _value_.

lib/compress.cc

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,11 @@ static std::vector<std::span<u8>> split(std::span<u8> input) {
4545
return vec;
4646
}
4747

48-
static std::span<u8> zlib_compress(std::span<u8> input) {
48+
static std::span<u8> zlib_compress(std::span<u8> input, int level) {
4949
// Initialize zlib stream. Since debug info is generally compressed
50-
// pretty well with lower compression levels, we chose compression
51-
// level 1.
50+
// pretty well with lower compression levels, the default level is 1.
5251
z_stream strm = {};
53-
CHECK(deflateInit2(&strm, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
52+
CHECK(deflateInit2(&strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
5453

5554
// Set an input buffer
5655
strm.avail_in = input.size();
@@ -94,7 +93,7 @@ static std::span<u8> zlib_compress(std::span<u8> input) {
9493
return {buf, (size_t)(bufsize - strm.avail_out)};
9594
}
9695

97-
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
96+
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size, i64 level) {
9897
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
9998
std::vector<u32> adlers(inputs.size());
10099
shards.resize(inputs.size());
@@ -103,7 +102,7 @@ ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
103102
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
104103
std::span<u8> in = inputs[i];
105104
adlers[i] = adler32(1, in.data(), in.size());
106-
shards[i] = zlib_compress(in);
105+
shards[i] = zlib_compress(in, level);
107106
});
108107

109108
// Combine checksums

lib/lib.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ class Compressor {
665665

666666
class ZlibCompressor : public Compressor {
667667
public:
668-
ZlibCompressor(u8 *buf, i64 size);
668+
ZlibCompressor(u8 *buf, i64 size, i64 level);
669669
void write_to(u8 *buf) override;
670670

671671
private:
@@ -674,7 +674,7 @@ class ZlibCompressor : public Compressor {
674674

675675
class ZstdCompressor : public Compressor {
676676
public:
677-
ZstdCompressor(u8 *buf, i64 size, i64 level = 3);
677+
ZstdCompressor(u8 *buf, i64 size, i64 level);
678678
void write_to(u8 *buf) override;
679679
};
680680

src/cmdline.cc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ static const char helpmsg[] = R"(
8989
--color-diagnostics=[auto,always,never]
9090
Use colors in diagnostics
9191
--color-diagnostics Alias for --color-diagnostics=always
92-
--compress-debug-sections [none,zlib,zlib-gabi,zstd,zstd:1,...,zstd:22]
92+
--compress-debug-sections=[none,zlib,zlib:0,...,zlib:9,zstd,zstd:1,...,zstd:22]
9393
Compress .debug_* sections
9494
--dc Ignored
9595
--dependency-file=FILE Write Makefile-style dependency rules to FILE
@@ -1041,12 +1041,21 @@ std::vector<std::string> parse_nonpositional_args(Context<E> &ctx) {
10411041
} else if (read_arg("compress-debug-sections")) {
10421042
if (arg == "zlib" || arg == "zlib-gabi") {
10431043
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZLIB;
1044+
ctx.arg.compress_debug_sections_level = 1;
1045+
} else if (arg.starts_with("zlib:")) {
1046+
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZLIB;
1047+
i64 level = parse_number(ctx, "compress-debug-sections", arg.substr(5));
1048+
if (level < 0 || 9 < level)
1049+
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg
1050+
<< " (zlib level must be between 0 and 9)";
1051+
ctx.arg.compress_debug_sections_level = level;
10441052
} else if (arg == "zstd") {
10451053
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZSTD;
1054+
ctx.arg.compress_debug_sections_level = 3;
10461055
} else if (arg.starts_with("zstd:")) {
10471056
ctx.arg.compress_debug_sections = ELFCOMPRESS_ZSTD;
10481057
i64 level = parse_number(ctx, "compress-debug-sections", arg.substr(5));
1049-
if (level < 1 || level > 22)
1058+
if (level < 1 || 22 < level)
10501059
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg
10511060
<< " (zstd level must be between 1 and 22)";
10521061
ctx.arg.compress_debug_sections_level = level;

src/mold.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2420,7 +2420,7 @@ struct Context {
24202420
bool z_text = false;
24212421
bool zero_to_bss = false;
24222422
i64 compress_debug_sections = ELFCOMPRESS_NONE;
2423-
i64 compress_debug_sections_level = 3;
2423+
i64 compress_debug_sections_level = 0;
24242424
i64 filler = -1;
24252425
i64 spare_dynamic_tags = 5;
24262426
i64 spare_program_headers = 0;

src/output-chunks.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2861,7 +2861,8 @@ CompressedSection<E>::CompressedSection(Context<E> &ctx, Chunk<E> &chunk) {
28612861
chunk.write_to(ctx, buf.get());
28622862

28632863
if (ctx.arg.compress_debug_sections == ELFCOMPRESS_ZLIB)
2864-
compressor.reset(new ZlibCompressor(buf.get(), chunk.shdr.sh_size));
2864+
compressor.reset(new ZlibCompressor(buf.get(), chunk.shdr.sh_size,
2865+
ctx.arg.compress_debug_sections_level));
28652866
else
28662867
compressor.reset(new ZstdCompressor(buf.get(), chunk.shdr.sh_size,
28672868
ctx.arg.compress_debug_sections_level));
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
. $(dirname $0)/common.inc
3+
4+
cat <<EOF | $CC -c -g -o $t/a.o -xc -
5+
#include <stdio.h>
6+
#include <stdlib.h>
7+
#include <string.h>
8+
int main() { printf("Hello world\n"); }
9+
EOF
10+
11+
# Level 0 stores data uncompressed; level 9 compresses hardest.
12+
# The level-0 executable must be larger than the level-9 one.
13+
$CC -B. -o $t/exe0 $t/a.o -Wl,--compress-debug-sections=zlib:0
14+
$CC -B. -o $t/exe9 $t/a.o -Wl,--compress-debug-sections=zlib:9
15+
readelf -WS $t/exe0 | grep -q '\.debug_info .* [Cx] '
16+
readelf -WS $t/exe9 | grep -q '\.debug_info .* [Cx] '
17+
[ $(wc -c < $t/exe0) -gt $(wc -c < $t/exe9) ]
18+
19+
# Out-of-range level should fail with a descriptive error
20+
not $CC -B. -o $t/exe-1 $t/a.o -Wl,--compress-debug-sections=zlib:-1 |&
21+
grep 'zlib level must be between 0 and 9'
22+
not $CC -B. -o $t/exe10 $t/a.o -Wl,--compress-debug-sections=zlib:10 |&
23+
grep 'zlib level must be between 0 and 9'
Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,21 @@
11
#!/bin/bash
22
. $(dirname $0)/common.inc
33

4-
# arm-linux-gnueabihf-objcopy crashes on x86-64
5-
[[ $MACHINE = arm* ]] && skip
6-
[ $MACHINE = riscv32 ] && skip
7-
8-
command -v zstdcat >& /dev/null || skip
9-
104
cat <<EOF | $CC -c -g -o $t/a.o -xc -
115
#include <stdio.h>
12-
13-
int main() {
14-
printf("Hello world\n");
15-
return 0;
16-
}
6+
int main() { printf("Hello world\n"); }
177
EOF
188

199
# Test zstd:1 (lowest level)
2010
$CC -B. -o $t/exe1 $t/a.o -Wl,--compress-debug-sections=zstd:1
21-
$OBJCOPY --dump-section .debug_info=$t/debug_info1 $t/exe1
22-
dd if=$t/debug_info1 of=$t/debug_info1.zstd bs=24 skip=1 status=none
23-
zstdcat $t/debug_info1.zstd > /dev/null
11+
readelf -WS $t/exe1 | grep -q '\.debug_info .* [Cx] '
12+
13+
# Test zstd:22 (highest level)
14+
$CC -B. -o $t/exe22 $t/a.o -Wl,--compress-debug-sections=zstd:22
15+
readelf -WS $t/exe22 | grep -q '\.debug_info .* [Cx] '
2416

25-
# Test zstd:19 (high level)
26-
$CC -B. -o $t/exe19 $t/a.o -Wl,--compress-debug-sections=zstd:19
27-
$OBJCOPY --dump-section .debug_info=$t/debug_info19 $t/exe19
28-
dd if=$t/debug_info19 of=$t/debug_info19.zstd bs=24 skip=1 status=none
29-
zstdcat $t/debug_info19.zstd > /dev/null
17+
# Out-of-range level should fail with a descriptive error
18+
not $CC -B. -o $t/exe0 $t/a.o -Wl,--compress-debug-sections=zstd:0 |&
19+
grep 'zstd level must be between 1 and 22'
20+
not $CC -B. -o $t/exe23 $t/a.o -Wl,--compress-debug-sections=zstd:23 |&
21+
grep 'zstd level must be between 1 and 22'

0 commit comments

Comments
 (0)