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 @@ -9,6 +9,9 @@ @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* @data, 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 @@ -36,7 +39,7 @@ ; CHECK: Sections: ; CHECK-NEXT: - Type: CUSTOM ; CHECK-NEXT: Name: dylink -; CHECK-NEXT: MemorySize: 12 +; CHECK-NEXT: MemorySize: 20 ; CHECK-NEXT: MemoryAlignment: 2 ; CHECK-NEXT: TableSize: 2 ; CHECK-NEXT: TableAlignment: 0 @@ -90,6 +93,12 @@ ; CHECK-NEXT: GlobalMutable: true ; CHECK-NEXT: - Type: FUNCTION +; CHECK: - Type: EXPORT +; CHECK-NEXT: Exports: +; 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 @@ -97,7 +106,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: 230141046A230241006A360200230141086A230241016A3602002301410C6A230141006A360200230141106A23033602000B ; check the data segment initialized with __memory_base global as offset @@ -108,4 +129,4 @@ ; CHECK-NEXT: Offset: ; CHECK-NEXT: Opcode: GLOBAL_GET ; CHECK-NEXT: Index: 1 -; CHECK-NEXT: Content: '020000000000000001000000' +; CHECK-NEXT: Content: '0200000000000000010000000000000000000000' diff --git a/lld/test/wasm/undefined-data.ll b/lld/test/wasm/undefined-data.ll --- a/lld/test/wasm/undefined-data.ll +++ b/lld/test/wasm/undefined-data.ll @@ -1,6 +1,6 @@ ; RUN: llc -filetype=obj %s -o %t.o ; RUN: not wasm-ld -o %t.wasm %t.o 2>&1 | FileCheck %s -check-prefix=UNDEF -; RUN: not wasm-ld --allow-undefined -o %t.wasm %t.o 2>&1 | FileCheck %s -check-prefix=BADRELOC +; RUN: not wasm-ld --shared -o %t.wasm %t.o 2>&1 | FileCheck %s -check-prefix=BADRELOC target triple = "wasm32-unknown-unknown" diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -278,10 +278,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(); @@ -298,7 +303,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); @@ -422,11 +427,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) { @@ -518,12 +533,6 @@ Config->ImportTable = true; } - if (Config->Shared) { - Config->ImportMemory = true; - Config->ExportDynamic = true; - Config->AllowUndefined = true; - } - // Handle --trace-symbol. for (auto *Arg : Args.filtered(OPT_trace_symbol)) Symtab->trace(Arg->getValue()); @@ -531,6 +540,12 @@ if (!Config->Relocatable) createSyntheticSymbols(); + if (Config->Shared) { + Config->ImportMemory = true; + Config->ExportDynamic = true; + Config->AllowUndefined = true; + } + createFiles(Args); if (errorCount()) return; @@ -547,15 +562,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 @@ -296,3 +296,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 { + uint32_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 (Sym->isUndefined()) { + // Undefined addresses are accessed via imported GOT globals + writeU8(OS, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); + writeUleb128(OS, Sym->getGOTIndex(), "global index"); + } else { + // Defined global data is accessed via known 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, 2, "align"); + writeUleb128(OS, 0, "offset"); + } +} diff --git a/lld/wasm/InputFiles.h b/lld/wasm/InputFiles.h --- a/lld/wasm/InputFiles.h +++ b/lld/wasm/InputFiles.h @@ -96,6 +96,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 @@ -406,6 +406,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 @@ -65,7 +65,9 @@ uint32_t lookupType(const WasmSignature &Sig); uint32_t registerType(const WasmSignature &Sig); - void createCtorFunction(); + void createApplyRelocationsFunction(); + void createCallCtorsFunction(); + void calculateInitFunctions(); void processRelocations(InputChunk *Chunk); void assignIndexes(); @@ -1149,17 +1151,6 @@ File->TypeMap[Reloc.Index] = registerType(Types[Reloc.Index]); File->TypeIsUsed[Reloc.Index] = true; break; - case R_WASM_MEMORY_ADDR_SLEB: - case R_WASM_MEMORY_ADDR_I32: - case R_WASM_MEMORY_ADDR_LEB: { - DataSymbol *Sym = File->getDataSymbol(Reloc.Index); - if (!Config->Relocatable && !isa(Sym) && !Sym->isWeak()) - error(File->getName() + ": relocation " + - relocTypeToString(Reloc.Type) + " cannot be used againt symbol " + - Sym->getName() + "; recompile with -fPIC"); - - break; - } case R_WASM_GLOBAL_INDEX_LEB: { auto* Sym = File->getSymbols()[Reloc.Index]; if (!isa(Sym) && !Sym->isInGOT()) { @@ -1168,7 +1159,24 @@ } } } + + if (Config->Pic) { + // Certain relocation types can't be used when building PIC output, since + // they would require absolute symbol addresses at link time. + switch (Reloc.Type) { + case R_WASM_TABLE_INDEX_SLEB: + case R_WASM_MEMORY_ADDR_SLEB: + case R_WASM_MEMORY_ADDR_LEB: { + Symbol *Sym = File->getSymbols()[Reloc.Index]; + error(File->getName() + ": relocation " + + relocTypeToString(Reloc.Type) + " cannot be used againt symbol " + + Sym->getName() + "; recompile with -fPIC"); + break; + } + } + } } + } void Writer::assignIndexes() { @@ -1272,12 +1280,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; @@ -1286,11 +1320,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 @@ -1348,12 +1387,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 @@ -241,11 +241,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 {