Skip to content

Commit 2513407

Browse files
committed
[lld][WebAssembly] Add support for -Bsymbolic flag
This flag works in a similar way to the ELF linker in that it will resolve any defined symbols to their local definition with a shared library or -pie executable. This flag has no effect on static linking. Differential Revision: https://reviews.llvm.org/D89152
1 parent b215a26 commit 2513407

File tree

8 files changed

+165
-19
lines changed

8 files changed

+165
-19
lines changed

lld/test/wasm/bsymbolic.s

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %s -o %t.o
2+
// RUN: wasm-ld --no-entry -Bsymbolic %t.o -o %t2.so 2>&1 | FileCheck -check-prefix=WARNING %s
3+
// WARNING: warning: -Bsymbolic is only meaningful when combined with -shared
4+
5+
// RUN: wasm-ld --experimental-pic -shared %t.o -o %t0.so
6+
// RUN: obj2yaml %t0.so | FileCheck -check-prefix=NOOPTION %s
7+
8+
// RUN: wasm-ld --experimental-pic -shared -Bsymbolic %t.o -o %t1.so
9+
// RUN: obj2yaml %t1.so | FileCheck -check-prefix=SYMBOLIC %s
10+
11+
// NOOPTION - Type: IMPORT
12+
// NOOPTION: - Module: GOT.func
13+
// NOOPTION-NEXT: Field: foo
14+
// NOOPTION-NEXT: Kind: GLOBAL
15+
// NOOPTION-NEXT: GlobalType: I32
16+
// NOOPTION-NEXT: GlobalMutable: true
17+
// NOOPTION-NEXT: - Module: GOT.mem
18+
// NOOPTION-NEXT: Field: bar
19+
// NOOPTION-NEXT: Kind: GLOBAL
20+
// NOOPTION-NEXT: GlobalType: I32
21+
// NOOPTION-NEXT: GlobalMutable: true
22+
23+
// NOOPTION: - Type: GLOBAL
24+
// NOOPTION-NEXT: Globals:
25+
// NOOPTION-NEXT: - Index: 4
26+
// NOOPTION-NEXT: Type: I32
27+
// NOOPTION-NEXT: Mutable: false
28+
// NOOPTION-NEXT: InitExpr:
29+
// NOOPTION-NEXT: Opcode: I32_CONST
30+
// NOOPTION-NEXT: Value: 0
31+
// NOOPTION-NEXT: - Type: EXPORT
32+
33+
// SYMBOLIC-NOT: - Module: GOT.mem
34+
// SYMBOLIC-NOT: - Module: GOT.func
35+
36+
// SYMBOLIC: - Type: GLOBAL
37+
// SYMBOLIC-NEXT: Globals:
38+
// SYMBOLIC-NEXT: - Index: 2
39+
// SYMBOLIC-NEXT: Type: I32
40+
// SYMBOLIC-NEXT: Mutable: true
41+
// SYMBOLIC-NEXT: InitExpr:
42+
// SYMBOLIC-NEXT: Opcode: I32_CONST
43+
// SYMBOLIC-NEXT: Value: 0
44+
// SYMBOLIC-NEXT: - Index: 3
45+
// SYMBOLIC-NEXT: Type: I32
46+
// SYMBOLIC-NEXT: Mutable: true
47+
// SYMBOLIC-NEXT: InitExpr:
48+
// SYMBOLIC-NEXT: Opcode: I32_CONST
49+
// SYMBOLIC-NEXT: Value: 0
50+
// SYMBOLIC-NEXT: - Index: 4
51+
// SYMBOLIC-NEXT: Type: I32
52+
// SYMBOLIC-NEXT: Mutable: false
53+
// SYMBOLIC-NEXT: InitExpr:
54+
// SYMBOLIC-NEXT: Opcode: I32_CONST
55+
// SYMBOLIC-NEXT: Value: 0
56+
// SYMBOLIC-NEXT: - Type: EXPORT
57+
58+
.globl foo
59+
foo:
60+
.functype foo () -> ()
61+
end_function
62+
63+
.globl get_foo_address
64+
get_foo_address:
65+
.functype get_foo_address () -> (i32)
66+
global.get foo@GOT
67+
end_function
68+
69+
.globl get_bar_address
70+
get_bar_address:
71+
.functype get_bar_address () -> (i32)
72+
global.get bar@GOT
73+
end_function
74+
75+
.globl bar
76+
.section .data.bar,"",@
77+
bar:
78+
.int 42
79+
.size bar, 4

