Skip to content

Commit 46707b0

Browse files
authored
[AArch64,ELF] Allow implicit $d/$x at section beginning
The start state of a new section is `EMS_None`, often leading to a $d/$x at offset 0. Introduce a MCTargetOption/cl::opt "implicit-mapsyms" to allow an alternative behavior (ARM-software/abi-aa#274): * Set the start state to `EMS_Data` or `EMS_A64`. * For text sections, add an ending $x only if the final data is not instructions. * For non-text sections, add an ending $d only if the final data is not data commands. ``` .section .text.1,"ax" nop // emit $d .long 42 // emit $x .section .text.2,"ax" nop ``` This new behavior decreases the .symtab size significantly: ``` % bloaty a64-2/bin/clang -- a64-0/bin/clang FILE SIZE VM SIZE -------------- -------------- -5.4% -1.13Mi [ = ] 0 .strtab -50.9% -4.09Mi [ = ] 0 .symtab -4.0% -5.22Mi [ = ] 0 TOTAL ``` --- This scheme works as long as the user can rule out some error scenarios: * .text.1 assembled using the traditional behavior is combined with .text.2 using the new behavior * A linker script combining non-text sections and text sections. The lack of mapping symbols in the non-text sections could make them treated as code, unless the linker inserts extra mapping symbols. The above mix-and-match scenarios aren't an issue at all for a significant portion of users. A text section may start with data commands in rare cases (e.g. -fsanitize=function) that many users don't care about. When combing `(.text.0; .word 0)` and `(.text.1; .word 0)`, the ending $x of .text.0 and the initial $d of .text.1 may have the same address. If both sections reside in the same file, ensure the ending symbol comes before the initial $d of .text.1, so that a dumb linker respecting the symbol order will place the ending $x before the initial $d. Disassemblers using stable sort will see both symbols at the same address, and the second will win. When section ordering mechanisms (e.g. --symbol-ordering-file, --call-graph-profile-sort, `.text : { second.o(.text) first.o(.text) }`) are involved, the initial data in a text section following a text section with trailing data could be misidentified as code, but the issue is local and the risk could be acceptable. Pull Request: #99718
1 parent 0bd90ec commit 46707b0

File tree

7 files changed

+179
-17
lines changed

7 files changed

+179
-17
lines changed
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# REQUIRES: aarch64
2+
# RUN: llvm-mc -filetype=obj -triple=aarch64 -implicit-mapsyms %s -o %t.o
3+
# RUN: ld.lld %t.o -z keep-text-section-prefix -o %t
4+
# RUN: llvm-objdump -d --no-print-imm-hex --show-all-symbols %t | FileCheck %s
5+
6+
# CHECK: <_start>:
7+
# CHECK-NEXT: nop
8+
# CHECK-EMPTY:
9+
# CHECK-NEXT: <$d>:
10+
# CHECK-NEXT: .word 0x0000002a
11+
# CHECK-EMPTY:
12+
# CHECK-NEXT: <$x>:
13+
# CHECK-NEXT: nop
14+
# CHECK-EMPTY:
15+
# CHECK-NEXT: Disassembly of section .text.hot:
16+
# CHECK-EMPTY:
17+
# CHECK-NEXT: <.text.hot>:
18+
# CHECK-NEXT: nop
19+
# CHECK-EMPTY:
20+
# CHECK-NEXT: <$d>:
21+
# CHECK-NEXT: .word 0x0000002a
22+
# CHECK-EMPTY:
23+
# CHECK-NEXT: <$d>:
24+
# CHECK-NEXT: <$x>:
25+
# CHECK-NEXT: udf #42
26+
# CHECK-EMPTY:
27+
# CHECK-NEXT: <$x>:
28+
# CHECK-NEXT: nop
29+
30+
## Trailing data followed by a section starting with an instruction.
31+
.section .text.1,"ax"
32+
.globl _start
33+
_start:
34+
nop
35+
.long 42
36+
.section .text.2,"ax"
37+
nop
38+
39+
## Trailing data followed by a section starting with a data directive.
40+
.section .text.hot.1,"ax"
41+
nop
42+
.long 42
43+
.section .text.hot.2,"ax"
44+
.long 42
45+
nop

llvm/include/llvm/MC/MCAssembler.h

+1
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ class MCAssembler {
218218
const_iterator begin() const { return Sections.begin(); }
219219
const_iterator end() const { return Sections.end(); }
220220

221+
SmallVectorImpl<const MCSymbol *> &getSymbols() { return Symbols; }
221222
iterator_range<pointee_iterator<
222223
typename SmallVector<const MCSymbol *, 0>::const_iterator>>
223224
symbols() const {

llvm/include/llvm/MC/MCTargetOptions.h

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ class MCTargetOptions {
6464
// Use CREL relocation format for ELF.
6565
bool Crel = false;
6666

67+
bool ImplicitMapSyms = false;
68+
6769
// If true, prefer R_X86_64_[REX_]GOTPCRELX to R_X86_64_GOTPCREL on x86-64
6870
// ELF.
6971
bool X86RelaxRelocations = true;

llvm/include/llvm/MC/MCTargetOptionsCommandFlags.h

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ bool getSaveTempLabels();
5353

5454
bool getCrel();
5555

56+
bool getImplicitMapSyms();
57+
5658
bool getX86RelaxRelocations();
5759

5860
bool getX86Sse2Avx();

llvm/lib/MC/MCTargetOptionsCommandFlags.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ MCOPT(bool, NoDeprecatedWarn)
4848
MCOPT(bool, NoTypeCheck)
4949
MCOPT(bool, SaveTempLabels)
5050
MCOPT(bool, Crel)
51+
MCOPT(bool, ImplicitMapSyms)
5152
MCOPT(bool, X86RelaxRelocations)
5253
MCOPT(bool, X86Sse2Avx)
5354
MCOPT(std::string, ABIName)
@@ -134,6 +135,14 @@ llvm::mc::RegisterMCTargetOptionsFlags::RegisterMCTargetOptionsFlags() {
134135
cl::desc("Use CREL relocation format for ELF"));
135136
MCBINDOPT(Crel);
136137

138+
static cl::opt<bool> ImplicitMapSyms(
139+
"implicit-mapsyms",
140+
cl::desc("Allow mapping symbol at section beginning to be implicit, "
141+
"lowering number of mapping symbols at the expense of some "
142+
"portability. Recommended for projects that can build all their "
143+
"object files using this option"));
144+
MCBINDOPT(ImplicitMapSyms);
145+
137146
static cl::opt<bool> X86RelaxRelocations(
138147
"x86-relax-relocations",
139148
cl::desc(
@@ -174,6 +183,7 @@ MCTargetOptions llvm::mc::InitMCTargetOptionsFromFlags() {
174183
Options.MCNoTypeCheck = getNoTypeCheck();
175184
Options.MCSaveTempLabels = getSaveTempLabels();
176185
Options.Crel = getCrel();
186+
Options.ImplicitMapSyms = getImplicitMapSyms();
177187
Options.X86RelaxRelocations = getX86RelaxRelocations();
178188
Options.X86Sse2Avx = getX86Sse2Avx();
179189
Options.EmitDwarfUnwind = getEmitDwarfUnwind();

llvm/lib/Target/AArch64/MCTargetDesc/AArch64ELFStreamer.cpp

+72-7
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@
2424
#include "llvm/MC/MCAssembler.h"
2525
#include "llvm/MC/MCCodeEmitter.h"
2626
#include "llvm/MC/MCContext.h"
27+
#include "llvm/MC/MCELFObjectWriter.h"
2728
#include "llvm/MC/MCELFStreamer.h"
2829
#include "llvm/MC/MCExpr.h"
2930
#include "llvm/MC/MCInst.h"
30-
#include "llvm/MC/MCObjectWriter.h"
3131
#include "llvm/MC/MCSectionELF.h"
3232
#include "llvm/MC/MCStreamer.h"
3333
#include "llvm/MC/MCSubtargetInfo.h"
3434
#include "llvm/MC/MCSymbolELF.h"
35+
#include "llvm/MC/MCTargetOptions.h"
3536
#include "llvm/MC/MCWinCOFFStreamer.h"
3637
#include "llvm/Support/Casting.h"
3738
#include "llvm/Support/FormattedStream.h"
@@ -176,19 +177,29 @@ void AArch64TargetAsmStreamer::emitInst(uint32_t Inst) {
176177
/// by MachO. Beware!
177178
class AArch64ELFStreamer : public MCELFStreamer {
178179
public:
180+
friend AArch64TargetELFStreamer;
179181
AArch64ELFStreamer(MCContext &Context, std::unique_ptr<MCAsmBackend> TAB,
180182
std::unique_ptr<MCObjectWriter> OW,
181183
std::unique_ptr<MCCodeEmitter> Emitter)
182184
: MCELFStreamer(Context, std::move(TAB), std::move(OW),
183185
std::move(Emitter)),
184-
LastEMS(EMS_None) {}
186+
LastEMS(EMS_None) {
187+
auto *TO = getContext().getTargetOptions();
188+
ImplicitMapSyms = TO && TO->ImplicitMapSyms;
189+
}
185190

186191
void changeSection(MCSection *Section, uint32_t Subsection = 0) override {
187-
// We have to keep track of the mapping symbol state of any sections we
188-
// use. Each one should start off as EMS_None, which is provided as the
189-
// default constructor by DenseMap::lookup.
192+
// Save the mapping symbol state for potential reuse when revisiting the
193+
// section. When ImplicitMapSyms is true, the initial state is
194+
// EMS_A64 for text sections and EMS_Data for the others.
190195
LastMappingSymbols[getCurrentSection().first] = LastEMS;
191-
LastEMS = LastMappingSymbols.lookup(Section);
196+
auto It = LastMappingSymbols.find(Section);
197+
if (It != LastMappingSymbols.end())
198+
LastEMS = It->second;
199+
else if (ImplicitMapSyms)
200+
LastEMS = Section->isText() ? EMS_A64 : EMS_Data;
201+
else
202+
LastEMS = EMS_None;
192203

193204
MCELFStreamer::changeSection(Section, Subsection);
194205
}
@@ -269,13 +280,15 @@ class AArch64ELFStreamer : public MCELFStreamer {
269280
LastEMS = EMS_A64;
270281
}
271282

272-
void emitMappingSymbol(StringRef Name) {
283+
MCSymbol *emitMappingSymbol(StringRef Name) {
273284
auto *Symbol = cast<MCSymbolELF>(getContext().createLocalSymbol(Name));
274285
emitLabel(Symbol);
286+
return Symbol;
275287
}
276288

277289
DenseMap<const MCSection *, ElfMappingSymbol> LastMappingSymbols;
278290
ElfMappingSymbol LastEMS;
291+
bool ImplicitMapSyms;
279292
};
280293
} // end anonymous namespace
281294

@@ -297,6 +310,58 @@ void AArch64TargetELFStreamer::finish() {
297310
AArch64ELFStreamer &S = getStreamer();
298311
MCContext &Ctx = S.getContext();
299312
auto &Asm = S.getAssembler();
313+
314+
// If ImplicitMapSyms is specified, ensure that text sections end with
315+
// the A64 state while non-text sections end with the data state. When
316+
// sections are combined by the linker, the subsequent section will start with
317+
// the right state. The ending mapping symbol is added right after the last
318+
// symbol relative to the section. When a dumb linker combines (.text.0; .word
319+
// 0) and (.text.1; .word 0), the ending $x of .text.0 precedes the $d of
320+
// .text.1, even if they have the same address.
321+
if (S.ImplicitMapSyms) {
322+
auto &Syms = Asm.getSymbols();
323+
const size_t NumSyms = Syms.size();
324+
DenseMap<MCSection *, std::pair<size_t, MCSymbol *>> EndMapSym;
325+
for (MCSection &Sec : Asm) {
326+
S.switchSection(&Sec);
327+
if (S.LastEMS == (Sec.isText() ? AArch64ELFStreamer::EMS_Data
328+
: AArch64ELFStreamer::EMS_A64))
329+
EndMapSym.insert(
330+
{&Sec, {NumSyms, S.emitMappingSymbol(Sec.isText() ? "$x" : "$d")}});
331+
}
332+
if (Syms.size() != NumSyms) {
333+
SmallVector<const MCSymbol *, 0> NewSyms;
334+
DenseMap<MCSection *, size_t> Cnt;
335+
Syms.truncate(NumSyms);
336+
// Find the last symbol index for each candidate section.
337+
for (auto [I, Sym] : llvm::enumerate(Syms)) {
338+
if (!Sym->isInSection())
339+
continue;
340+
auto It = EndMapSym.find(&Sym->getSection());
341+
if (It != EndMapSym.end())
342+
It->second.first = I;
343+
}
344+
SmallVector<size_t, 0> Idx;
345+
for (auto [I, Sym] : llvm::enumerate(Syms)) {
346+
NewSyms.push_back(Sym);
347+
if (!Sym->isInSection())
348+
continue;
349+
auto It = EndMapSym.find(&Sym->getSection());
350+
// If `Sym` is the last symbol relative to the section, add the ending
351+
// mapping symbol after `Sym`.
352+
if (It != EndMapSym.end() && I == It->second.first) {
353+
NewSyms.push_back(It->second.second);
354+
Idx.push_back(I);
355+
}
356+
}
357+
Syms = std::move(NewSyms);
358+
// F.second holds the number of symbols added before the FILE symbol.
359+
// Take into account the inserted mapping symbols.
360+
for (auto &F : S.getWriter().getFileNames())
361+
F.second += llvm::lower_bound(Idx, F.second) - Idx.begin();
362+
}
363+
}
364+
300365
MCSectionELF *MemtagSec = nullptr;
301366
for (const MCSymbol &Symbol : Asm.symbols()) {
302367
const auto &Sym = cast<MCSymbolELF>(Symbol);
+47-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
// RUN: llvm-mc -triple=aarch64 -filetype=obj %s | llvm-objdump -t - | FileCheck %s --match-full-lines
2+
// RUN: llvm-mc -triple=aarch64 -filetype=obj -implicit-mapsyms %s | llvm-objdump -t - | FileCheck %s --check-prefix=CHECK1 --match-full-lines
23

4+
/// The test covers many state transitions. Let's use the first state and the last state to describe a section.
5+
/// .text goes through cd -> dd -> cc -> dd.
6+
/// .data goes through dd -> dc -> cd.
7+
.file "0.s"
38
.section .text1,"ax"
49
add w0, w0, w0
510

@@ -12,29 +17,61 @@ add w0, w0, w0
1217
.popsection
1318

1419
.text
15-
add w1, w1, w1
20+
.word 42
1621

1722
.section .text1,"ax"
1823
add w1, w1, w1
1924

25+
.text
26+
add w1, w1, w1
27+
28+
.section .data,"aw"
29+
.word 42
30+
add w0, w0, w0
31+
2032
.text
2133
.word 42
2234

35+
## .rodata and subsequent symbols should be after the FILE symbol of "1.s".
36+
.file "1.s"
2337
.section .rodata,"a"
2438
.word 42
2539
add w0, w0, w0
2640

41+
.section .data,"aw"
42+
add w0, w0, w0
43+
.word 42
44+
45+
.text
46+
2747
.ident "clang"
2848
.section ".note.GNU-stack","",@progbits
2949

3050
// CHECK: SYMBOL TABLE:
31-
// CHECK-NEXT: 0000000000000000 l .text1 0000000000000000 $x
32-
// CHECK-NEXT: 0000000000000000 l .text 0000000000000000 $x
33-
// CHECK-NEXT: 0000000000000004 l .text 0000000000000000 $d
34-
// CHECK-NEXT: 0000000000000000 l .data 0000000000000000 $d
35-
// CHECK-NEXT: 0000000000000008 l .text 0000000000000000 $x
36-
// CHECK-NEXT: 000000000000000c l .text 0000000000000000 $d
37-
// CHECK-NEXT: 0000000000000000 l .rodata 0000000000000000 $d
38-
// CHECK-NEXT: 0000000000000004 l .rodata 0000000000000000 $x
39-
// CHECK-NEXT: 0000000000000000 l .comment 0000000000000000 $d
51+
// CHECK-NEXT: 0000000000000000 l df *ABS* 0000000000000000 0.s
52+
// CHECK-NEXT: 0000000000000000 l .text1 0000000000000000 $x
53+
// CHECK-NEXT: 0000000000000000 l .text 0000000000000000 $x
54+
// CHECK-NEXT: 0000000000000004 l .text 0000000000000000 $d
55+
// CHECK-NEXT: 0000000000000000 l .data 0000000000000000 $d
56+
// CHECK-NEXT: 000000000000000c l .text 0000000000000000 $x
57+
// CHECK-NEXT: 0000000000000008 l .data 0000000000000000 $x
58+
// CHECK-NEXT: 0000000000000010 l .text 0000000000000000 $d
59+
// CHECK-NEXT: 0000000000000000 l df *ABS* 0000000000000000 1.s
60+
// CHECK-NEXT: 0000000000000000 l .rodata 0000000000000000 $d
61+
// CHECK-NEXT: 0000000000000004 l .rodata 0000000000000000 $x
62+
// CHECK-NEXT: 0000000000000010 l .data 0000000000000000 $d
63+
// CHECK-NEXT: 0000000000000000 l .comment 0000000000000000 $d
4064
// CHECK-NOT: {{.}}
65+
66+
// CHECK1: SYMBOL TABLE:
67+
// CHECK1-NEXT: 0000000000000000 l df *ABS* 0000000000000000 0.s
68+
// CHECK1-NEXT: 0000000000000004 l .text 0000000000000000 $d
69+
// CHECK1-NEXT: 000000000000000c l .text 0000000000000000 $x
70+
// CHECK1-NEXT: 0000000000000008 l .data 0000000000000000 $x
71+
// CHECK1-NEXT: 0000000000000010 l .text 0000000000000000 $d
72+
// CHECK1-NEXT: 0000000000000014 l .text 0000000000000000 $x
73+
// CHECK1-NEXT: 0000000000000000 l df *ABS* 0000000000000000 1.s
74+
// CHECK1-NEXT: 0000000000000004 l .rodata 0000000000000000 $x
75+
// CHECK1-NEXT: 0000000000000008 l .rodata 0000000000000000 $d
76+
// CHECK1-NEXT: 0000000000000010 l .data 0000000000000000 $d
77+
// CHECK1-NOT: {{.}}

0 commit comments

Comments
 (0)