Skip to content

Commit c6acf09

Browse files
[WebAssembly] Add segment RETAIN flag to support private retained data
In WebAssembly, we have `WASM_SYMBOL_RETAIN` symbol flag to mark the referenced content as retained. However, the flag is not enough to express retained data that is not referenced by any symbol. This patch adds a new segment flag WASM_SEG_FLAG_RETAIN to support "private" linkage data that is retained by llvm.used. This kind of data that is not referenced but must be retained is usually used with encapsulation symbols (__start/__stop). Swift runtime uses this technique and depends on the fact "all metadata sections in live objects are retained", which was not guaranteed with `--gc-sections` before this patch.
1 parent 2e3de99 commit c6acf09

File tree

11 files changed

+210
-29
lines changed

11 files changed

+210
-29
lines changed

lld/test/wasm/no-strip-segment.s

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# RUN: split-file %s %t
2+
# RUN: llvm-mc -filetype=obj --triple=wasm32-unknown-unknown -o %t/main.o %t/main.s
3+
# RUN: llvm-mc -filetype=obj --triple=wasm32-unknown-unknown -o %t/liba_x.o %t/liba_x.s
4+
# RUN: llvm-mc -filetype=obj --triple=wasm32-unknown-unknown -o %t/liba_y.o %t/liba_y.s
5+
# RUN: rm -f %t/liba.a
6+
# RUN: llvm-ar rcs %t/liba.a %t/liba_x.o %t/liba_y.o
7+
# RUN: wasm-ld %t/main.o %t/liba.a --gc-sections -o %t/main.wasm --print-gc-sections | FileCheck %s --check-prefix=GC
8+
# RUN: obj2yaml %t/main.wasm | FileCheck %s
9+
10+
# --gc-sections should remove non-retained and unused "weathers" section from live object liba_x.o
11+
# GC: removing unused section {{.*}}/liba.a(liba_x.o):(weathers)
12+
# Should not remove retained "greetings" sections from live objects main.o and liba_x.o
13+
# GC-NOT: removing unused section %t/main.o:(greetings)
14+
# GC-NOT: removing unused section %t/liba_x.o:(greetings)
15+
16+
# Note: All symbols are private so that they don't join the symbol table.
17+
18+
#--- main.s
19+
.functype grab_liba () -> ()
20+
.globl _start
21+
_start:
22+
.functype _start () -> ()
23+
call grab_liba
24+
end_function
25+
26+
.section greetings,"R",@
27+
.asciz "hello"
28+
.section weathers,"R",@
29+
.asciz "cloudy"
30+
31+
#--- liba_x.s
32+
.globl grab_liba
33+
grab_liba:
34+
.functype grab_liba () -> ()
35+
end_function
36+
37+
.section greetings,"R",@
38+
.asciz "world"
39+
.section weathers,"",@
40+
.asciz "rainy"
41+
42+
#--- liba_y.s
43+
.section greetings,"R",@
44+
.asciz "bye"
45+
46+
47+
# "greetings" section
48+
# CHECK: - Type: DATA
49+
# CHECK: Segments:
50+
# CHECK: - SectionOffset: 7
51+
# CHECK: InitFlags: 0
52+
# CHECK: Offset:
53+
# CHECK: Opcode: I32_CONST
54+
# CHECK: Value: 1024
55+
# CHECK: Content: 68656C6C6F00776F726C6400
56+
# "weahters" section.
57+
# CHECK: - SectionOffset: 25
58+
# CHECK: InitFlags: 0
59+
# CHECK: Offset:
60+
# CHECK: Opcode: I32_CONST
61+
# CHECK: Value: 1036
62+
# CHECK: Content: 636C6F75647900

