Make no output section from an empty section that defines no symbol

An assembler's empty .section, or an emptied coverage section, makes
no section in ld-prime's output when no symbol is defined in it; ours
carried such sections through. A symbol keeps the section, even a
local one: the arm64 assembler starts every section with an ltmp
label, so on arm64 an empty .section stays, in ld-prime's output as
in ours, while on x86-64 it goes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Chris AllenandClaude Opus 5.5 authored and Rui Ueyama committed 2026-09-30 13:09:25 +09:00
1 parent bc4ced43b8
commit 1e65e0cd97
2 files changed
+47

No files matched your search

+33
View File
@@ -3951,6 +3951,39 @@ pub fn create_output_sections<E: Target>(ctx: &mut Context<E>) {
ctx.isecs[i].set_output_section(ChunkId::Output(osec_id));
}
// An input section with no bytes and no symbol in it makes no
// output section (an assembler's empty .section, an emptied
// coverage section): ld-prime drops it, and its subsections with
// it. A symbol keeps it, even a local one such as the ltmp label
// the arm64 assembler puts at the start of every section.
if !relocatable {
let is_bare = |&m: &u32| {
let isec = &ctx.isecs[m as usize];
isec.size == 0
&& !ctx.objs[isec.file as usize].nlists.iter().any(|nlist| {
!nlist.is_stab()
&& nlist.n_type() == N_SECT
&& nlist.n_sect as u32 == isec.shndx + 1
})
};
let dropped: Vec<OutputSectionId> = (0..ctx.output_sections.len())
.filter(|&i| {
let osec = &ctx.output_sections[i];
!osec.members.is_empty()
&& osec.members.iter().all(is_bare)
&& !(osec.hdr.segname == "__TEXT" && osec.hdr.sectname == "__text")
})
.map(|i| OutputSectionId::new(i as u32))
.collect();
for id in dropped {
for m in std::mem::take(&mut ctx.output_sections[id.index()].members) {
ctx.isecs[m as usize].set_alive(false);
}
ctx.chunks.retain(|&c| c != ChunkId::Output(id));
by_out.retain(|_, &mut v| v != id);
}
}
// A final image always has a __TEXT,__text section, empty if no
// code reached it (a dylib of only data; ld-prime writes one of
// size 0, byte-aligned).
+14
View File
@@ -51,6 +51,8 @@ $t/exe | grep '^5 ok$'
cat <<EOF | $CC -o $t/b.o -c -x assembler -
.section __DATA,__zero_one
.section __DATA,__zero_two
.section __DATA,__zero_labeled
zero_label:
EOF
cat <<EOF | $CC -o $t/c.o -c -xc -
_Thread_local int tls_var = 3;
@@ -58,3 +60,15 @@ int get_tls(void) { return tls_var; }
EOF
$CC --ld-path=$mold -o $t/exe2 $t/main.o $t/a.o $t/b.o $t/c.o
$t/exe2 | grep '^5 ok$'
# An empty section with no symbol in it makes no output section, as
# with ld-prime. The arm64 assembler gives every section an ltmp
# symbol, so there all three stay.
otool -l $t/exe2 > $t/lc2
grep -q '__zero_labeled' $t/lc2
if [ $ARCH = x86_64 ]; then
not grep -q '__zero_one' $t/lc2
not grep -q '__zero_two' $t/lc2
else
grep -q '__zero_one' $t/lc2
grep -q '__zero_two' $t/lc2
fi