[lld][WebAssembly] Allow linking of pic code into static binaries
Summary: See https://github.com/emscripten-core/emscripten/issues/9013 Subscribers: dschuff, jgravelle-google, aheejin, sunfish, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D65922 llvm-svn: 368719
This commit is contained in:
parent
57ae300562
commit
7185a7301e
|
@ -0,0 +1,95 @@
|
|||
; Test that PIC code can be linked into static binaries.
|
||||
; In this case the GOT entries will end up as internalized wasm globals with
|
||||
; fixed values.
|
||||
; RUN: llc -relocation-model=pic -filetype=obj %p/Inputs/ret32.ll -o %t.ret32.o
|
||||
; RUN: llc -relocation-model=pic -filetype=obj %s -o %t.o
|
||||
; RUN: wasm-ld -o %t.wasm %t.o %t.ret32.o
|
||||
; RUN: obj2yaml %t.wasm | FileCheck %s
|
||||
|
||||
target triple = "wasm32-unknown-emscripten"
|
||||
|
||||
declare i32 @ret32(float)
|
||||
@global_float = global float 1.0
|
||||
@hidden_float = hidden global float 2.0
|
||||
|
||||
@ret32_ptr = global i32 (float)* @ret32, align 4
|
||||
|
||||
define i32 (float)* @getaddr_external() {
|
||||
ret i32 (float)* @ret32;
|
||||
}
|
||||
|
||||
define i32 ()* @getaddr_hidden() {
|
||||
ret i32 ()* @hidden_func;
|
||||
}
|
||||
|
||||
define hidden i32 @hidden_func() {
|
||||
ret i32 1
|
||||
}
|
||||
|
||||
define void @_start() {
|
||||
entry:
|
||||
%f = load float, float* @hidden_float, align 4
|
||||
%addr = load i32 (float)*, i32 (float)** @ret32_ptr, align 4
|
||||
%arg = load float, float* @global_float, align 4
|
||||
call i32 %addr(float %arg)
|
||||
|
||||
%addr2 = call i32 (float)* @getaddr_external()
|
||||
%arg2 = load float, float* @hidden_float, align 4
|
||||
call i32 %addr2(float %arg2)
|
||||
|
||||
%addr3 = call i32 ()* @getaddr_hidden()
|
||||
call i32 %addr3()
|
||||
|
||||
ret void
|
||||
}
|
||||
|
||||
; CHECK: - Type: GLOBAL
|
||||
; CHECK-NEXT: Globals:
|
||||
|
||||
; __stack_pointer
|
||||
; CHECK-NEXT: - Index: 0
|
||||
; CHECK-NEXT: Type: I32
|
||||
; CHECK-NEXT: Mutable: true
|
||||
; CHECK-NEXT: InitExpr:
|
||||
; CHECK-NEXT: Opcode: I32_CONST
|
||||
; CHECK-NEXT: Value: 66576
|
||||
|
||||
; GOT.func.ret32
|
||||
; CHECK-NEXT: - Index: 1
|
||||
; CHECK-NEXT: Type: I32
|
||||
; CHECK-NEXT: Mutable: false
|
||||
; CHECK-NEXT: InitExpr:
|
||||
; CHECK-NEXT: Opcode: I32_CONST
|
||||
; CHECK-NEXT: Value: 2
|
||||
|
||||
; __table_base
|
||||
; CHECK-NEXT: - Index: 2
|
||||
; CHECK-NEXT: Type: I32
|
||||
; CHECK-NEXT: Mutable: false
|
||||
; CHECK-NEXT: InitExpr:
|
||||
; CHECK-NEXT: Opcode: I32_CONST
|
||||
; CHECK-NEXT: Value: 1
|
||||
|
||||
; GOT.mem.global_float
|
||||
; CHECK-NEXT: - Index: 3
|
||||
; CHECK-NEXT: Type: I32
|
||||
; CHECK-NEXT: Mutable: false
|
||||
; CHECK-NEXT: InitExpr:
|
||||
; CHECK-NEXT: Opcode: I32_CONST
|
||||
; CHECK-NEXT: Value: 1024
|
||||
|
||||
; GOT.mem.ret32_ptr
|
||||
; CHECK-NEXT: - Index: 4
|
||||
; CHECK-NEXT: Type: I32
|
||||
; CHECK-NEXT: Mutable: false
|
||||
; CHECK-NEXT: InitExpr:
|
||||
; CHECK-NEXT: Opcode: I32_CONST
|
||||
; CHECK-NEXT: Value: 1032
|
||||
|
||||
; __memory_base
|
||||
; CHECK-NEXT: - Index: 5
|
||||
; CHECK-NEXT: Type: I32
|
||||
; CHECK-NEXT: Mutable: false
|
||||
; CHECK-NEXT: InitExpr:
|
||||
; CHECK-NEXT: Opcode: I32_CONST
|
||||
; CHECK-NEXT: Value: 1024
|
|
@ -536,6 +536,8 @@ static void createOptionalSymbols() {
|
|||
if (!config->isPic) {
|
||||
WasmSym::globalBase = symtab->addOptionalDataSymbol("__global_base");
|
||||
WasmSym::heapBase = symtab->addOptionalDataSymbol("__heap_base");
|
||||
WasmSym::definedMemoryBase = symtab->addOptionalDataSymbol("__memory_base");
|
||||
WasmSym::definedTableBase = symtab->addOptionalDataSymbol("__table_base");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,20 @@ static void reportUndefined(const Symbol* sym) {
|
|||
error(toString(sym->getFile()) + ": undefined symbol: " + toString(*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)
|
||||
out.importSec->addGOTEntry(sym);
|
||||
else
|
||||
out.globalSec->addDummyGOTEntry(sym);
|
||||
}
|
||||
|
||||
void lld::wasm::scanRelocations(InputChunk *chunk) {
|
||||
if (!chunk->live)
|
||||
return;
|
||||
|
@ -67,7 +81,7 @@ void lld::wasm::scanRelocations(InputChunk *chunk) {
|
|||
break;
|
||||
case R_WASM_GLOBAL_INDEX_LEB:
|
||||
if (!isa<GlobalSymbol>(sym))
|
||||
out.importSec->addGOTEntry(sym);
|
||||
addGOTEntry(sym);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -88,7 +102,7 @@ void lld::wasm::scanRelocations(InputChunk *chunk) {
|
|||
// will be converted into code by `generateRelocationCode`. This code
|
||||
// requires the symbols to have GOT entires.
|
||||
if (requiresGOTAccess(sym))
|
||||
out.importSec->addGOTEntry(sym);
|
||||
addGOTEntry(sym);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -37,7 +37,9 @@ GlobalSymbol *WasmSym::tlsBase;
|
|||
GlobalSymbol *WasmSym::tlsSize;
|
||||
GlobalSymbol *WasmSym::tlsAlign;
|
||||
UndefinedGlobal *WasmSym::tableBase;
|
||||
DefinedData *WasmSym::definedTableBase;
|
||||
UndefinedGlobal *WasmSym::memoryBase;
|
||||
DefinedData *WasmSym::definedMemoryBase;
|
||||
|
||||
WasmSymbolType Symbol::getWasmType() const {
|
||||
if (isa<FunctionSymbol>(this))
|
||||
|
@ -111,9 +113,11 @@ void Symbol::setOutputSymbolIndex(uint32_t index) {
|
|||
void Symbol::setGOTIndex(uint32_t index) {
|
||||
LLVM_DEBUG(dbgs() << "setGOTIndex " << name << " -> " << index << "\n");
|
||||
assert(gotIndex == INVALID_INDEX);
|
||||
if (config->isPic) {
|
||||
// Any symbol that is assigned a GOT entry must be exported othewise the
|
||||
// dynamic linker won't be able create the entry that contains it.
|
||||
forceExport = true;
|
||||
}
|
||||
gotIndex = index;
|
||||
}
|
||||
|
||||
|
|
|
@ -472,10 +472,12 @@ struct WasmSym {
|
|||
// __table_base
|
||||
// Used in PIC code for offset of indirect function table
|
||||
static UndefinedGlobal *tableBase;
|
||||
static DefinedData *definedTableBase;
|
||||
|
||||
// __memory_base
|
||||
// Used in PIC code for offset of global data
|
||||
static UndefinedGlobal *memoryBase;
|
||||
static DefinedData *definedMemoryBase;
|
||||
};
|
||||
|
||||
// A buffer class that is large enough to hold any Symbol-derived
|
||||
|
|
|
@ -103,6 +103,7 @@ uint32_t ImportSection::getNumImports() const {
|
|||
|
||||
void ImportSection::addGOTEntry(Symbol *sym) {
|
||||
assert(!isSealed);
|
||||
LLVM_DEBUG(dbgs() << "addGOTEntry: " << toString(*sym) << "\n");
|
||||
if (sym->hasGOTIndex())
|
||||
return;
|
||||
sym->setGOTIndex(numImportedGlobals++);
|
||||
|
@ -235,11 +236,26 @@ void MemorySection::writeBody() {
|
|||
writeUleb128(os, maxMemoryPages, "max pages");
|
||||
}
|
||||
|
||||
void GlobalSection::assignIndexes() {
|
||||
uint32_t globalIndex = out.importSec->getNumImportedGlobals();
|
||||
for (InputGlobal *g : inputGlobals)
|
||||
g->setGlobalIndex(globalIndex++);
|
||||
for (Symbol *sym : gotSymbols)
|
||||
sym->setGOTIndex(globalIndex++);
|
||||
}
|
||||
|
||||
void GlobalSection::addDummyGOTEntry(Symbol *sym) {
|
||||
LLVM_DEBUG(dbgs() << "addDummyGOTEntry: " << toString(*sym) << "\n");
|
||||
if (sym->hasGOTIndex())
|
||||
return;
|
||||
gotSymbols.push_back(sym);
|
||||
}
|
||||
|
||||
void GlobalSection::writeBody() {
|
||||
raw_ostream &os = bodyOutputStream;
|
||||
|
||||
writeUleb128(os, numGlobals(), "global count");
|
||||
for (const InputGlobal *g : inputGlobals)
|
||||
for (InputGlobal *g : inputGlobals)
|
||||
writeGlobal(os, g->global);
|
||||
for (const DefinedData *sym : definedFakeGlobals) {
|
||||
WasmGlobal global;
|
||||
|
@ -248,16 +264,22 @@ void GlobalSection::writeBody() {
|
|||
global.InitExpr.Value.Int32 = sym->getVirtualAddress();
|
||||
writeGlobal(os, global);
|
||||
}
|
||||
for (const Symbol *sym : gotSymbols) {
|
||||
WasmGlobal global;
|
||||
global.Type = {WASM_TYPE_I32, false};
|
||||
global.InitExpr.Opcode = WASM_OPCODE_I32_CONST;
|
||||
if (auto *d = dyn_cast<DefinedData>(sym))
|
||||
global.InitExpr.Value.Int32 = d->getVirtualAddress();
|
||||
else if (auto *f = cast<DefinedFunction>(sym))
|
||||
global.InitExpr.Value.Int32 = f->getTableIndex();
|
||||
writeGlobal(os, global);
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalSection::addGlobal(InputGlobal *global) {
|
||||
if (!global->live)
|
||||
return;
|
||||
uint32_t globalIndex =
|
||||
out.importSec->getNumImportedGlobals() + inputGlobals.size();
|
||||
LLVM_DEBUG(dbgs() << "addGlobal: " << globalIndex << "\n");
|
||||
global->setGlobalIndex(globalIndex);
|
||||
out.globalSec->inputGlobals.push_back(global);
|
||||
inputGlobals.push_back(global);
|
||||
}
|
||||
|
||||
void EventSection::writeBody() {
|
||||
|
|
|
@ -52,6 +52,8 @@ public:
|
|||
|
||||
virtual void writeBody() {}
|
||||
|
||||
virtual void assignIndexes() {}
|
||||
|
||||
void finalizeContents() override {
|
||||
writeBody();
|
||||
bodyOutputStream.flush();
|
||||
|
@ -173,14 +175,17 @@ class GlobalSection : public SyntheticSection {
|
|||
public:
|
||||
GlobalSection() : SyntheticSection(llvm::wasm::WASM_SEC_GLOBAL) {}
|
||||
uint32_t numGlobals() const {
|
||||
return inputGlobals.size() + definedFakeGlobals.size();
|
||||
return inputGlobals.size() + definedFakeGlobals.size() + gotSymbols.size();
|
||||
}
|
||||
bool isNeeded() const override { return numGlobals() > 0; }
|
||||
void assignIndexes() override;
|
||||
void writeBody() override;
|
||||
void addGlobal(InputGlobal *global);
|
||||
void addDummyGOTEntry(Symbol *sym);
|
||||
|
||||
std::vector<const DefinedData *> definedFakeGlobals;
|
||||
std::vector<InputGlobal *> inputGlobals;
|
||||
std::vector<Symbol *> gotSymbols;
|
||||
};
|
||||
|
||||
// The event section contains a list of declared wasm events associated with the
|
||||
|
|
|
@ -226,7 +226,9 @@ void Writer::layoutMemory() {
|
|||
}
|
||||
|
||||
if (WasmSym::globalBase)
|
||||
WasmSym::globalBase->setVirtualAddress(config->globalBase);
|
||||
WasmSym::globalBase->setVirtualAddress(memoryPtr);
|
||||
if (WasmSym::definedMemoryBase)
|
||||
WasmSym::definedMemoryBase->setVirtualAddress(memoryPtr);
|
||||
|
||||
uint32_t dataStart = memoryPtr;
|
||||
|
||||
|
@ -617,6 +619,8 @@ void Writer::assignIndexes() {
|
|||
for (InputEvent *event : file->events)
|
||||
out.eventSec->addEvent(event);
|
||||
}
|
||||
|
||||
out.globalSec->assignIndexes();
|
||||
}
|
||||
|
||||
static StringRef getOutputDataSegmentName(StringRef name) {
|
||||
|
@ -862,8 +866,11 @@ void Writer::run() {
|
|||
|
||||
// For PIC code the table base is assigned dynamically by the loader.
|
||||
// For non-PIC, we start at 1 so that accessing table index 0 always traps.
|
||||
if (!config->isPic)
|
||||
if (!config->isPic) {
|
||||
tableBase = 1;
|
||||
if (WasmSym::definedTableBase)
|
||||
WasmSym::definedTableBase->setVirtualAddress(tableBase);
|
||||
}
|
||||
|
||||
log("-- createOutputSegments");
|
||||
createOutputSegments();
|
||||
|
|
Loading…
Reference in New Issue