lld/wasm/InputChunks.h

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class InputChunk {
8181
void generateRelocationCode(raw_ostream &os) const;
8282

8383
bool isTLS() const { return flags & llvm::wasm::WASM_SEG_FLAG_TLS; }
84+
bool isRetained() const { return flags & llvm::wasm::WASM_SEG_FLAG_RETAIN; }
8485

8586
ObjFile *file;
8687
OutputSection *outputSec = nullptr;

lld/wasm/MarkLive.cpp

+31-8
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ class MarkLive {
4040

4141
private:
4242
void enqueue(Symbol *sym);
43+
void enqueue(InputChunk *chunk);
4344
void enqueueInitFunctions(const ObjFile *sym);
45+
void enqueueRetainedSegments(const ObjFile *file);
4446
void mark();
4547
bool isCallCtorsLive();
4648

@@ -56,21 +58,30 @@ void MarkLive::enqueue(Symbol *sym) {
5658
LLVM_DEBUG(dbgs() << "markLive: " << sym->getName() << "\n");
5759

5860
InputFile *file = sym->getFile();
59-
bool needInitFunctions = file && !file->isLive() && sym->isDefined();
61+
bool markImplicitDeps = file && !file->isLive() && sym->isDefined();
6062

6163
sym->markLive();
6264

63-
// Mark ctor functions in the object that defines this symbol live.
64-
// The ctor functions are all referenced by the synthetic callCtors
65-
// function. However, this function does not contain relocations so we
66-
// have to manually mark the ctors as live.
67-
if (needInitFunctions)
65+
if (markImplicitDeps) {
66+
// Mark ctor functions in the object that defines this symbol live.
67+
// The ctor functions are all referenced by the synthetic callCtors
68+
// function. However, this function does not contain relocations so we
69+
// have to manually mark the ctors as live.
6870
enqueueInitFunctions(cast<ObjFile>(file));
71+
// Mark retained segments in the object that defines this symbol live.
72+
enqueueRetainedSegments(cast<ObjFile>(file));
73+
}
6974

7075
if (InputChunk *chunk = sym->getChunk())
7176
queue.push_back(chunk);
7277
}
7378

79+
void MarkLive::enqueue(InputChunk *chunk) {
80+
LLVM_DEBUG(dbgs() << "markLive: " << toString(chunk) << "\n");
81+
chunk->live = true;
82+
queue.push_back(chunk);
83+
}
84+
7485
// The ctor functions are all referenced by the synthetic callCtors
7586
// function. However, this function does not contain relocations so we
7687
// have to manually mark the ctors as live.
@@ -83,6 +94,14 @@ void MarkLive::enqueueInitFunctions(const ObjFile *obj) {
8394
}
8495
}
8596

97+
// Mark segments flagged by segment-level no-strip. Segment-level no-strip is
98+
// usually used to retain segments without having symbol table entry.
99+
void MarkLive::enqueueRetainedSegments(const ObjFile *file) {
100+
for (InputChunk *chunk : file->segments)
101+
if (chunk->isRetained())
102+
enqueue(chunk);
103+
}
104+
86105
void MarkLive::run() {
87106
// Add GC root symbols.
88107
if (!config->entry.empty())
@@ -96,10 +115,14 @@ void MarkLive::run() {
96115
if (WasmSym::callDtors)
97116
enqueue(WasmSym::callDtors);
98117

99-
// Enqueue constructors in objects explicitly live from the command-line.
100118
for (const ObjFile *obj : ctx.objectFiles)
101-
if (obj->isLive())
119+
if (obj->isLive()) {
120+
// Enqueue constructors in objects explicitly live from the command-line.
102121
enqueueInitFunctions(obj);
122+
// Enqueue retained segments in objects explicitly live from the
123+
// command-line.
124+
enqueueRetainedSegments(obj);
125+
}
103126

104127
mark();
105128

llvm/include/llvm/BinaryFormat/Wasm.h

+1
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ enum WasmSymbolType : unsigned {
216216
enum WasmSegmentFlag : unsigned {
217217
WASM_SEG_FLAG_STRINGS = 0x1,
218218
WASM_SEG_FLAG_TLS = 0x2,
219+
WASM_SEG_FLAG_RETAIN = 0x4,
219220
};
220221

221222
// Kinds of tag attributes.

llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h

+3
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,14 @@ class TargetLoweringObjectFileCOFF : public TargetLoweringObjectFile {
207207

208208
class TargetLoweringObjectFileWasm : public TargetLoweringObjectFile {
209209
mutable unsigned NextUniqueID = 0;
210+
SmallPtrSet<GlobalObject *, 2> Used;
210211

211212
public:
212213
TargetLoweringObjectFileWasm() = default;
213214
~TargetLoweringObjectFileWasm() override = default;
214215

216+
void getModuleMetadata(Module &M) override;
217+
215218
MCSection *getExplicitSectionGlobal(const GlobalObject *GO, SectionKind Kind,
216219
const TargetMachine &TM) const override;
217220

llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp

+22-7
Original file line numberDiff line numberDiff line change
@@ -2141,7 +2141,7 @@ static const Comdat *getWasmComdat(const GlobalValue *GV) {
21412141
return C;
21422142
}
21432143

2144-
static unsigned getWasmSectionFlags(SectionKind K) {
2144+
static unsigned getWasmSectionFlags(SectionKind K, bool Retain) {
21452145
unsigned Flags = 0;
21462146

21472147
if (K.isThreadLocal())
@@ -2150,11 +2150,22 @@ static unsigned getWasmSectionFlags(SectionKind K) {
21502150
if (K.isMergeableCString())
21512151
Flags |= wasm::WASM_SEG_FLAG_STRINGS;
21522152

2153+
if (Retain)
2154+
Flags |= wasm::WASM_SEG_FLAG_RETAIN;
2155+
21532156
// TODO(sbc): Add suport for K.isMergeableConst()
21542157

21552158
return Flags;
21562159
}
21572160

2161+
void TargetLoweringObjectFileWasm::getModuleMetadata(Module &M) {
2162+
SmallVector<GlobalValue *, 4> Vec;
2163+
collectUsedGlobalVariables(M, Vec, false);
2164+
for (GlobalValue *GV : Vec)
2165+
if (auto *GO = dyn_cast<GlobalObject>(GV))
2166+
Used.insert(GO);
2167+
}
2168+
21582169
MCSection *TargetLoweringObjectFileWasm::getExplicitSectionGlobal(
21592170
const GlobalObject *GO, SectionKind Kind, const TargetMachine &TM) const {
21602171
// We don't support explict section names for functions in the wasm object
@@ -2178,16 +2189,18 @@ MCSection *TargetLoweringObjectFileWasm::getExplicitSectionGlobal(
21782189
Group = C->getName();
21792190
}
21802191

2181-
unsigned Flags = getWasmSectionFlags(Kind);
2192+
unsigned Flags = getWasmSectionFlags(Kind, Used.count(GO));
21822193
MCSectionWasm *Section = getContext().getWasmSection(
21832194
Name, Kind, Flags, Group, MCContext::GenericSectionID);
21842195

21852196
return Section;
21862197
}
21872198

2188-
static MCSectionWasm *selectWasmSectionForGlobal(
2189-
MCContext &Ctx, const GlobalObject *GO, SectionKind Kind, Mangler &Mang,
2190-
const TargetMachine &TM, bool EmitUniqueSection, unsigned *NextUniqueID) {
2199+
static MCSectionWasm *
2200+
selectWasmSectionForGlobal(MCContext &Ctx, const GlobalObject *GO,
2201+
SectionKind Kind, Mangler &Mang,
2202+
const TargetMachine &TM, bool EmitUniqueSection,
2203+
unsigned *NextUniqueID, bool Retain) {
21912204
StringRef Group = "";
21922205
if (const Comdat *C = getWasmComdat(GO)) {
21932206
Group = C->getName();
@@ -2212,7 +2225,7 @@ static MCSectionWasm *selectWasmSectionForGlobal(
22122225
(*NextUniqueID)++;
22132226
}
22142227

2215-
unsigned Flags = getWasmSectionFlags(Kind);
2228+
unsigned Flags = getWasmSectionFlags(Kind, Retain);
22162229
return Ctx.getWasmSection(Name, Kind, Flags, Group, UniqueID);
22172230
}
22182231

@@ -2230,9 +2243,11 @@ MCSection *TargetLoweringObjectFileWasm::SelectSectionForGlobal(
22302243
else
22312244
EmitUniqueSection = TM.getDataSections();
22322245
EmitUniqueSection |= GO->hasComdat();
2246+
bool Retain = Used.count(GO);
2247+
EmitUniqueSection |= Retain;
22332248

22342249
return selectWasmSectionForGlobal(getContext(), GO, Kind, getMangler(), TM,
2235-
EmitUniqueSection, &NextUniqueID);
2250+
EmitUniqueSection, &NextUniqueID, Retain);
22362251
}
22372252

22382253
bool TargetLoweringObjectFileWasm::shouldPutJumpTableInFunctionSection(

llvm/lib/MC/MCParser/WasmAsmParser.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ class WasmAsmParser : public MCAsmParserExtension {
115115
case 'S':
116116
flags |= wasm::WASM_SEG_FLAG_STRINGS;
117117
break;
118+
case 'R':
119+
flags |= wasm::WASM_SEG_FLAG_RETAIN;
120+
break;
118121
default:
119122
return -1U;
120123
}

llvm/lib/MC/MCSectionWasm.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ void MCSectionWasm::printSwitchToSection(const MCAsmInfo &MAI, const Triple &T,
7070
OS << 'S';
7171
if (SegmentFlags & wasm::WASM_SEG_FLAG_TLS)
7272
OS << 'T';
73+
if (SegmentFlags & wasm::WASM_SEG_FLAG_RETAIN)
74+
OS << 'R';
7375

7476
OS << '"';
7577

llvm/lib/ObjectYAML/WasmYAML.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,7 @@ void ScalarBitSetTraits<WasmYAML::SegmentFlags>::bitset(
561561
#define BCase(X) IO.bitSetCase(Value, #X, wasm::WASM_SEG_FLAG_##X)
562562
BCase(STRINGS);
563563
BCase(TLS);
564+
BCase(RETAIN);
564565
#undef BCase
565566
}
566567

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
; RUN: llc < %s --mtriple=wasm32-unknown-unknown | FileCheck %s
2+
3+
@llvm.used = appending global [
4+
5 x ptr
5+
] [
6+
ptr @ga, ptr @gb, ptr @gc, ptr @gd, ptr @ge
7+
], section "llvm.metadata"
8+
9+
; CHECK: .section .data.ga,"R",@
10+
@ga = global i32 42
11+
; CHECK: .section .data.gb,"R",@
12+
@gb = internal global i32 41
13+
; CHECK: .section .data..Lgc,"R",@
14+
@gc = private global i32 40
15+
; CHECK: .section .rodata.gd,"R",@
16+
@gd = constant i32 39
17+
18+
; All sections with the same explicit name are flagged as retained if a part of them is retained.
19+
; CHECK: .section dddd,"R",@
20+
@ge = global i32 38, section "dddd"
21+
; CHECK: .section dddd,"R",@
22+
@gg = global i32 37, section "dddd"
+62-14
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,69 @@
1-
; RUN: llc -filetype=obj -wasm-keep-registers %s -o - | llvm-readobj --symbols - | FileCheck %s
1+
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -filetype=obj -wasm-keep-registers -o - | obj2yaml - | FileCheck %s
22

3-
target triple = "wasm32-unknown-unknown"
4-
5-
@llvm.used = appending global [1 x ptr] [ptr @foo], section "llvm.metadata"
3+
@llvm.used = appending global [5 x ptr] [
4+
ptr @foo, ptr @gv0, ptr @gv1, ptr @gv2, ptr @gv3
5+
], section "llvm.metadata"
66

77
define i32 @foo() {
88
entry:
99
ret i32 0
1010
}
1111

12-
; CHECK: Symbols [
13-
; CHECK-NEXT: Symbol {
14-
; CHECK-NEXT: Name: foo
15-
; CHECK-NEXT: Type: FUNCTION (0x0)
16-
; CHECK-NEXT: Flags [ (0x80)
17-
; CHECK-NEXT: NO_STRIP (0x80)
18-
; CHECK-NEXT: ]
19-
; CHECK-NEXT: ElementIndex: 0x0
20-
; CHECK-NEXT: }
21-
; CHECK-NEXT: ]
12+
; externally visible GV has NO_STRIP/RETAIN in both symtab entry and segment info
13+
@gv0 = global i32 42
14+
; internal GV has NO_STRIP/RETAIN in both symtab entry and segment info
15+
@gv1 = internal global i32 41
16+
; private GV has RETAIN in segment info only (no symtab entry)
17+
@gv2 = private global i32 40
18+
; explicit section names
19+
@gv3 = global i32 39, section "ddd.hello"
20+
@gv4.not.used = global i64 38, section "ddd.hello"
21+
22+
; CHECK: SymbolTable:
23+
; CHECK-NEXT: - Index: 0
24+
; CHECK-NEXT: Kind: FUNCTION
25+
; CHECK-NEXT: Name: foo
26+
; CHECK-NEXT: Flags: [ NO_STRIP ]
27+
; CHECK-NEXT: Function: 0
28+
; CHECK-NEXT: - Index: 1
29+
; CHECK-NEXT: Kind: DATA
30+
; CHECK-NEXT: Name: gv0
31+
; CHECK-NEXT: Flags: [ NO_STRIP ]
32+
; CHECK-NEXT: Segment: 0
33+
; CHECK-NEXT: Size: 4
34+
; CHECK-NEXT: - Index: 2
35+
; CHECK-NEXT: Kind: DATA
36+
; CHECK-NEXT: Name: gv1
37+
; CHECK-NEXT: Flags: [ BINDING_LOCAL, NO_STRIP ]
38+
; CHECK-NEXT: Segment: 1
39+
; CHECK-NEXT: Size: 4
40+
; CHECK-NEXT: - Index: 3
41+
; CHECK-NEXT: Kind: DATA
42+
; CHECK-NEXT: Name: gv3
43+
; CHECK-NEXT: Flags: [ NO_STRIP ]
44+
; CHECK-NEXT: Segment: 3
45+
; CHECK-NEXT: Size: 4
46+
; CHECK-NEXT: - Index: 4
47+
; CHECK-NEXT: Kind: DATA
48+
; CHECK-NEXT: Name: gv4.not.used
49+
; CHECK-NEXT: Flags: [ ]
50+
; CHECK-NEXT: Segment: 3
51+
; CHECK-NEXT: Offset: 8
52+
; CHECK-NEXT: Size: 8
53+
; CHECK-NEXT: SegmentInfo:
54+
; CHECK-NEXT: - Index: 0
55+
; CHECK-NEXT: Name: .data.gv0
56+
; CHECK-NEXT: Alignment: 2
57+
; CHECK-NEXT: Flags: [ RETAIN ]
58+
; CHECK-NEXT: - Index: 1
59+
; CHECK-NEXT: Name: .data.gv1
60+
; CHECK-NEXT: Alignment: 2
61+
; CHECK-NEXT: Flags: [ RETAIN ]
62+
; CHECK-NEXT: - Index: 2
63+
; CHECK-NEXT: Name: .data..Lgv2
64+
; CHECK-NEXT: Alignment: 2
65+
; CHECK-NEXT: Flags: [ RETAIN ]
66+
; CHECK-NEXT: - Index: 3
67+
; CHECK-NEXT: Name: ddd.hello
68+
; CHECK-NEXT: Alignment: 3
69+
; CHECK-NEXT: Flags: [ RETAIN ]

0 commit comments

Comments
 (0)