Skip to content

Commit 6d44a1e

Browse files
authored
[ELF] Adjust --compress-sections to support compression level
zstd excels at scaling from low-ratio-very-fast to high-ratio-pretty-slow. Some users prioritize speed and prefer disk read speed, while others focus on achieving the highest compression ratio possible, similar to traditional high-ratio codecs like LZMA. Add an optional `level` to `--compress-sections` (#84855) to cater to these diverse needs. While we initially aimed for a one-size-fits-all approach, this no longer seems to work. (https://richg42.blogspot.com/2015/11/the-lossless-decompression-pareto.html) When --compress-debug-sections is used together, make --compress-sections take precedence since --compress-sections is usually more specific. Remove the level distinction between -O/-O1 and -O2 for --compress-debug-sections=zlib for a more consistent user experience. Pull Request: #90567
1 parent 91fef00 commit 6d44a1e

File tree

8 files changed

+56
-35
lines changed

8 files changed

+56
-35
lines changed

lld/ELF/Config.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ struct Config {
224224
bool checkSections;
225225
bool checkDynamicRelocs;
226226
std::optional<llvm::DebugCompressionType> compressDebugSections;
227-
llvm::SmallVector<std::pair<llvm::GlobPattern, llvm::DebugCompressionType>, 0>
227+
llvm::SmallVector<
228+
std::tuple<llvm::GlobPattern, llvm::DebugCompressionType, unsigned>, 0>
228229
compressSections;
229230
bool cref;
230231
llvm::SmallVector<std::pair<llvm::GlobPattern, uint64_t>, 0>

lld/ELF/Driver.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,9 +1533,17 @@ static void readConfigs(opt::InputArgList &args) {
15331533
": parse error, not 'section-glob=[none|zlib|zstd]'");
15341534
continue;
15351535
}
1536-
auto type = getCompressionType(fields[1], arg->getSpelling());
1536+
auto [typeStr, levelStr] = fields[1].split(':');
1537+
auto type = getCompressionType(typeStr, arg->getSpelling());
1538+
unsigned level = 0;
1539+
if (fields[1].size() != typeStr.size() &&
1540+
!llvm::to_integer(levelStr, level)) {
1541+
error(arg->getSpelling() +
1542+
": expected a non-negative integer compression level, but got '" +
1543+
levelStr + "'");
1544+
}
15371545
if (Expected<GlobPattern> pat = GlobPattern::create(fields[0])) {
1538-
config->compressSections.emplace_back(std::move(*pat), type);
1546+
config->compressSections.emplace_back(std::move(*pat), type, level);
15391547
} else {
15401548
error(arg->getSpelling() + ": " + toString(pat.takeError()));
15411549
continue;

lld/ELF/Options.td

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,9 @@ defm compress_debug_sections:
6868
MetaVarName<"[none,zlib,zstd]">;
6969

7070
defm compress_sections: EEq<"compress-sections",
71-
"Compress non-SHF_ALLOC output sections matching <section-glob>">,
72-
MetaVarName<"<section-glob>=[none|zlib|zstd]">;
71+
"Compress output sections that match the glob and do not have the SHF_ALLOC flag."
72+
"The compression level is <level> (if specified) or a default speed-focused level">,
73+
MetaVarName<"<section-glob>={none,zlib,zstd}[:level]">;
7374

7475
defm defsym: Eq<"defsym", "Define a symbol alias">, MetaVarName<"<symbol>=<value>">;
7576

lld/ELF/OutputSections.cpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -339,12 +339,13 @@ template <class ELFT> void OutputSection::maybeCompress() {
339339
(void)sizeof(Elf_Chdr);
340340

341341
DebugCompressionType ctype = DebugCompressionType::None;
342-
for (auto &[glob, t] : config->compressSections)
343-
if (glob.match(name))
344-
ctype = t;
342+
unsigned level = 0; // default compression level
345343
if (!(flags & SHF_ALLOC) && config->compressDebugSections &&
346344
name.starts_with(".debug_") && size)
347345
ctype = *config->compressDebugSections;
346+
for (auto &[glob, t, l] : config->compressSections)
347+
if (glob.match(name))
348+
std::tie(ctype, level) = {t, l};
348349
if (ctype == DebugCompressionType::None)
349350
return;
350351
if (flags & SHF_ALLOC) {
@@ -376,13 +377,14 @@ template <class ELFT> void OutputSection::maybeCompress() {
376377
auto shardsOut = std::make_unique<SmallVector<uint8_t, 0>[]>(numShards);
377378

378379
#if LLVM_ENABLE_ZSTD
379-
// Use ZSTD's streaming compression API which permits parallel workers working
380-
// on the stream. See http://facebook.github.io/zstd/zstd_manual.html
381-
// "Streaming compression - HowTo".
380+
// Use ZSTD's streaming compression API. See
381+
// http://facebook.github.io/zstd/zstd_manual.html "Streaming compression -
382+
// HowTo".
382383
if (ctype == DebugCompressionType::Zstd) {
383384
parallelFor(0, numShards, [&](size_t i) {
384385
SmallVector<uint8_t, 0> out;
385386
ZSTD_CCtx *cctx = ZSTD_createCCtx();
387+
ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, level);
386388
ZSTD_inBuffer zib = {shardsIn[i].data(), shardsIn[i].size(), 0};
387389
ZSTD_outBuffer zob = {nullptr, 0, 0};
388390
size_t size;
@@ -410,12 +412,10 @@ template <class ELFT> void OutputSection::maybeCompress() {
410412

411413
#if LLVM_ENABLE_ZLIB
412414
// We chose 1 (Z_BEST_SPEED) as the default compression level because it is
413-
// the fastest. If -O2 is given, we use level 6 to compress debug info more by
414-
// ~15%. We found that level 7 to 9 doesn't make much difference (~1% more
415-
// compression) while they take significant amount of time (~2x), so level 6
416-
// seems enough.
415+
// fast and provides decent compression ratios.
417416
if (ctype == DebugCompressionType::Zlib) {
418-
const int level = config->optimize >= 2 ? 6 : Z_BEST_SPEED;
417+
if (!level)
418+
level = Z_BEST_SPEED;
419419

420420
// Compress shards and compute Alder-32 checksums. Use Z_SYNC_FLUSH for all
421421
// shards but the last to flush the output to a byte boundary to be

lld/docs/ReleaseNotes.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ Non-comprehensive list of changes in this release
2626
ELF Improvements
2727
----------------
2828

29-
* ``--compress-sections <section-glib>=[none|zlib|zstd]`` is added to compress
29+
* ``--compress-sections <section-glib>={none,zlib,zstd}[:level]`` is added to compress
3030
matched output sections without the ``SHF_ALLOC`` flag.
3131
(`#84855 <https://github.com/llvm/llvm-project/pull/84855>`_)
32+
(`#90567 <https://github.com/llvm/llvm-project/pull/90567>`_)
33+
* The default compression level for zlib is now independent of linker
34+
optimization level (``Z_BEST_SPEED``).
3235
* ``GNU_PROPERTY_AARCH64_FEATURE_PAUTH`` notes, ``R_AARCH64_AUTH_ABS64`` and
3336
``R_AARCH64_AUTH_RELATIVE`` relocations are now supported.
3437
(`#72714 <https://github.com/llvm/llvm-project/pull/72714>`_)

lld/docs/ld.lld.1

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,16 +156,16 @@ may be
156156
No compression.
157157
.It Cm zlib
158158
The default compression level is 1 (fastest) as the debug info usually
159-
compresses well at that level. If you want to compress it more,
160-
you can specify
161-
.Fl O2
162-
to set the compression level to 6.
159+
compresses well at that level.
163160
.It Cm zstd
164-
The compression level is 5.
161+
Use the default compression level in zstd.
165162
.El
166163
.Pp
167-
.It Fl -compress-sections Ns = Ns Ar section-glob=[none|zlib|zstd]
164+
.It Fl -compress-sections Ns = Ns Ar section-glob={none,zlib,zstd}[:level]
168165
Compress output sections that match the glob and do not have the SHF_ALLOC flag.
166+
The compression level is
167+
.Cm level
168+
(if specified) or a default speed-focused level.
169169
This is like a generalized
170170
.Cm --compress-debug-sections.
171171
.It Fl -cref

lld/test/ELF/compress-sections.s

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# CHECK1: 0000000000000010 0 NOTYPE LOCAL DEFAULT [[#]] (nonalloc0) sym0
1717
# CHECK1: 0000000000000008 0 NOTYPE LOCAL DEFAULT [[#]] (nonalloc1) sym1
1818

19-
# RUN: ld.lld -pie a.o --compress-sections '*c0=zlib' --compress-sections .debug_str=zstd -o out2
19+
# RUN: ld.lld -pie a.o --compress-sections '*c0=zlib' --compress-sections .debug_str=zstd:3 -o out2
2020
# RUN: llvm-readelf -SrsX -x nonalloc0 -x .debug_str out2 | FileCheck %s --check-prefix=CHECK2
2121

2222
# CHECK2: Name Type Address Off Size ES Flg Lk Inf Al
@@ -39,11 +39,11 @@
3939
# CHECK2-NEXT: 02000000 00000000 38000000 00000000
4040
# CHECK2-NEXT: 01000000 00000000 {{.*}}
4141

42-
## --compress-debug-sections=none takes precedence.
43-
# RUN: ld.lld a.o --compress-debug-sections=none --compress-sections .debug_str=zstd -o out3
42+
## --compress-sections takes precedence.
43+
# RUN: ld.lld a.o --compress-sections .debug_str=zstd --compress-debug-sections=none -o out3
4444
# RUN: llvm-readelf -S out3 | FileCheck %s --check-prefix=CHECK3
4545

46-
# CHECK3: .debug_str PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 01 MS 0 0 1
46+
# CHECK3: .debug_str PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 01 MSC 0 0 1
4747

4848
# RUN: not ld.lld a.o --compress-sections '*0=zlib' 2>&1 | \
4949
# RUN: FileCheck %s --check-prefix=ERR-ALLOC --implicit-check-not=error:
@@ -62,6 +62,16 @@
6262
# ERR3: unknown --compress-sections value: zlib-gabi
6363
# ERR3-NEXT: --compress-sections: parse error, not 'section-glob=[none|zlib|zstd]'
6464

65+
# RUN: not ld.lld a.o --compress-sections='a=zlib:' --compress-sections='a=zlib:-1' 2>&1 | \
66+
# RUN: FileCheck %s --check-prefix=ERR4 --implicit-check-not=error:
67+
# ERR4: error: --compress-sections: expected a non-negative integer compression level, but got ''
68+
# ERR4: error: --compress-sections: expected a non-negative integer compression level, but got '-1'
69+
70+
## Invalid compression level for zlib.
71+
# RUN: not ld.lld a.o --compress-sections='.debug*=zlib:99' 2>&1 | \
72+
# RUN: FileCheck %s --check-prefix=ERR6 --implicit-check-not=error:
73+
# ERR6: error: --compress-sections: deflateInit2 returned -2
74+
6575
.globl _start
6676
_start:
6777
ret

lld/test/ELF/compressed-debug-level.test

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,20 @@
22

33
# RUN: yaml2obj %s -o %t.o
44

5+
## LLD uses zlib compression of level 1 by default. Unlike previous versions,
6+
## -O does not change the level.
57
# RUN: ld.lld %t.o -o %t.default --compress-debug-sections=zlib
68
# RUN: llvm-readelf --sections %t.default | FileCheck -check-prefixes=HEADER,LEVEL1 %s
79

810
# RUN: ld.lld -O0 %t.o -o %t.O0 --compress-debug-sections=zlib
9-
# RUN: llvm-readelf --sections %t.O0 | FileCheck -check-prefixes=HEADER,LEVEL1 %s
1011
# RUN: cmp %t.default %t.O0
1112

12-
# RUN: ld.lld -O1 %t.o -o %t.O1 --compress-debug-sections=zlib
13-
# RUN: llvm-readelf --sections %t.O1 | FileCheck -check-prefixes=HEADER,LEVEL1 %s
14-
# RUN: cmp %t.default %t.O1
15-
1613
# RUN: ld.lld -O2 %t.o -o %t.O2 --compress-debug-sections=zlib
17-
# RUN: llvm-readelf --sections %t.O2 | FileCheck -check-prefixes=HEADER,LEVEL6 %s
14+
# RUN: cmp %t.default %t.O2
1815

19-
## LLD uses zlib compression of level 1 when -O0, -O1 and level 6 when -O2.
20-
## Here we check how -O flag affects the size of compressed sections produced.
16+
## --compression-level specifies the level.
17+
# RUN: ld.lld %t.o -o %t.6 --compress-sections=.debug_info=zlib:6
18+
# RUN: llvm-readelf --sections %t.6 | FileCheck -check-prefixes=HEADER,LEVEL6 %s
2119

2220
# HEADER: [Nr] Name Type Address Off Size
2321
# LEVEL1: [ 1] .debug_info PROGBITS 00000000 000094 00001{{[bc]}}

0 commit comments

Comments
 (0)