diff --git a/lld/test/wasm/shared.ll b/lld/test/wasm/shared.ll --- a/lld/test/wasm/shared.ll +++ b/lld/test/wasm/shared.ll @@ -4,15 +4,18 @@ target triple = "wasm32-unknown-unknown" -@data = hidden global i32 2, align 4 +@data1 = hidden global i32 2, align 4 @indirect_func = local_unnamed_addr global i32 ()* @foo, align 4 @indirect_func_external = local_unnamed_addr global void ()* @func_external, align 4 +@data_addr = local_unnamed_addr global i32* @data1, align 4 +@data_addr_external = local_unnamed_addr global i32* @data_external, align 4 + define default i32 @foo() { entry: ; To ensure we use __stack_pointer %ptr = alloca i32 - %0 = load i32, i32* @data, align 4 + %0 = load i32, i32* @data1, align 4 %1 = load i32, i32* @data_external, align 4 %2 = load i32 ()*, i32 ()** @indirect_func, align 4 call i32 %2() @@ -29,7 +32,7 @@ ; CHECK: Sections: ; CHECK-NEXT: - Type: CUSTOM ; CHECK-NEXT: Name: dylink -; CHECK-NEXT: MemorySize: 8 +; CHECK-NEXT: MemorySize: 20 ; CHECK-NEXT: MemoryAlignment: 2 ; CHECK-NEXT: TableSize: 2 ; CHECK-NEXT: TableAlignment: 0 @@ -70,6 +73,28 @@ ; CHECK-NEXT: Kind: FUNCTION ; CHECK-NEXT: SigIndex: 1 +; CHECK: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: __wasm_call_ctors +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 + + +; check the contents of __wasm_call_ctors and __wasm_apply_relocs functions +; TODO(sbc): Disassembly and verify instructions. + +; CHECK: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Locals: [] +; CHECK-NEXT: Body: 10020B +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Locals: [] +; CHECK-NEXT: Body: 230141046A230241006A360002230141086A230241016A3600022301410C6A230141006A360002230141106A23033600020 + ; check for elem segment initialized with __table_base global as offset ; CHECK: - Type: ELEM @@ -77,7 +102,7 @@ ; CHECK-NEXT: - Offset: ; CHECK-NEXT: Opcode: GLOBAL_GET ; CHECK-NEXT: Index: 2 -; CHECK-NEXT: Functions: [ 1, 0 ] +; CHECK-NEXT: Functions: [ 3, 0 ] ; check the data segment initialized with __memory_base global as offset @@ -88,4 +113,4 @@ ; CHECK-NEXT: Offset: ; CHECK-NEXT: Opcode: GLOBAL_GET ; CHECK-NEXT: Index: 1 -; CHECK-NEXT: Content: '0000000001000000' +; CHECK-NEXT: Content: '0200000000000000010000000000000000000000' diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -412,11 +412,21 @@ static llvm::wasm::WasmGlobalType MutableGlobalTypeI32 = {WASM_TYPE_I32, true}; - if (!Config->Relocatable) + if (!Config->Relocatable) { WasmSym::CallCtors = Symtab->addSyntheticFunction( "__wasm_call_ctors", WASM_SYMBOL_VISIBILITY_HIDDEN, make(NullSignature, "__wasm_call_ctors")); + if (Config->Pic) { + WasmSym::ApplyRelocs = Symtab->addSyntheticFunction( + "__wasm_apply_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN, + make(NullSignature, "__wasm_apply_relocs")); + + WasmSym::CallCtors->ForceExport = true; + } + + } + // The __stack_pointer is imported in the shared library case, and exported // in the non-shared (executable) case. if (Config->Shared) { diff --git a/lld/wasm/InputChunks.h b/lld/wasm/InputChunks.h --- a/lld/wasm/InputChunks.h +++ b/lld/wasm/InputChunks.h @@ -92,6 +92,8 @@ static bool classof(const InputChunk *C) { return C->kind() == DataSegment; } + void writeRelocationFunction(raw_ostream &OS) const; + uint32_t getAlignment() const { return Segment.Data.Alignment; } StringRef getName() const override { return Segment.Data.Name; } StringRef getDebugName() const override { return StringRef(); } diff --git a/lld/wasm/InputChunks.cpp b/lld/wasm/InputChunks.cpp --- a/lld/wasm/InputChunks.cpp +++ b/lld/wasm/InputChunks.cpp @@ -289,3 +289,59 @@ memcpy(Buf, LastRelocEnd, ChunkSize); LLVM_DEBUG(dbgs() << " total: " << (Buf + ChunkSize - Orig) << "\n"); } + +void InputSegment::writeRelocationFunction(raw_ostream &OS) const { + int32_t SegmentVA = OutputSeg->StartVA + OutputSegmentOffset; + for (const WasmRelocation &Rel : Relocations) { + uint32_t Offset = Rel.Offset - getInputSectionOffset(); + uint32_t OutputVA = SegmentVA + Offset; + LLVM_DEBUG(dbgs() << "write reloc: type=" << reloctTypeToString(Rel.Type) + << " addend=" << Rel.Addend << " index=" << Rel.Index + << " offset=" << Rel.Offset << " vaddr=" << OutputVA + << "\n"); + + // Get __memory_base + writeU8(OS, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); + writeUleb128(OS, WasmSym::MemoryBase->getGlobalIndex(), "memory_base"); + + // Add the offset of the relocation + writeU8(OS, WASM_OPCODE_I32_CONST, "I32_CONST"); + writeSleb128(OS, OutputVA, "offset"); + writeU8(OS, WASM_OPCODE_I32_ADD, "ADD"); + + // Now figure out what we want to store + switch (Rel.Type) { + case R_WASM_TABLE_INDEX_I32: + // Add the table index to the __table_base + writeU8(OS, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); + writeUleb128(OS, WasmSym::TableBase->getGlobalIndex(), "table_base"); + writeU8(OS, WASM_OPCODE_I32_CONST, "CONST"); + writeSleb128(OS, File->calcNewValue(Rel), "new table index"); + writeU8(OS, WASM_OPCODE_I32_ADD, "ADD"); + break; + case R_WASM_MEMORY_ADDR_I32: { + Symbol* Sym = File->getSymbol(Rel); + if (auto* UD = dyn_cast(Sym)) { + // Undefined global addresses are accessed via imported globals + writeU8(OS, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); + writeUleb128(OS, UD->getGlobalIndex(), "global index"); + } else { + // Defined global data is accessed via offset from __memory_base + writeU8(OS, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); + writeUleb128(OS, WasmSym::MemoryBase->getGlobalIndex(), "memory_base"); + writeU8(OS, WASM_OPCODE_I32_CONST, "CONST"); + writeSleb128(OS, File->calcNewValue(Rel), "new memory offset"); + writeU8(OS, WASM_OPCODE_I32_ADD, "ADD"); + } + break; + } + default: + llvm_unreachable("unknown relocation type in data segment"); + } + + // Store that value at the virtual address + writeU8(OS, WASM_OPCODE_I32_STORE, "I32_STORE"); + writeUleb128(OS, 0, "offset"); + writeUleb128(OS, 2, "align"); + } +} diff --git a/lld/wasm/InputFiles.h b/lld/wasm/InputFiles.h --- a/lld/wasm/InputFiles.h +++ b/lld/wasm/InputFiles.h @@ -95,6 +95,9 @@ uint32_t calcNewValue(const WasmRelocation &Reloc) const; uint32_t calcNewAddend(const WasmRelocation &Reloc) const; uint32_t calcExpectedValue(const WasmRelocation &Reloc) const; + Symbol *getSymbol(const WasmRelocation &Reloc) const { + return Symbols[Reloc.Index]; + }; const WasmSection *CodeSection = nullptr; const WasmSection *DataSection = nullptr; diff --git a/lld/wasm/MarkLive.cpp b/lld/wasm/MarkLive.cpp --- a/lld/wasm/MarkLive.cpp +++ b/lld/wasm/MarkLive.cpp @@ -76,6 +76,11 @@ } } + if (Config->Pic) { + Enqueue(WasmSym::CallCtors); + Enqueue(WasmSym::ApplyRelocs); + } + // Follow relocations to mark all reachable chunks. while (!Q.empty()) { InputChunk *C = Q.pop_back_val(); diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h --- a/lld/wasm/Symbols.h +++ b/lld/wasm/Symbols.h @@ -410,6 +410,10 @@ // Function that directly calls all ctors in priority order. static DefinedFunction *CallCtors; + // __wasm_apply_relocs + // Function that applies relocations to data segment post-instantiation. + static DefinedFunction *ApplyRelocs; + // __dso_handle // Symbol used in calls to __cxa_atexit to determine current DLL static DefinedData *DsoHandle; diff --git a/lld/wasm/Symbols.cpp b/lld/wasm/Symbols.cpp --- a/lld/wasm/Symbols.cpp +++ b/lld/wasm/Symbols.cpp @@ -24,6 +24,7 @@ using namespace lld::wasm; DefinedFunction *WasmSym::CallCtors; +DefinedFunction *WasmSym::ApplyRelocs; DefinedData *WasmSym::DsoHandle; DefinedData *WasmSym::DataEnd; DefinedData *WasmSym::HeapBase; diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -63,7 +63,9 @@ uint32_t lookupType(const WasmSignature &Sig); uint32_t registerType(const WasmSignature &Sig); - void createCtorFunction(); + void createApplyRelocationsFunction(); + void createCallCtorsFunction(); + void calculateInitFunctions(); void assignIndexes(); void calculateImports(); @@ -1138,12 +1140,37 @@ } } -static const int OPCODE_CALL = 0x10; -static const int OPCODE_END = 0xb; +// For -fPIC we create an additional static constructor function that sets +// locations in memory according the relocation data. +void Writer::createApplyRelocationsFunction() { + LLVM_DEBUG(dbgs() << "createApplyRelocationsFunction\n"); + // First write the body's contents to a string. + std::string BodyContent; + { + raw_string_ostream OS(BodyContent); + writeUleb128(OS, 0, "num locals"); + for (const OutputSegment *Seg : Segments) + for (const InputSegment *InSeg : Seg->InputSegments) + InSeg->writeRelocationFunction(OS); + writeU8(OS, WASM_OPCODE_END, "END"); + } + + // Once we know the size of the body we can create the final function body + std::string FunctionBody; + { + raw_string_ostream OS(FunctionBody); + writeUleb128(OS, BodyContent.size(), "function size"); + OS << BodyContent; + } + + ArrayRef Body = arrayRefFromStringRef(Saver.save(FunctionBody)); + cast(WasmSym::ApplyRelocs->Function)->setBody(Body); +} + // Create synthetic "__wasm_call_ctors" function based on ctor functions // in input object. -void Writer::createCtorFunction() { +void Writer::createCallCtorsFunction() { if (!WasmSym::CallCtors->isLive()) return; @@ -1152,11 +1179,15 @@ { raw_string_ostream OS(BodyContent); writeUleb128(OS, 0, "num locals"); + if (Config->Pic) { + writeU8(OS, WASM_OPCODE_CALL, "CALL"); + writeUleb128(OS, WasmSym::ApplyRelocs->getFunctionIndex(), "function index"); + } for (const WasmInitEntry &F : InitFunctions) { - writeU8(OS, OPCODE_CALL, "CALL"); + writeU8(OS, WASM_OPCODE_CALL, "CALL"); writeUleb128(OS, F.Sym->getFunctionIndex(), "function index"); } - writeU8(OS, OPCODE_END, "END"); + writeU8(OS, WASM_OPCODE_END, "END"); } // Once we know the size of the body we can create the final function body @@ -1212,12 +1243,15 @@ assignIndexes(); log("-- calculateInitFunctions"); calculateInitFunctions(); - if (!Config->Relocatable) - createCtorFunction(); log("-- calculateTypes"); calculateTypes(); log("-- layoutMemory"); layoutMemory(); + if (!Config->Relocatable) { + if (Config->Pic) + createApplyRelocationsFunction(); + createCallCtorsFunction(); + } log("-- calculateExports"); calculateExports(); log("-- calculateCustomSections"); diff --git a/llvm/include/llvm/BinaryFormat/Wasm.h b/llvm/include/llvm/BinaryFormat/Wasm.h --- a/llvm/include/llvm/BinaryFormat/Wasm.h +++ b/llvm/include/llvm/BinaryFormat/Wasm.h @@ -236,11 +236,14 @@ // Opcodes used in initializer expressions. enum : unsigned { WASM_OPCODE_END = 0x0b, + WASM_OPCODE_CALL = 0x10, WASM_OPCODE_GLOBAL_GET = 0x23, + WASM_OPCODE_I32_STORE = 0x36, WASM_OPCODE_I32_CONST = 0x41, WASM_OPCODE_I64_CONST = 0x42, WASM_OPCODE_F32_CONST = 0x43, WASM_OPCODE_F64_CONST = 0x44, + WASM_OPCODE_I32_ADD = 0x6a, }; enum : unsigned {