lld/wasm/Config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace wasm {
2323
// Most fields are initialized by the driver.
2424
struct Configuration {
2525
bool allowUndefined;
26+
bool bsymbolic;
2627
bool checkFeatures;
2728
bool compressRelocations;
2829
bool demangle;

lld/wasm/Driver.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ static StringRef getEntry(opt::InputArgList &args) {
327327
// Initializes Config members by the command line options.
328328
static void readConfigs(opt::InputArgList &args) {
329329
config->allowUndefined = args.hasArg(OPT_allow_undefined);
330+
config->bsymbolic = args.hasArg(OPT_Bsymbolic);
330331
config->checkFeatures =
331332
args.hasFlag(OPT_check_features, OPT_no_check_features, true);
332333
config->compressRelocations = args.hasArg(OPT_compress_relocations);
@@ -490,6 +491,10 @@ static void checkOptions(opt::InputArgList &args) {
490491
warn("creating PIEs, with -pie, is not yet stable");
491492
}
492493
}
494+
495+
if (config->bsymbolic && !config->shared) {
496+
warn("-Bsymbolic is only meaningful when combined with -shared");
497+
}
493498
}
494499

495500
// Force Sym to be entered in the output. Used for -u or equivalent.

lld/wasm/Options.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ multiclass B<string name, string help1, string help2> {
1818
}
1919

2020
// The following flags are shared with the ELF linker
21+
def Bsymbolic: F<"Bsymbolic">, HelpText<"Bind defined symbols locally">;
22+
2123
def color_diagnostics: F<"color-diagnostics">,
2224
HelpText<"Use colors in diagnostics">;
2325

lld/wasm/Relocations.cpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,17 @@ using namespace llvm::wasm;
1616

1717
namespace lld {
1818
namespace wasm {
19+
1920
static bool requiresGOTAccess(const Symbol *sym) {
20-
return config->isPic && !sym->isHidden() && !sym->isLocal();
21+
if (!config->isPic)
22+
return false;
23+
if (sym->isHidden() || sym->isLocal())
24+
return false;
25+
// With `-Bsymbolic` (or when building an executable) as don't need to use
26+
// the GOT for symbols that are defined within the current module.
27+
if (sym->isDefined() && (!config->shared || config->bsymbolic))
28+
return false;
29+
return true;
2130
}
2231

2332
static bool allowUndefined(const Symbol* sym) {
@@ -41,17 +50,10 @@ static void reportUndefined(const Symbol* sym) {
4150
}
4251

4352
static void addGOTEntry(Symbol *sym) {
44-
// In PIC mode a GOT entry is an imported global that the dynamic linker
45-
// will assign.
46-
// In non-PIC mode (i.e. when code compiled as fPIC is linked into a static
47-
// binary) we create an internal wasm global with a fixed value that takes the
48-
// place of th GOT entry and effectivly acts as an i32 const. This can
49-
// potentially be optimized away at runtime or with a post-link tool.
50-
// TODO(sbc): Linker relaxation might also be able to optimize this away.
51-
if (config->isPic)
53+
if (requiresGOTAccess(sym))
5254
out.importSec->addGOTEntry(sym);
5355
else
54-
out.globalSec->addStaticGOTEntry(sym);
56+
out.globalSec->addInternalGOTEntry(sym);
5557
}
5658

5759
void scanRelocations(InputChunk *chunk) {

lld/wasm/SyntheticSections.cpp

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -268,21 +268,56 @@ void GlobalSection::assignIndexes() {
268268
uint32_t globalIndex = out.importSec->getNumImportedGlobals();
269269
for (InputGlobal *g : inputGlobals)
270270
g->setGlobalIndex(globalIndex++);
271-
for (Symbol *sym : staticGotSymbols)
271+
for (Symbol *sym : internalGotSymbols)
272272
sym->setGOTIndex(globalIndex++);
273273
isSealed = true;
274274
}
275275

276-
void GlobalSection::addStaticGOTEntry(Symbol *sym) {
276+
void GlobalSection::addInternalGOTEntry(Symbol *sym) {
277277
assert(!isSealed);
278278
if (sym->requiresGOT)
279279
return;
280-
LLVM_DEBUG(dbgs() << "addStaticGOTEntry: " << sym->getName() << " "
280+
LLVM_DEBUG(dbgs() << "addInternalGOTEntry: " << sym->getName() << " "
281281
<< toString(sym->kind()) << "\n");
282282
sym->requiresGOT = true;
283283
if (auto *F = dyn_cast<FunctionSymbol>(sym))
284284
out.elemSec->addEntry(F);
285-
staticGotSymbols.push_back(sym);
285+
internalGotSymbols.push_back(sym);
286+
}
287+
288+
void GlobalSection::generateRelocationCode(raw_ostream &os) const {
289+
unsigned opcode_ptr_const = config->is64.getValueOr(false)
290+
? WASM_OPCODE_I64_CONST
291+
: WASM_OPCODE_I32_CONST;
292+
unsigned opcode_ptr_add = config->is64.getValueOr(false)
293+
? WASM_OPCODE_I64_ADD
294+
: WASM_OPCODE_I32_ADD;
295+
296+
for (const Symbol *sym : internalGotSymbols) {
297+
if (auto *d = dyn_cast<DefinedData>(sym)) {
298+
// Get __memory_base
299+
writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
300+
writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "__memory_base");
301+
302+
// Add the virtual address of the data symbol
303+
writeU8(os, opcode_ptr_const, "CONST");
304+
writeSleb128(os, d->getVirtualAddress(), "offset");
305+
} else if (auto *f = dyn_cast<FunctionSymbol>(sym)) {
306+
// Get __table_base
307+
writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
308+
writeUleb128(os, WasmSym::tableBase->getGlobalIndex(), "__table_base");
309+
310+
// Add the table index to __table_base
311+
writeU8(os, opcode_ptr_const, "CONST");
312+
writeSleb128(os, f->getTableIndex(), "offset");
313+
} else {
314+
assert(isa<UndefinedData>(sym));
315+
continue;
316+
}
317+
writeU8(os, opcode_ptr_add, "ADD");
318+
writeU8(os, WASM_OPCODE_GLOBAL_SET, "GLOBAL_SET");
319+
writeUleb128(os, sym->getGOTIndex(), "got_entry");
320+
}
286321
}
287322

288323
void GlobalSection::writeBody() {
@@ -292,9 +327,9 @@ void GlobalSection::writeBody() {
292327
for (InputGlobal *g : inputGlobals)
293328
writeGlobal(os, g->global);
294329
// TODO(wvo): when do these need I64_CONST?
295-
for (const Symbol *sym : staticGotSymbols) {
330+
for (const Symbol *sym : internalGotSymbols) {
296331
WasmGlobal global;
297-
global.Type = {WASM_TYPE_I32, false};
332+
global.Type = {WASM_TYPE_I32, config->isPic};
298333
global.InitExpr.Opcode = WASM_OPCODE_I32_CONST;
299334
if (auto *d = dyn_cast<DefinedData>(sym))
300335
global.InitExpr.Value.Int32 = d->getVirtualAddress();

lld/wasm/SyntheticSections.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,21 +197,35 @@ class GlobalSection : public SyntheticSection {
197197
uint32_t numGlobals() const {
198198
assert(isSealed);
199199
return inputGlobals.size() + dataAddressGlobals.size() +
200-
staticGotSymbols.size();
200+
internalGotSymbols.size();
201201
}
202202
bool isNeeded() const override { return numGlobals() > 0; }
203203
void assignIndexes() override;
204204
void writeBody() override;
205205
void addGlobal(InputGlobal *global);
206206
void addDataAddressGlobal(DefinedData *global);
207-
void addStaticGOTEntry(Symbol *sym);
207+
208+
// Add an internal GOT entry global that corresponds to the given symbol.
209+
// Normally GOT entries are imported and assigned by the external dynamic
210+
// linker. However, when linking PIC code statically or when linking with
211+
// -Bsymbolic we can internalize GOT entries by declaring globals the hold
212+
// symbol addresses.
213+
//
214+
// For the static linking case these internal globals can be completely
215+
// eliminated by a post-link optimizer such as wasm-opt.
216+
//
217+
// TODO(sbc): Another approach to optimizing these away could be to use
218+
// specific relocation types combined with linker relaxation which could
219+
// transform a `global.get` to an `i32.const`.
220+
void addInternalGOTEntry(Symbol *sym);
221+
void generateRelocationCode(raw_ostream &os) const;
208222

209223
std::vector<const DefinedData *> dataAddressGlobals;
210224

211225
protected:
212226
bool isSealed = false;
213227
std::vector<InputGlobal *> inputGlobals;
214-
std::vector<Symbol *> staticGotSymbols;
228+
std::vector<Symbol *> internalGotSymbols;
215229
};
216230

217231
class ExportSection : public SyntheticSection {

lld/wasm/Writer.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,9 +965,17 @@ void Writer::createApplyRelocationsFunction() {
965965
{
966966
raw_string_ostream os(bodyContent);
967967
writeUleb128(os, 0, "num locals");
968+
969+
// First apply relocations to any internalized GOT entries. These
970+
// are the result of relaxation when building with -Bsymbolic.
971+
out.globalSec->generateRelocationCode(os);
972+
973+
// Next apply any realocation to the data section by reading GOT entry
974+
// globals.
968975
for (const OutputSegment *seg : segments)
969976
for (const InputSegment *inSeg : seg->inputSegments)
970977
inSeg->generateRelocationCode(os);
978+
971979
writeU8(os, WASM_OPCODE_END, "END");
972980
}
973981

0 commit comments

Comments
 (0)