Skip to content

Commit f1ca2a0

Browse files
authored
[ELF] Add --compress-section to compress matched non-SHF_ALLOC sections
--compress-sections <section-glib>=[none|zlib|zstd] is similar to --compress-debug-sections but applies to broader sections without the SHF_ALLOC flag. lld will report an error if a SHF_ALLOC section is matched. An interesting use case is to compress `.strtab`/`.symtab`, which consume a significant portion of the file size (15.1% for a release build of Clang). An older revision is available at https://reviews.llvm.org/D154641 . This patch focuses on non-allocated sections for safety. Moving `maybeCompress` as D154641 does not handle STT_SECTION symbols for `-r --compress-debug-sections=zlib` (see `relocatable-section-symbol.s` from #66804). Since different output sections may use different compression algorithms, we need CompressedData::type to generalize config->compressDebugSections. GNU ld feature request: https://sourceware.org/bugzilla/show_bug.cgi?id=27452 Link: https://discourse.llvm.org/t/rfc-compress-arbitrary-sections-with-ld-lld-compress-sections/71674 Pull Request: #84855
1 parent c1af6ab commit f1ca2a0

File tree

11 files changed

+259
-19
lines changed

11 files changed

+259
-19
lines changed

lld/ELF/Config.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,9 @@ struct Config {
222222
CGProfileSortKind callGraphProfileSort;
223223
bool checkSections;
224224
bool checkDynamicRelocs;
225-
llvm::DebugCompressionType compressDebugSections;
225+
std::optional<llvm::DebugCompressionType> compressDebugSections;
226+
llvm::SmallVector<std::pair<llvm::GlobPattern, llvm::DebugCompressionType>, 0>
227+
compressSections;
226228
bool cref;
227229
llvm::SmallVector<std::pair<llvm::GlobPattern, uint64_t>, 0>
228230
deadRelocInNonAlloc;

lld/ELF/Driver.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,9 +1224,10 @@ static void readConfigs(opt::InputArgList &args) {
12241224
config->checkSections =
12251225
args.hasFlag(OPT_check_sections, OPT_no_check_sections, true);
12261226
config->chroot = args.getLastArgValue(OPT_chroot);
1227-
config->compressDebugSections = getCompressionType(
1228-
args.getLastArgValue(OPT_compress_debug_sections, "none"),
1229-
"--compress-debug-sections");
1227+
if (auto *arg = args.getLastArg(OPT_compress_debug_sections)) {
1228+
config->compressDebugSections =
1229+
getCompressionType(arg->getValue(), "--compress-debug-sections");
1230+
}
12301231
config->cref = args.hasArg(OPT_cref);
12311232
config->optimizeBBJumps =
12321233
args.hasFlag(OPT_optimize_bb_jumps, OPT_no_optimize_bb_jumps, false);
@@ -1516,6 +1517,23 @@ static void readConfigs(opt::InputArgList &args) {
15161517
}
15171518
}
15181519

1520+
for (opt::Arg *arg : args.filtered(OPT_compress_sections)) {
1521+
SmallVector<StringRef, 0> fields;
1522+
StringRef(arg->getValue()).split(fields, '=');
1523+
if (fields.size() != 2 || fields[1].empty()) {
1524+
error(arg->getSpelling() +
1525+
": parse error, not 'section-glob=[none|zlib|zstd]'");
1526+
continue;
1527+
}
1528+
auto type = getCompressionType(fields[1], arg->getSpelling());
1529+
if (Expected<GlobPattern> pat = GlobPattern::create(fields[0])) {
1530+
config->compressSections.emplace_back(std::move(*pat), type);
1531+
} else {
1532+
error(arg->getSpelling() + ": " + toString(pat.takeError()));
1533+
continue;
1534+
}
1535+
}
1536+
15191537
for (opt::Arg *arg : args.filtered(OPT_z)) {
15201538
std::pair<StringRef, StringRef> option =
15211539
StringRef(arg->getValue()).split('=');

lld/ELF/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ defm compress_debug_sections:
6767
Eq<"compress-debug-sections", "Compress DWARF debug sections">,
6868
MetaVarName<"[none,zlib,zstd]">;
6969

70+
defm compress_sections: EEq<"compress-sections",
71+
"Compress non-SHF_ALLOC output sections matching <section-glob>">,
72+
MetaVarName<"<section-glob>=[none|zlib|zstd]">;
73+
7074
defm defsym: Eq<"defsym", "Define a symbol alias">, MetaVarName<"<symbol>=<value>">;
7175

7276
defm optimize_bb_jumps: BB<"optimize-bb-jumps",

lld/ELF/OutputSections.cpp

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -326,32 +326,52 @@ static SmallVector<uint8_t, 0> deflateShard(ArrayRef<uint8_t> in, int level,
326326
}
327327
#endif
328328

329-
// Compress section contents if this section contains debug info.
329+
// Compress certain non-SHF_ALLOC sections:
330+
//
331+
// * (if --compress-debug-sections is specified) non-empty .debug_* sections
332+
// * (if --compress-sections is specified) matched sections
330333
template <class ELFT> void OutputSection::maybeCompress() {
331334
using Elf_Chdr = typename ELFT::Chdr;
332335
(void)sizeof(Elf_Chdr);
333336

334-
// Compress only DWARF debug sections.
335-
if (config->compressDebugSections == DebugCompressionType::None ||
336-
(flags & SHF_ALLOC) || !name.starts_with(".debug_") || size == 0)
337+
DebugCompressionType ctype = DebugCompressionType::None;
338+
for (auto &[glob, t] : config->compressSections)
339+
if (glob.match(name))
340+
ctype = t;
341+
if (!(flags & SHF_ALLOC) && config->compressDebugSections &&
342+
name.starts_with(".debug_") && size)
343+
ctype = *config->compressDebugSections;
344+
if (ctype == DebugCompressionType::None)
345+
return;
346+
if (flags & SHF_ALLOC) {
347+
errorOrWarn("--compress-sections: section '" + name +
348+
"' with the SHF_ALLOC flag cannot be compressed");
337349
return;
350+
}
338351

339-
llvm::TimeTraceScope timeScope("Compress debug sections");
352+
llvm::TimeTraceScope timeScope("Compress sections");
340353
compressed.uncompressedSize = size;
341354
auto buf = std::make_unique<uint8_t[]>(size);
342355
// Write uncompressed data to a temporary zero-initialized buffer.
343356
{
344357
parallel::TaskGroup tg;
345358
writeTo<ELFT>(buf.get(), tg);
346359
}
360+
// The generic ABI specifies "The sh_size and sh_addralign fields of the
361+
// section header for a compressed section reflect the requirements of the
362+
// compressed section." However, 1-byte alignment has been wildly accepted
363+
// and utilized for a long time. Removing alignment padding is particularly
364+
// useful when there are many compressed output sections.
365+
addralign = 1;
347366

348367
#if LLVM_ENABLE_ZSTD
349368
// Use ZSTD's streaming compression API which permits parallel workers working
350369
// on the stream. See http://facebook.github.io/zstd/zstd_manual.html
351370
// "Streaming compression - HowTo".
352-
if (config->compressDebugSections == DebugCompressionType::Zstd) {
371+
if (ctype == DebugCompressionType::Zstd) {
353372
// Allocate a buffer of half of the input size, and grow it by 1.5x if
354373
// insufficient.
374+
compressed.type = ELFCOMPRESS_ZSTD;
355375
compressed.shards = std::make_unique<SmallVector<uint8_t, 0>[]>(1);
356376
SmallVector<uint8_t, 0> &out = compressed.shards[0];
357377
out.resize_for_overwrite(std::max<size_t>(size / 2, 32));
@@ -424,6 +444,7 @@ template <class ELFT> void OutputSection::maybeCompress() {
424444
}
425445
size += 4; // checksum
426446

447+
compressed.type = ELFCOMPRESS_ZLIB;
427448
compressed.shards = std::move(shardsOut);
428449
compressed.numShards = numShards;
429450
compressed.checksum = checksum;
@@ -450,20 +471,18 @@ void OutputSection::writeTo(uint8_t *buf, parallel::TaskGroup &tg) {
450471
if (type == SHT_NOBITS)
451472
return;
452473

453-
// If --compress-debug-section is specified and if this is a debug section,
454-
// we've already compressed section contents. If that's the case,
455-
// just write it down.
474+
// If the section is compressed due to
475+
// --compress-debug-section/--compress-sections, the content is already known.
456476
if (compressed.shards) {
457477
auto *chdr = reinterpret_cast<typename ELFT::Chdr *>(buf);
478+
chdr->ch_type = compressed.type;
458479
chdr->ch_size = compressed.uncompressedSize;
459480
chdr->ch_addralign = addralign;
460481
buf += sizeof(*chdr);
461-
if (config->compressDebugSections == DebugCompressionType::Zstd) {
462-
chdr->ch_type = ELFCOMPRESS_ZSTD;
482+
if (compressed.type == ELFCOMPRESS_ZSTD) {
463483
memcpy(buf, compressed.shards[0].data(), compressed.shards[0].size());
464484
return;
465485
}
466-
chdr->ch_type = ELFCOMPRESS_ZLIB;
467486

468487
// Compute shard offsets.
469488
auto offsets = std::make_unique<size_t[]>(compressed.numShards);

lld/ELF/OutputSections.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct PhdrEntry;
2323

2424
struct CompressedData {
2525
std::unique_ptr<SmallVector<uint8_t, 0>[]> shards;
26+
uint32_t type = 0;
2627
uint32_t numShards = 0;
2728
uint32_t checksum = 0;
2829
uint64_t uncompressedSize;
@@ -116,12 +117,13 @@ class OutputSection final : public SectionBase {
116117
void sortInitFini();
117118
void sortCtorsDtors();
118119

120+
// Used for implementation of --compress-debug-sections and
121+
// --compress-sections.
122+
CompressedData compressed;
123+
119124
private:
120125
SmallVector<InputSection *, 0> storage;
121126

122-
// Used for implementation of --compress-debug-sections option.
123-
CompressedData compressed;
124-
125127
std::array<uint8_t, 4> getFiller();
126128
};
127129

lld/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ 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
30+
matched output sections without the ``SHF_ALLOC`` flag.
31+
(`#84855 <https://github.com/llvm/llvm-project/pull/84855>`_)
32+
2933
Breaking changes
3034
----------------
3135

lld/docs/ld.lld.1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ to set the compression level to 6.
164164
The compression level is 5.
165165
.El
166166
.Pp
167+
.It Fl -compress-sections Ns = Ns Ar section-glob=[none|zlib|zstd]
168+
Compress output sections that match the glob and do not have the SHF_ALLOC flag.
169+
This is like a generalized
170+
.Cm --compress-debug-sections.
167171
.It Fl -cref
168172
Output cross reference table. If
169173
.Fl Map

lld/test/ELF/compress-sections-err.s

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
# RUN: ld.lld %t.o --compress-debug-sections=zlib --compress-debug-sections=none -o /dev/null 2>&1 | count 0
66
# RUN: not ld.lld %t.o --compress-debug-sections=zlib -o /dev/null 2>&1 | \
77
# RUN: FileCheck %s --implicit-check-not=error:
8+
# RUN: not ld.lld %t.o --compress-sections=foo=zlib -o /dev/null 2>&1 | \
9+
# RUN: FileCheck %s --check-prefix=CHECK2 --implicit-check-not=error:
810

911
# CHECK: error: --compress-debug-sections: LLVM was not built with LLVM_ENABLE_ZLIB or did not find zlib at build time
12+
# CHECK2: error: --compress-sections: LLVM was not built with LLVM_ENABLE_ZLIB or did not find zlib at build time
1013

1114
.globl _start
1215
_start:
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# REQUIRES: x86, zlib
2+
3+
# RUN: rm -rf %t && mkdir %t && cd %t
4+
# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o a.o
5+
# RUN: ld.lld -pie a.o --compress-sections .strtab=zlib --compress-sections .symtab=zlib -o out
6+
# RUN: llvm-readelf -Ss -x .strtab out 2>&1 | FileCheck %s
7+
8+
# CHECK: nonalloc0 PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 00 0 0 1
9+
# CHECK: .symtab SYMTAB 0000000000000000 [[#%x,]] [[#%x,]] 18 C 12 3 1
10+
# CHECK-NEXT: .shstrtab STRTAB 0000000000000000 [[#%x,]] [[#%x,]] 00 0 0 1
11+
# CHECK-NEXT: .strtab STRTAB 0000000000000000 [[#%x,]] [[#%x,]] 00 C 0 0 1
12+
13+
## TODO Add compressed SHT_STRTAB/SHT_SYMTAB support to llvm-readelf
14+
# CHECK: warning: {{.*}}: unable to get the string table for the SHT_SYMTAB section: SHT_STRTAB string table section
15+
16+
# CHECK: Hex dump of section '.strtab':
17+
# CHECK-NEXT: 01000000 00000000 1a000000 00000000
18+
# CHECK-NEXT: 01000000 00000000 {{.*}}
19+
20+
# RUN: not ld.lld -shared a.o --compress-sections .dynstr=zlib 2>&1 | FileCheck %s --check-prefix=ERR-ALLOC
21+
# ERR-ALLOC: error: --compress-sections: section '.dynstr' with the SHF_ALLOC flag cannot be compressed
22+
23+
.globl _start, g0, g1
24+
_start:
25+
l0:
26+
g0:
27+
g1:
28+
29+
.section nonalloc0,""
30+
.quad .text+1
31+
.quad .text+2

lld/test/ELF/compress-sections.s

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# REQUIRES: x86, zlib, zstd
2+
3+
# RUN: rm -rf %t && mkdir %t && cd %t
4+
# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o a.o
5+
# RUN: ld.lld -pie a.o -o out --compress-sections '*0=zlib' --compress-sections '*0=none' --compress-sections 'nomatch=none'
6+
# RUN: llvm-readelf -SrsX out | FileCheck %s --check-prefix=CHECK1
7+
8+
# CHECK1: Name Type Address Off Size ES Flg Lk Inf Al
9+
# CHECK1: foo0 PROGBITS [[#%x,FOO0:]] [[#%x,]] [[#%x,]] 00 A 0 0 8
10+
# CHECK1-NEXT: foo1 PROGBITS [[#%x,FOO1:]] [[#%x,]] [[#%x,]] 00 A 0 0 8
11+
# CHECK1-NEXT: .text PROGBITS [[#%x,TEXT:]] [[#%x,]] [[#%x,]] 00 AX 0 0 4
12+
# CHECK1: nonalloc0 PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 00 0 0 8
13+
# CHECK1-NEXT: nonalloc1 PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 00 0 0 8
14+
# CHECK1-NEXT: .debug_str PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 01 MS 0 0 1
15+
16+
# CHECK1: 0000000000000010 0 NOTYPE LOCAL DEFAULT [[#]] (nonalloc0) sym0
17+
# CHECK1: 0000000000000008 0 NOTYPE LOCAL DEFAULT [[#]] (nonalloc1) sym1
18+
19+
# RUN: ld.lld -pie a.o --compress-sections '*c0=zlib' --compress-sections .debug_str=zstd -o out2
20+
# RUN: llvm-readelf -SrsX -x nonalloc0 -x .debug_str out2 | FileCheck %s --check-prefix=CHECK2
21+
22+
# CHECK2: Name Type Address Off Size ES Flg Lk Inf Al
23+
# CHECK2: foo0 PROGBITS [[#%x,FOO0:]] [[#%x,]] [[#%x,]] 00 A 0 0 8
24+
# CHECK2-NEXT: foo1 PROGBITS [[#%x,FOO1:]] [[#%x,]] [[#%x,]] 00 A 0 0 8
25+
# CHECK2-NEXT: .text PROGBITS [[#%x,TEXT:]] [[#%x,]] [[#%x,]] 00 AX 0 0 4
26+
# CHECK2: nonalloc0 PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 00 C 0 0 1
27+
# CHECK2-NEXT: nonalloc1 PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 00 0 0 8
28+
# CHECK2-NEXT: .debug_str PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 01 MSC 0 0 1
29+
30+
# CHECK2: 0000000000000010 0 NOTYPE LOCAL DEFAULT [[#]] (nonalloc0) sym0
31+
# CHECK2: 0000000000000008 0 NOTYPE LOCAL DEFAULT [[#]] (nonalloc1) sym1
32+
33+
# CHECK2: Hex dump of section 'nonalloc0':
34+
## zlib with ch_size=0x10
35+
# CHECK2-NEXT: 01000000 00000000 10000000 00000000
36+
# CHECK2-NEXT: 01000000 00000000 {{.*}}
37+
# CHECK2: Hex dump of section '.debug_str':
38+
## zstd with ch_size=0x38
39+
# CHECK2-NEXT: 02000000 00000000 38000000 00000000
40+
# CHECK2-NEXT: 01000000 00000000 {{.*}}
41+
42+
## --compress-debug-sections=none takes precedence.
43+
# RUN: ld.lld a.o --compress-debug-sections=none --compress-sections .debug_str=zstd -o out3
44+
# RUN: llvm-readelf -S out3 | FileCheck %s --check-prefix=CHECK3
45+
46+
# CHECK3: .debug_str PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 01 MS 0 0 1
47+
48+
# RUN: not ld.lld a.o --compress-sections '*0=zlib' 2>&1 | \
49+
# RUN: FileCheck %s --check-prefix=ERR-ALLOC --implicit-check-not=error:
50+
# ERR-ALLOC: error: --compress-sections: section 'foo0' with the SHF_ALLOC flag cannot be compressed
51+
52+
# RUN: not ld.lld --compress-sections=foo a.o 2>&1 | \
53+
# RUN: FileCheck %s --check-prefix=ERR1 --implicit-check-not=error:
54+
# ERR1: error: --compress-sections: parse error, not 'section-glob=[none|zlib|zstd]'
55+
56+
# RUN: not ld.lld --compress-sections 'a[=zlib' a.o 2>&1 | \
57+
# RUN: FileCheck %s --check-prefix=ERR2 --implicit-check-not=error:
58+
# ERR2: error: --compress-sections: invalid glob pattern, unmatched '['
59+
60+
# RUN: not ld.lld a.o --compress-sections='.debug*=zlib-gabi' --compress-sections='.debug*=' 2>&1 | \
61+
# RUN: FileCheck -check-prefix=ERR3 %s
62+
# ERR3: unknown --compress-sections value: zlib-gabi
63+
# ERR3-NEXT: --compress-sections: parse error, not 'section-glob=[none|zlib|zstd]'
64+
65+
.globl _start
66+
_start:
67+
ret
68+
69+
.section foo0,"a"
70+
.balign 8
71+
.quad .text-.
72+
.quad .text-.
73+
.section foo1,"a"
74+
.balign 8
75+
.quad .text-.
76+
.quad .text-.
77+
.section nonalloc0,""
78+
.balign 8
79+
.quad .text+1
80+
.quad .text+2
81+
sym0:
82+
.section nonalloc1,""
83+
.balign 8
84+
.quad 42
85+
sym1:
86+
87+
.section .debug_str,"MS",@progbits,1
88+
.Linfo_string0:
89+
.asciz "AAAAAAAAAAAAAAAAAAAAAAAAAAA"
90+
.Linfo_string1:
91+
.asciz "BBBBBBBBBBBBBBBBBBBBBBBBBBB"
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# REQUIRES: x86, zlib
2+
3+
# RUN: rm -rf %t && split-file %s %t && cd %t
4+
# RUN: llvm-mc -filetype=obj -triple=x86_64 a.s -o a.o
5+
# RUN: ld.lld -T a.lds a.o --compress-sections nonalloc=zlib --compress-sections str=zlib -o out
6+
# RUN: llvm-readelf -SsXz -p str out | FileCheck %s
7+
8+
# CHECK: Name Type Address Off Size ES Flg Lk Inf Al
9+
# CHECK: nonalloc PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 00 C 0 0 1
10+
# CHECK-NEXT: str PROGBITS 0000000000000000 [[#%x,]] [[#%x,]] 01 MSC 0 0 1
11+
12+
# CHECK: 0000000000000000 0 NOTYPE GLOBAL DEFAULT [[#]] (nonalloc) nonalloc_start
13+
# CHECK: 0000000000000023 0 NOTYPE GLOBAL DEFAULT [[#]] (nonalloc) nonalloc_end
14+
# CHECK: String dump of section 'str':
15+
# CHECK-NEXT: [ 0] AAA
16+
# CHECK-NEXT: [ 4] BBB
17+
18+
## TODO The uncompressed size of 'nonalloc' is dependent on linker script
19+
## commands, which is not handled. We should report an error.
20+
# RUN: ld.lld -T b.lds a.o --compress-sections nonalloc=zlib
21+
22+
#--- a.s
23+
.globl _start
24+
_start:
25+
ret
26+
27+
.section nonalloc0,""
28+
.balign 8
29+
.quad .text
30+
.quad .text
31+
.section nonalloc1,""
32+
.balign 8
33+
.quad 42
34+
35+
.section str,"MS",@progbits,1
36+
.asciz "AAA"
37+
.asciz "BBB"
38+
39+
#--- a.lds
40+
SECTIONS {
41+
.text : { *(.text) }
42+
c = SIZEOF(.text);
43+
b = c+1;
44+
a = b+1;
45+
nonalloc : {
46+
nonalloc_start = .;
47+
## In general, using data commands is error-prone. This case is correct, though.
48+
*(nonalloc*) QUAD(SIZEOF(.text))
49+
. += a;
50+
nonalloc_end = .;
51+
}
52+
str : { *(str) }
53+
}
54+
55+
#--- b.lds
56+
SECTIONS {
57+
nonalloc : { *(nonalloc*) . += a; }
58+
.text : { *(.text) }
59+
a = b+1;
60+
b = c+1;
61+
c = SIZEOF(.text);
62+
}

0 commit comments

Comments
 (0)