Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -65,3 +65,11 @@ ------------------ * Item 1. + +WebAssembly Improvements +------------------------ + +* Add initial support for creating shared libraries (-shared). + Note: The shared library format is still under active development and may + undergo significant changes in future versions. + See: https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md Index: docs/WebAssembly.rst =================================================================== --- docs/WebAssembly.rst +++ docs/WebAssembly.rst @@ -29,6 +29,8 @@ There are several key features that are not yet implement in the WebAssembly ports: +- Support for building shared libraries via ``-shared`` is still as work in + progress. - COMDAT support. This means that support for C++ is still very limited. - Function stripping. Currently there is no support for ``--gc-sections`` so functions and data from a given object will linked as a unit. Index: test/wasm/shared.ll =================================================================== --- /dev/null +++ test/wasm/shared.ll @@ -0,0 +1,70 @@ +; RUN: llc -O0 -filetype=obj %s -o %t.o +; RUN: wasm-ld -shared -o %t.wasm %t.o +; RUN: obj2yaml %t.wasm | FileCheck %s + +target triple = "wasm32-unknown-unknown" + +@used_data = hidden global i32 2, align 4 +@indirect_func = local_unnamed_addr global void ()* @foo, align 4 + +define default void @foo() { +entry: + %0 = load i32, i32* @used_data, align 4 + %1 = load void ()*, void ()** @indirect_func, align 4 + call void %1() + ret void +} + +; check for dylink section at start + +; CHECK: Sections: +; CHECK-NEXT: - Type: CUSTOM +; CHECK-NEXT: Name: dylink +; CHECK-NEXT: MemorySize: 4 +; CHECK-NEXT: MemoryAlignment: 2 +; CHECK-NEXT: TableSize: 1 +; CHECK-NEXT: TableAlignment: 0 + +; check for import of __table_base and __memory_base globals + +; CHECK: - Type: IMPORT +; CHECK-NEXT: Imports: +; CHECK-NEXT: - Module: env +; CHECK-NEXT: Field: __indirect_function_table +; CHECK-NEXT: Kind: TABLE +; CHECK-NEXT: Table: +; CHECK-NEXT: ElemType: ANYFUNC +; CHECK-NEXT: Limits: +; CHECK-NEXT: Flags: [ HAS_MAX ] +; CHECK-NEXT: Initial: 0x00000001 +; CHECK-NEXT: Maximum: 0x00000001 +; CHECK-NEXT: - Module: env +; CHECK-NEXT: Field: __table_base +; CHECK-NEXT: Kind: GLOBAL +; CHECK-NEXT: GlobalType: I32 +; CHECK-NEXT: GlobalMutable: false +; CHECK-NEXT: - Module: env +; CHECK-NEXT: Field: __memory_base +; CHECK-NEXT: Kind: GLOBAL +; CHECK-NEXT: GlobalType: I32 +; CHECK-NEXT: GlobalMutable: false + +; check for elem segment initialized with __table_base global as offset + +; CHECK: - Type: ELEM +; CHECK-NEXT: Segments: +; CHECK-NEXT: - Offset: +; CHECK-NEXT: Opcode: GET_GLOBAL +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: Functions: [ 1 ] + +; check the data segment initialized with __memory_base global as offset + +; CHECK: - Type: DATA +; CHECK-NEXT: Segments: +; CHECK-NEXT: - SectionOffset: 6 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: GET_GLOBAL +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: Content: '00000000' Index: wasm/Config.h =================================================================== --- wasm/Config.h +++ wasm/Config.h @@ -31,9 +31,11 @@ bool SharedMemory; bool ImportTable; bool MergeDataSegments; + bool Pie; bool PrintGcSections; bool Relocatable; bool SaveTemps; + bool Shared; bool StripAll; bool StripDebug; bool StackFirst; @@ -52,6 +54,9 @@ llvm::StringSet<> AllowUndefinedSymbols; std::vector SearchPaths; llvm::CachePruningPolicy ThinLTOCachePolicy; + + // True if we are creating position-independent code. + bool Pic; }; // The only instance of Configuration struct. Index: wasm/Driver.cpp =================================================================== --- wasm/Driver.cpp +++ wasm/Driver.cpp @@ -369,6 +369,7 @@ errorHandler().ErrorLimit = args::getInteger(Args, OPT_error_limit, 20); Config->AllowUndefined = Args.hasArg(OPT_allow_undefined); + 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"); @@ -391,13 +392,14 @@ Config->MergeDataSegments = Args.hasFlag(OPT_merge_data_segments, OPT_no_merge_data_segments, !Config->Relocatable); + Config->Pie = Args.hasFlag(OPT_pie, OPT_no_pie, false); Config->PrintGcSections = Args.hasFlag(OPT_print_gc_sections, OPT_no_print_gc_sections, false); Config->SaveTemps = Args.hasArg(OPT_save_temps); Config->SearchPaths = args::getStrings(Args, OPT_L); + Config->Shared = Args.hasArg(OPT_shared); Config->StripAll = Args.hasArg(OPT_strip_all); Config->StripDebug = Args.hasArg(OPT_strip_debug); - Config->CompressRelocations = Args.hasArg(OPT_compress_relocations); Config->StackFirst = Args.hasArg(OPT_stack_first); Config->ThinLTOCacheDir = Args.getLastArgValue(OPT_thinlto_cache_dir); Config->ThinLTOCachePolicy = CHECK( @@ -432,6 +434,9 @@ return; } + if (Config->Pie && Config->Shared) + error("-shared and -pie may not be used together"); + if (Config->OutputFile.empty()) error("no output file specified"); @@ -447,8 +452,21 @@ error("-r -and --compress-relocations may not be used together"); if (Args.hasArg(OPT_undefined)) error("-r -and --undefined may not be used together"); + if (Config->Pie) + error("-r and -pie may not be used together"); } + Config->Pic = Config->Pie || Config->Shared; + + if (Config->Pic) { + if (Config->ExportTable) + error("-shared/-pie is incompatible with --export-table"); + Config->ImportTable = true; + } + + if (Config->Shared) + Config->ExportDynamic = true; + Symbol *EntrySym = nullptr; if (!Config->Relocatable) { llvm::wasm::WasmGlobal Global; @@ -475,6 +493,28 @@ "__dso_handle", WASM_SYMBOL_VISIBILITY_HIDDEN); WasmSym::DataEnd = Symtab->addSyntheticDataSymbol("__data_end", 0); + if (Config->Pic) { + // For PIC code, we import two global variables (__memory_base and + // __table_base) from the environment and use these as the offset at + // which to load our static data and function table. + // See: https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md + static llvm::wasm::WasmGlobalType GlobalTypeI32 = {WASM_TYPE_I32, false}; + + WasmSym::MemoryBase = Symtab->addUndefinedGlobal( + "__memory_base", WASM_SYMBOL_VISIBILITY_HIDDEN, nullptr, + &GlobalTypeI32); + Config->AllowUndefinedSymbols.insert(WasmSym::MemoryBase->getName()); + WasmSym::MemoryBase->IsUsedInRegularObj = true; + WasmSym::MemoryBase->markLive(); + + WasmSym::TableBase = Symtab->addUndefinedGlobal( + "__table_base", WASM_SYMBOL_VISIBILITY_HIDDEN, nullptr, + &GlobalTypeI32); + Config->AllowUndefinedSymbols.insert(WasmSym::TableBase->getName()); + WasmSym::TableBase->IsUsedInRegularObj = true; + WasmSym::TableBase->markLive(); + } + // These two synthetic symbols exist purely for the embedder so we always // want to export them. WasmSym::HeapBase->ForceExport = true; @@ -511,7 +551,7 @@ // Add synthetic dummies for weak undefined functions. handleWeakUndefines(); - if (!Config->Entry.empty()) { + if (!Config->Shared && !Config->Entry.empty()) { EntrySym = handleUndefined(Config->Entry); if (EntrySym && EntrySym->isDefined()) EntrySym->ForceExport = true; Index: wasm/LTO.cpp =================================================================== --- wasm/LTO.cpp +++ wasm/LTO.cpp @@ -57,6 +57,13 @@ C.OptLevel = Config->LTOO; C.MAttrs = GetMAttrs(); + if (Config->Relocatable) + C.RelocModel = None; + else if (Config->Pic) + C.RelocModel = Reloc::PIC_; + else + C.RelocModel = Reloc::Static; + if (Config->SaveTemps) checkError(C.addSaveTemps(Config->OutputFile.str() + ".", /*UseInputModulePath*/ true)); Index: wasm/Options.td =================================================================== --- wasm/Options.td +++ wasm/Options.td @@ -17,7 +17,7 @@ def no_ # NAME: Flag<["--", "-"], "no-" # name>, HelpText; } -// The follow flags are shared with the ELF linker +// The following flags are shared with the ELF linker def color_diagnostics: F<"color-diagnostics">, HelpText<"Use colors in diagnostics">; @@ -75,12 +75,18 @@ def O: JoinedOrSeparate<["-"], "O">, HelpText<"Optimize output file size">; +defm pie: B<"pie", + "Create a position independent executable", + "Do not create a position independent executable (default)">; + defm print_gc_sections: B<"print-gc-sections", "List removed unused sections", "Do not list removed unused sections">; def relocatable: F<"relocatable">, HelpText<"Create relocatable object file">; +def shared: F<"shared">, HelpText<"Build a shared object">; + def strip_all: F<"strip-all">, HelpText<"Strip all symbols">; def strip_debug: F<"strip-debug">, HelpText<"Strip debugging information">; Index: wasm/OutputSections.cpp =================================================================== --- wasm/OutputSections.cpp +++ wasm/OutputSections.cpp @@ -136,9 +136,18 @@ for (OutputSegment *Segment : Segments) { raw_string_ostream OS(Segment->Header); writeUleb128(OS, 0, "memory index"); - writeUleb128(OS, WASM_OPCODE_I32_CONST, "opcode:i32const"); - writeSleb128(OS, Segment->StartVA, "memory offset"); - writeUleb128(OS, WASM_OPCODE_END, "opcode:end"); + WasmInitExpr InitExpr; + if (Config->Pic) { + assert(Segments.size() <= 1 && + "Currenly only a single data segment is supported in PIC mode"); + InitExpr.Opcode = WASM_OPCODE_GET_GLOBAL; + InitExpr.Value.Global = + cast(WasmSym::MemoryBase)->getGlobalIndex(); + } else { + InitExpr.Opcode = WASM_OPCODE_I32_CONST; + InitExpr.Value.Int32 = Segment->StartVA; + } + writeInitExpr(OS, InitExpr); writeUleb128(OS, Segment->Size, "segment size"); OS.flush(); Index: wasm/Symbols.h =================================================================== --- wasm/Symbols.h +++ wasm/Symbols.h @@ -310,6 +310,14 @@ // __dso_handle // Symbol used in calls to __cxa_atexit to determine current DLL static DefinedData *DsoHandle; + + // __table_base + // Used in PIC code for offset of indirect function table + static Symbol *TableBase; + + // __memory_base + // Used in PIC code for offset of global data + static Symbol *MemoryBase; }; // A buffer class that is large enough to hold any Symbol-derived Index: wasm/Symbols.cpp =================================================================== --- wasm/Symbols.cpp +++ wasm/Symbols.cpp @@ -28,6 +28,8 @@ DefinedData *WasmSym::DataEnd; DefinedData *WasmSym::HeapBase; DefinedGlobal *WasmSym::StackPointer; +Symbol *WasmSym::TableBase; +Symbol *WasmSym::MemoryBase; WasmSymbolType Symbol::getWasmType() const { if (isa(this)) Index: wasm/Writer.cpp =================================================================== --- wasm/Writer.cpp +++ wasm/Writer.cpp @@ -39,7 +39,6 @@ using namespace lld::wasm; static constexpr int kStackAlignment = 16; -static constexpr int kInitialTableOffset = 1; static constexpr const char *kFunctionTableName = "__indirect_function_table"; namespace { @@ -90,6 +89,7 @@ void createCustomSections(); // Custom sections + void createDylinkSection(); void createRelocSections(); void createLinkingSection(); void createNameSection(); @@ -98,8 +98,13 @@ void writeSections(); uint64_t FileSize = 0; + uint32_t TableBase = 0; uint32_t NumMemoryPages = 0; uint32_t MaxMemoryPages = 0; + // Memory size and aligment. Written to the "dylink" section + // when build with -shared or -pie. + uint32_t MemAlign = 0; + uint32_t MemSize = 0; std::vector Types; DenseMap TypeIndices; @@ -161,7 +166,7 @@ } if (Config->ImportTable) { - uint32_t TableSize = kInitialTableOffset + IndirectFunctions.size(); + uint32_t TableSize = TableBase + IndirectFunctions.size(); WasmImport Import; Import.Module = "env"; Import.Field = kFunctionTableName; @@ -259,7 +264,7 @@ // no address-taken function will fail at validation time since it is // a validation error to include a call_indirect instruction if there // is not table. - uint32_t TableSize = kInitialTableOffset + IndirectFunctions.size(); + uint32_t TableSize = TableBase + IndirectFunctions.size(); SyntheticSection *Section = createSyntheticSection(WASM_SEC_TABLE); raw_ostream &OS = Section->getStream(); @@ -325,12 +330,18 @@ writeUleb128(OS, 1, "segment count"); writeUleb128(OS, 0, "table index"); WasmInitExpr InitExpr; - InitExpr.Opcode = WASM_OPCODE_I32_CONST; - InitExpr.Value.Int32 = kInitialTableOffset; + if (Config->Pic) { + InitExpr.Opcode = WASM_OPCODE_GET_GLOBAL; + InitExpr.Value.Global = + cast(WasmSym::TableBase)->getGlobalIndex(); + } else { + InitExpr.Opcode = WASM_OPCODE_I32_CONST; + InitExpr.Value.Int32 = TableBase; + } writeInitExpr(OS, InitExpr); writeUleb128(OS, IndirectFunctions.size(), "elem count"); - uint32_t TableIndex = kInitialTableOffset; + uint32_t TableIndex = TableBase; for (const FunctionSymbol *Sym : IndirectFunctions) { assert(Sym->getTableIndex() == TableIndex); writeUleb128(OS, Sym->getFunctionIndex(), "function index"); @@ -425,6 +436,20 @@ raw_string_ostream OS{Body}; }; +// Create the custom "dylink" section containing information for the dynamic +// linker. +// See +// https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md +void Writer::createDylinkSection() { + SyntheticSection *Section = createSyntheticSection(WASM_SEC_CUSTOM, "dylink"); + raw_ostream &OS = Section->getStream(); + + writeUleb128(OS, MemSize, "MemSize"); + writeUleb128(OS, int(log2(MemAlign)), "MemAlign"); + writeUleb128(OS, IndirectFunctions.size(), "TableSize"); + writeUleb128(OS, 0, "TableAlign"); +} + // Create the custom "linking" section containing linker metadata. // This is only created when relocatable output is requested. void Writer::createLinkingSection() { @@ -599,7 +624,7 @@ uint32_t MemoryPtr = 0; auto PlaceStack = [&]() { - if (Config->Relocatable) + if (Config->Relocatable || Config->Shared) return; MemoryPtr = alignTo(MemoryPtr, kStackAlignment); if (Config->ZStackSize != alignTo(Config->ZStackSize, kStackAlignment)) @@ -625,7 +650,9 @@ if (WasmSym::DsoHandle) WasmSym::DsoHandle->setVirtualAddress(DataStart); + MemAlign = 0; for (OutputSegment *Seg : Segments) { + MemAlign = std::max(MemAlign, Seg->Alignment); MemoryPtr = alignTo(MemoryPtr, Seg->Alignment); Seg->StartVA = MemoryPtr; log(formatv("mem: {0,-15} offset={1,-8} size={2,-8} align={3}", Seg->Name, @@ -658,8 +685,8 @@ else MemoryPtr = Config->InitialMemory; } - uint32_t MemSize = alignTo(MemoryPtr, WasmPageSize); - NumMemoryPages = MemSize / WasmPageSize; + MemSize = MemoryPtr; + NumMemoryPages = alignTo(MemoryPtr, WasmPageSize) / WasmPageSize; log("mem: total pages = " + Twine(NumMemoryPages)); if (Config->MaxMemory != 0) { @@ -682,6 +709,8 @@ void Writer::createSections() { // Known sections + if (Config->Pic) + createDylinkSection(); createTypeSection(); createImportSection(); createFunctionSection(); @@ -874,7 +903,7 @@ AddDefinedFunction(Func); } - uint32_t TableIndex = kInitialTableOffset; + uint32_t TableIndex = TableBase; auto HandleRelocs = [&](InputChunk *Chunk) { if (!Chunk->Live) return; @@ -926,6 +955,10 @@ } static StringRef getOutputDataSegmentName(StringRef Name) { + // With PIC code we currently only support a single data segment since + // we only have a single __memory_base to use as our base address. + if (Config->Pic) + return "data"; if (!Config->MergeDataSegments) return Name; if (Name.startswith(".text.")) @@ -1010,9 +1043,14 @@ } void Writer::run() { - if (Config->Relocatable) + if (Config->Relocatable || Config->Pic) Config->GlobalBase = 0; + // 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->Pic) + TableBase = 1; + log("-- calculateImports"); calculateImports(); log("-- assignIndexes");