[lld] 2513407 - [lld][WebAssembly] Add support for -Bsymbolic flag
Sam Clegg via llvm-commits
llvm-commits at lists.llvm.org
Mon Oct 12 17:25:25 PDT 2020
Author: Sam Clegg
Date: 2020-10-12T17:25:04-07:00
New Revision: 2513407d39506edf2a98f647088a9e1789f8c418
URL: https://github.com/llvm/llvm-project/commit/2513407d39506edf2a98f647088a9e1789f8c418
DIFF: https://github.com/llvm/llvm-project/commit/2513407d39506edf2a98f647088a9e1789f8c418.diff
LOG: [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
Added:
lld/test/wasm/bsymbolic.s
Modified:
lld/wasm/Config.h
lld/wasm/Driver.cpp
lld/wasm/Options.td
lld/wasm/Relocations.cpp
lld/wasm/SyntheticSections.cpp
lld/wasm/SyntheticSections.h
lld/wasm/Writer.cpp
Removed:
################################################################################
diff --git a/lld/test/wasm/bsymbolic.s b/lld/test/wasm/bsymbolic.s
new file mode 100644
index 000000000000..dc0e0ddcc773
--- /dev/null
+++ b/lld/test/wasm/bsymbolic.s
@@ -0,0 +1,79 @@
+// RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %s -o %t.o
+// RUN: wasm-ld --no-entry -Bsymbolic %t.o -o %t2.so 2>&1 | FileCheck -check-prefix=WARNING %s
+// WARNING: warning: -Bsymbolic is only meaningful when combined with -shared
+
+// RUN: wasm-ld --experimental-pic -shared %t.o -o %t0.so
+// RUN: obj2yaml %t0.so | FileCheck -check-prefix=NOOPTION %s
+
+// RUN: wasm-ld --experimental-pic -shared -Bsymbolic %t.o -o %t1.so
+// RUN: obj2yaml %t1.so | FileCheck -check-prefix=SYMBOLIC %s
+
+// NOOPTION - Type: IMPORT
+// NOOPTION: - Module: GOT.func
+// NOOPTION-NEXT: Field: foo
+// NOOPTION-NEXT: Kind: GLOBAL
+// NOOPTION-NEXT: GlobalType: I32
+// NOOPTION-NEXT: GlobalMutable: true
+// NOOPTION-NEXT: - Module: GOT.mem
+// NOOPTION-NEXT: Field: bar
+// NOOPTION-NEXT: Kind: GLOBAL
+// NOOPTION-NEXT: GlobalType: I32
+// NOOPTION-NEXT: GlobalMutable: true
+
+// NOOPTION: - Type: GLOBAL
+// NOOPTION-NEXT: Globals:
+// NOOPTION-NEXT: - Index: 4
+// NOOPTION-NEXT: Type: I32
+// NOOPTION-NEXT: Mutable: false
+// NOOPTION-NEXT: InitExpr:
+// NOOPTION-NEXT: Opcode: I32_CONST
+// NOOPTION-NEXT: Value: 0
+// NOOPTION-NEXT: - Type: EXPORT
+
+// SYMBOLIC-NOT: - Module: GOT.mem
+// SYMBOLIC-NOT: - Module: GOT.func
+
+// SYMBOLIC: - Type: GLOBAL
+// SYMBOLIC-NEXT: Globals:
+// SYMBOLIC-NEXT: - Index: 2
+// SYMBOLIC-NEXT: Type: I32
+// SYMBOLIC-NEXT: Mutable: true
+// SYMBOLIC-NEXT: InitExpr:
+// SYMBOLIC-NEXT: Opcode: I32_CONST
+// SYMBOLIC-NEXT: Value: 0
+// SYMBOLIC-NEXT: - Index: 3
+// SYMBOLIC-NEXT: Type: I32
+// SYMBOLIC-NEXT: Mutable: true
+// SYMBOLIC-NEXT: InitExpr:
+// SYMBOLIC-NEXT: Opcode: I32_CONST
+// SYMBOLIC-NEXT: Value: 0
+// SYMBOLIC-NEXT: - Index: 4
+// SYMBOLIC-NEXT: Type: I32
+// SYMBOLIC-NEXT: Mutable: false
+// SYMBOLIC-NEXT: InitExpr:
+// SYMBOLIC-NEXT: Opcode: I32_CONST
+// SYMBOLIC-NEXT: Value: 0
+// SYMBOLIC-NEXT: - Type: EXPORT
+
+.globl foo
+foo:
+ .functype foo () -> ()
+ end_function
+
+.globl get_foo_address
+get_foo_address:
+ .functype get_foo_address () -> (i32)
+ global.get foo at GOT
+ end_function
+
+.globl get_bar_address
+get_bar_address:
+ .functype get_bar_address () -> (i32)
+ global.get bar at GOT
+ end_function
+
+.globl bar
+.section .data.bar,"",@
+bar:
+ .int 42
+.size bar, 4
diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index cd6d57333a21..4439098e65ba 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -23,6 +23,7 @@ namespace wasm {
// Most fields are initialized by the driver.
struct Configuration {
bool allowUndefined;
+ bool bsymbolic;
bool checkFeatures;
bool compressRelocations;
bool demangle;
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index a6d26dcfcc43..c00c7eb7522e 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -327,6 +327,7 @@ static StringRef getEntry(opt::InputArgList &args) {
// Initializes Config members by the command line options.
static void readConfigs(opt::InputArgList &args) {
config->allowUndefined = args.hasArg(OPT_allow_undefined);
+ config->bsymbolic = args.hasArg(OPT_Bsymbolic);
config->checkFeatures =
args.hasFlag(OPT_check_features, OPT_no_check_features, true);
config->compressRelocations = args.hasArg(OPT_compress_relocations);
@@ -490,6 +491,10 @@ static void checkOptions(opt::InputArgList &args) {
warn("creating PIEs, with -pie, is not yet stable");
}
}
+
+ if (config->bsymbolic && !config->shared) {
+ warn("-Bsymbolic is only meaningful when combined with -shared");
+ }
}
// Force Sym to be entered in the output. Used for -u or equivalent.
diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td
index 27d54c5cdc64..1a5cf3513fea 100644
--- a/lld/wasm/Options.td
+++ b/lld/wasm/Options.td
@@ -18,6 +18,8 @@ multiclass B<string name, string help1, string help2> {
}
// The following flags are shared with the ELF linker
+def Bsymbolic: F<"Bsymbolic">, HelpText<"Bind defined symbols locally">;
+
def color_diagnostics: F<"color-diagnostics">,
HelpText<"Use colors in diagnostics">;
diff --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp
index 0a364d1a53ac..eec33b670127 100644
--- a/lld/wasm/Relocations.cpp
+++ b/lld/wasm/Relocations.cpp
@@ -16,8 +16,17 @@ using namespace llvm::wasm;
namespace lld {
namespace wasm {
+
static bool requiresGOTAccess(const Symbol *sym) {
- return config->isPic && !sym->isHidden() && !sym->isLocal();
+ if (!config->isPic)
+ return false;
+ if (sym->isHidden() || sym->isLocal())
+ return false;
+ // With `-Bsymbolic` (or when building an executable) as don't need to use
+ // the GOT for symbols that are defined within the current module.
+ if (sym->isDefined() && (!config->shared || config->bsymbolic))
+ return false;
+ return true;
}
static bool allowUndefined(const Symbol* sym) {
@@ -41,17 +50,10 @@ static void reportUndefined(const Symbol* sym) {
}
static void addGOTEntry(Symbol *sym) {
- // In PIC mode a GOT entry is an imported global that the dynamic linker
- // will assign.
- // In non-PIC mode (i.e. when code compiled as fPIC is linked into a static
- // binary) we create an internal wasm global with a fixed value that takes the
- // place of th GOT entry and effectivly acts as an i32 const. This can
- // potentially be optimized away at runtime or with a post-link tool.
- // TODO(sbc): Linker relaxation might also be able to optimize this away.
- if (config->isPic)
+ if (requiresGOTAccess(sym))
out.importSec->addGOTEntry(sym);
else
- out.globalSec->addStaticGOTEntry(sym);
+ out.globalSec->addInternalGOTEntry(sym);
}
void scanRelocations(InputChunk *chunk) {
diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp
index 753482fda410..ca4394e17212 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -268,21 +268,56 @@ void GlobalSection::assignIndexes() {
uint32_t globalIndex = out.importSec->getNumImportedGlobals();
for (InputGlobal *g : inputGlobals)
g->setGlobalIndex(globalIndex++);
- for (Symbol *sym : staticGotSymbols)
+ for (Symbol *sym : internalGotSymbols)
sym->setGOTIndex(globalIndex++);
isSealed = true;
}
-void GlobalSection::addStaticGOTEntry(Symbol *sym) {
+void GlobalSection::addInternalGOTEntry(Symbol *sym) {
assert(!isSealed);
if (sym->requiresGOT)
return;
- LLVM_DEBUG(dbgs() << "addStaticGOTEntry: " << sym->getName() << " "
+ LLVM_DEBUG(dbgs() << "addInternalGOTEntry: " << sym->getName() << " "
<< toString(sym->kind()) << "\n");
sym->requiresGOT = true;
if (auto *F = dyn_cast<FunctionSymbol>(sym))
out.elemSec->addEntry(F);
- staticGotSymbols.push_back(sym);
+ internalGotSymbols.push_back(sym);
+}
+
+void GlobalSection::generateRelocationCode(raw_ostream &os) const {
+ unsigned opcode_ptr_const = config->is64.getValueOr(false)
+ ? WASM_OPCODE_I64_CONST
+ : WASM_OPCODE_I32_CONST;
+ unsigned opcode_ptr_add = config->is64.getValueOr(false)
+ ? WASM_OPCODE_I64_ADD
+ : WASM_OPCODE_I32_ADD;
+
+ for (const Symbol *sym : internalGotSymbols) {
+ if (auto *d = dyn_cast<DefinedData>(sym)) {
+ // Get __memory_base
+ writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
+ writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "__memory_base");
+
+ // Add the virtual address of the data symbol
+ writeU8(os, opcode_ptr_const, "CONST");
+ writeSleb128(os, d->getVirtualAddress(), "offset");
+ } else if (auto *f = dyn_cast<FunctionSymbol>(sym)) {
+ // Get __table_base
+ writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
+ writeUleb128(os, WasmSym::tableBase->getGlobalIndex(), "__table_base");
+
+ // Add the table index to __table_base
+ writeU8(os, opcode_ptr_const, "CONST");
+ writeSleb128(os, f->getTableIndex(), "offset");
+ } else {
+ assert(isa<UndefinedData>(sym));
+ continue;
+ }
+ writeU8(os, opcode_ptr_add, "ADD");
+ writeU8(os, WASM_OPCODE_GLOBAL_SET, "GLOBAL_SET");
+ writeUleb128(os, sym->getGOTIndex(), "got_entry");
+ }
}
void GlobalSection::writeBody() {
@@ -292,9 +327,9 @@ void GlobalSection::writeBody() {
for (InputGlobal *g : inputGlobals)
writeGlobal(os, g->global);
// TODO(wvo): when do these need I64_CONST?
- for (const Symbol *sym : staticGotSymbols) {
+ for (const Symbol *sym : internalGotSymbols) {
WasmGlobal global;
- global.Type = {WASM_TYPE_I32, false};
+ global.Type = {WASM_TYPE_I32, config->isPic};
global.InitExpr.Opcode = WASM_OPCODE_I32_CONST;
if (auto *d = dyn_cast<DefinedData>(sym))
global.InitExpr.Value.Int32 = d->getVirtualAddress();
diff --git a/lld/wasm/SyntheticSections.h b/lld/wasm/SyntheticSections.h
index 079de93492f1..8e4019440171 100644
--- a/lld/wasm/SyntheticSections.h
+++ b/lld/wasm/SyntheticSections.h
@@ -197,21 +197,35 @@ class GlobalSection : public SyntheticSection {
uint32_t numGlobals() const {
assert(isSealed);
return inputGlobals.size() + dataAddressGlobals.size() +
- staticGotSymbols.size();
+ internalGotSymbols.size();
}
bool isNeeded() const override { return numGlobals() > 0; }
void assignIndexes() override;
void writeBody() override;
void addGlobal(InputGlobal *global);
void addDataAddressGlobal(DefinedData *global);
- void addStaticGOTEntry(Symbol *sym);
+
+ // Add an internal GOT entry global that corresponds to the given symbol.
+ // Normally GOT entries are imported and assigned by the external dynamic
+ // linker. However, when linking PIC code statically or when linking with
+ // -Bsymbolic we can internalize GOT entries by declaring globals the hold
+ // symbol addresses.
+ //
+ // For the static linking case these internal globals can be completely
+ // eliminated by a post-link optimizer such as wasm-opt.
+ //
+ // TODO(sbc): Another approach to optimizing these away could be to use
+ // specific relocation types combined with linker relaxation which could
+ // transform a `global.get` to an `i32.const`.
+ void addInternalGOTEntry(Symbol *sym);
+ void generateRelocationCode(raw_ostream &os) const;
std::vector<const DefinedData *> dataAddressGlobals;
protected:
bool isSealed = false;
std::vector<InputGlobal *> inputGlobals;
- std::vector<Symbol *> staticGotSymbols;
+ std::vector<Symbol *> internalGotSymbols;
};
class ExportSection : public SyntheticSection {
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index fee87f292c90..31618314cf52 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -965,9 +965,17 @@ void Writer::createApplyRelocationsFunction() {
{
raw_string_ostream os(bodyContent);
writeUleb128(os, 0, "num locals");
+
+ // First apply relocations to any internalized GOT entries. These
+ // are the result of relaxation when building with -Bsymbolic.
+ out.globalSec->generateRelocationCode(os);
+
+ // Next apply any realocation to the data section by reading GOT entry
+ // globals.
for (const OutputSegment *seg : segments)
for (const InputSegment *inSeg : seg->inputSegments)
inSeg->generateRelocationCode(os);
+
writeU8(os, WASM_OPCODE_END, "END");
}
More information about the llvm-commits
mailing list