diff --git a/lld/test/wasm/pic-static.ll b/lld/test/wasm/pic-static.ll new file mode 100644 --- /dev/null +++ b/lld/test/wasm/pic-static.ll @@ -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 diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -516,11 +516,8 @@ auto *stackPointer = make(global, nullptr); stackPointer->live = true; // For non-PIC code - // TODO(sbc): Remove WASM_SYMBOL_VISIBILITY_HIDDEN when the mutable global - // spec proposal is implemented in all major browsers. - // See: https://github.com/WebAssembly/mutable-global - WasmSym::stackPointer = symtab->addSyntheticGlobal( - "__stack_pointer", WASM_SYMBOL_VISIBILITY_HIDDEN, stackPointer); + WasmSym::stackPointer = createGlobalVariable("__stack_pointer", true, 0); + WasmSym::stackPointer->markLive(); } if (config->sharedMemory && !config->shared) { @@ -541,6 +538,8 @@ WasmSym::dataEnd = symtab->addOptionalDataSymbol("__data_end"); WasmSym::globalBase = symtab->addOptionalDataSymbol("__global_base"); WasmSym::heapBase = symtab->addOptionalDataSymbol("__heap_base"); + WasmSym::definedMemoryBase = symtab->addOptionalDataSymbol("__memory_base"); + WasmSym::definedTableBase = symtab->addOptionalDataSymbol("__table_base"); } } diff --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp --- a/lld/wasm/Relocations.cpp +++ b/lld/wasm/Relocations.cpp @@ -40,6 +40,20 @@ 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 @@ break; case R_WASM_GLOBAL_INDEX_LEB: if (!isa(sym)) - out.importSec->addGOTEntry(sym); + addGOTEntry(sym); break; } @@ -88,7 +102,7 @@ // 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 { diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h --- a/lld/wasm/Symbols.h +++ b/lld/wasm/Symbols.h @@ -472,10 +472,12 @@ // __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 diff --git a/lld/wasm/Symbols.cpp b/lld/wasm/Symbols.cpp --- a/lld/wasm/Symbols.cpp +++ b/lld/wasm/Symbols.cpp @@ -37,7 +37,9 @@ GlobalSymbol *WasmSym::tlsSize; GlobalSymbol *WasmSym::tlsAlign; UndefinedGlobal *WasmSym::tableBase; +DefinedData *WasmSym::definedTableBase; UndefinedGlobal *WasmSym::memoryBase; +DefinedData *WasmSym::definedMemoryBase; WasmSymbolType Symbol::getWasmType() const { if (isa(this)) @@ -111,9 +113,11 @@ void Symbol::setGOTIndex(uint32_t index) { LLVM_DEBUG(dbgs() << "setGOTIndex " << name << " -> " << index << "\n"); assert(gotIndex == INVALID_INDEX); - // 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; + 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; } diff --git a/lld/wasm/SyntheticSections.h b/lld/wasm/SyntheticSections.h --- a/lld/wasm/SyntheticSections.h +++ b/lld/wasm/SyntheticSections.h @@ -52,6 +52,8 @@ virtual void writeBody() {} + virtual void assignIndexes() {} + void finalizeContents() override { writeBody(); bodyOutputStream.flush(); @@ -173,14 +175,17 @@ 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 definedFakeGlobals; std::vector inputGlobals; + std::vector gotSymbols; }; // The event section contains a list of declared wasm events associated with the diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp --- a/lld/wasm/SyntheticSections.cpp +++ b/lld/wasm/SyntheticSections.cpp @@ -103,6 +103,7 @@ 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 @@ 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 @@ 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(sym)) + global.InitExpr.Value.Int32 = d->getVirtualAddress(); + else if (auto* f = cast(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() { diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -226,7 +226,9 @@ } 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 @@ for (InputEvent *event : file->events) out.eventSec->addEvent(event); } + + out.globalSec->assignIndexes(); } static StringRef getOutputDataSegmentName(StringRef name) { @@ -862,8 +866,12 @@ // 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();