diff --git a/lld/test/wasm/bsymbolic.s b/lld/test/wasm/bsymbolic.s new file mode 100644 --- /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@GOT + end_function + +.globl get_bar_address +get_bar_address: + .functype get_bar_address () -> (i32) + global.get bar@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 --- a/lld/wasm/Config.h +++ b/lld/wasm/Config.h @@ -23,6 +23,7 @@ // 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 --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -327,6 +327,7 @@ // 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 @@ 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 --- a/lld/wasm/Options.td +++ b/lld/wasm/Options.td @@ -18,6 +18,8 @@ } // 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 --- a/lld/wasm/Relocations.cpp +++ b/lld/wasm/Relocations.cpp @@ -16,8 +16,17 @@ 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 we skip the GOT import for + // symbols that are defined within the current module. + if ((!config->shared || config->bsymbolic) && sym->isDefined()) + return false; + return true; } static bool allowUndefined(const Symbol* sym) { @@ -41,17 +50,10 @@ } 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.h b/lld/wasm/SyntheticSections.h --- a/lld/wasm/SyntheticSections.h +++ b/lld/wasm/SyntheticSections.h @@ -197,21 +197,35 @@ 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 + // elimiantred 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 dataAddressGlobals; protected: bool isSealed = false; std::vector inputGlobals; - std::vector staticGotSymbols; + std::vector internalGotSymbols; }; class ExportSection : public SyntheticSection { diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp --- a/lld/wasm/SyntheticSections.cpp +++ b/lld/wasm/SyntheticSections.cpp @@ -268,21 +268,56 @@ 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(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(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(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(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 @@ 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(sym)) global.InitExpr.Value.Int32 = d->getVirtualAddress(); diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -965,9 +965,17 @@ { 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"); }