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,15 @@ ; 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 for elem segment initialized with __table_base global as offset ; CHECK: - Type: ELEM @@ -77,7 +89,19 @@ ; CHECK-NEXT: - Offset: ; CHECK-NEXT: Opcode: GLOBAL_GET ; CHECK-NEXT: Index: 2 -; CHECK-NEXT: Functions: [ 1, 0 ] +; CHECK-NEXT: Functions: [ 3, 0 ] + +; check the generated code in __wasm_call_ctors and __wasm_apply_relocs functions +; TODO(sbc): Disassemble 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 the data segment initialized with __memory_base global as offset @@ -88,4 +112,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 @@ -277,10 +277,15 @@ } } -static StringRef getEntry(opt::InputArgList &Args, StringRef Default) { +static StringRef getEntry(opt::InputArgList &Args) { auto *Arg = Args.getLastArg(OPT_entry, OPT_no_entry); - if (!Arg) - return Default; + if (!Arg) { + if (Args.hasArg(OPT_relocatable)) + return ""; + if (Args.hasArg(OPT_shared)) + return "__wasm_call_ctors"; + return "_start"; + } if (Arg->getOption().getID() == OPT_no_entry) return ""; return Arg->getValue(); @@ -295,7 +300,7 @@ Config->CompressRelocations = Args.hasArg(OPT_compress_relocations); Config->Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, true); Config->DisableVerify = Args.hasArg(OPT_disable_verify); - Config->Entry = getEntry(Args, Args.hasArg(OPT_relocatable) ? "" : "_start"); + Config->Entry = getEntry(Args); Config->ExportAll = Args.hasArg(OPT_export_all); Config->ExportDynamic = Args.hasFlag(OPT_export_dynamic, OPT_no_export_dynamic, false); @@ -412,11 +417,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) { + // For PIC code we create a synthetic function call __wasm_apply_relocs + // and add this as the first call in __wasm_call_ctors. + // We also unconditionally export + WasmSym::ApplyRelocs = Symtab->addSyntheticFunction( + "__wasm_apply_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN, + make(NullSignature, "__wasm_apply_relocs")); + } + } + // The __stack_pointer is imported in the shared library case, and exported // in the non-shared (executable) case. if (Config->Shared) { @@ -508,11 +523,6 @@ Config->ImportTable = true; } - if (Config->Shared) { - Config->ExportDynamic = true; - Config->AllowUndefined = true; - } - // Handle --trace-symbol. for (auto *Arg : Args.filtered(OPT_trace_symbol)) Symtab->trace(Arg->getValue()); @@ -520,6 +530,11 @@ if (!Config->Relocatable) createSyntheticSymbols(); + if (Config->Shared) { + Config->ExportDynamic = true; + Config->AllowUndefined = true; + } + createFiles(Args); if (errorCount()) return; @@ -536,15 +551,13 @@ handleUndefined(Arg->getValue()); Symbol *EntrySym = nullptr; - if (!Config->Relocatable) { - if (!Config->Shared && !Config->Entry.empty()) { - EntrySym = handleUndefined(Config->Entry); - if (EntrySym && EntrySym->isDefined()) - EntrySym->ForceExport = true; - else - error("entry symbol not defined (pass --no-entry to supress): " + - Config->Entry); - } + if (!Config->Relocatable && !Config->Entry.empty()) { + EntrySym = handleUndefined(Config->Entry); + if (EntrySym && EntrySym->isDefined()) + EntrySym->ForceExport = true; + else + error("entry symbol not defined (pass --no-entry to supress): " + + Config->Entry); } if (errorCount()) 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 generateRelocationCode(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,58 @@ memcpy(Buf, LastRelocEnd, ChunkSize); LLVM_DEBUG(dbgs() << " total: " << (Buf + ChunkSize - Orig) << "\n"); } + +// Generate code to apply relocations to the data section at runtime. +// This is only called when generating shared libaries (PIC) where address are +// not known at static link time. +void InputSegment::generateRelocationCode(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; + + // 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("unexpected 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,38 @@ } } -static const int OPCODE_CALL = 0x10; -static const int OPCODE_END = 0xb; +// For -shared (PIC) output, we create create a synthetic function which will +// apply any relocations to the data segments on startup. This function is +// called __wasm_apply_relocs and is added at the very beginning of +// __wasm_call_ctors before any of the constructors run. +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->generateRelocationCode(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 +1180,16 @@ { 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 +1245,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 {