[WebAssembly] Initial support for shared objects (-shared)

Based on the initial spec proposal:
https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md

The llvm/codegen side of this is still missing but I believe this change is
still worth landing as an incremental step

Differential Revision: https://reviews.llvm.org/D54249

llvm-svn: 346918
This commit is contained in:
Sam Clegg 2018-11-15 00:37:21 +00:00
parent ae533d31af
commit bfb75348e2
11 changed files with 212 additions and 17 deletions

View File

@ -65,3 +65,11 @@ MachO Improvements
------------------
* 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

View File

@ -29,6 +29,8 @@ Missing features
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.

70
lld/test/wasm/shared.ll Normal file
View File

@ -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: __memory_base
; CHECK-NEXT: Kind: GLOBAL
; CHECK-NEXT: GlobalType: I32
; CHECK-NEXT: GlobalMutable: false
; CHECK-NEXT: - Module: env
; CHECK-NEXT: Field: __table_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: 1
; 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: 0
; CHECK-NEXT: Content: '00000000'

View File

@ -31,9 +31,11 @@ struct Configuration {
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 @@ struct Configuration {
llvm::StringSet<> AllowUndefinedSymbols;
std::vector<llvm::StringRef> SearchPaths;
llvm::CachePruningPolicy ThinLTOCachePolicy;
// True if we are creating position-independent code.
bool Pic;
};
// The only instance of Configuration struct.

View File

@ -369,6 +369,7 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
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 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
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 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
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 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
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 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
"__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 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
// 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;

View File

@ -57,6 +57,13 @@ static std::unique_ptr<lto::LTO> createLTO() {
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));

View File

@ -17,7 +17,7 @@ multiclass B<string name, string help1, string help2> {
def no_ # NAME: Flag<["--", "-"], "no-" # name>, HelpText<help2>;
}
// 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">, MetaVarName<"<path>">,
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">;

View File

@ -136,9 +136,18 @@ DataSection::DataSection(ArrayRef<OutputSegment *> Segments)
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<GlobalSymbol>(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();

View File

@ -28,6 +28,8 @@ DefinedData *WasmSym::DsoHandle;
DefinedData *WasmSym::DataEnd;
DefinedData *WasmSym::HeapBase;
DefinedGlobal *WasmSym::StackPointer;
Symbol *WasmSym::TableBase;
Symbol *WasmSym::MemoryBase;
WasmSymbolType Symbol::getWasmType() const {
if (isa<FunctionSymbol>(this))

View File

@ -310,6 +310,14 @@ struct WasmSym {
// __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

View File

@ -39,7 +39,6 @@ using namespace lld;
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 @@ private:
void createCustomSections();
// Custom sections
void createDylinkSection();
void createRelocSections();
void createLinkingSection();
void createNameSection();
@ -98,8 +98,13 @@ private:
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<const WasmSignature *> Types;
DenseMap<WasmSignature, int32_t> TypeIndices;
@ -161,7 +166,7 @@ void Writer::createImportSection() {
}
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 @@ void Writer::createTableSection() {
// 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 @@ void Writer::createElemSection() {
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<GlobalSymbol>(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 @@ public:
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 @@ void Writer::layoutMemory() {
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 @@ void Writer::layoutMemory() {
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 @@ void Writer::layoutMemory() {
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 @@ SyntheticSection *Writer::createSyntheticSection(uint32_t Type,
void Writer::createSections() {
// Known sections
if (Config->Pic)
createDylinkSection();
createTypeSection();
createImportSection();
createFunctionSection();
@ -874,7 +903,7 @@ void Writer::assignIndexes() {
AddDefinedFunction(Func);
}
uint32_t TableIndex = kInitialTableOffset;
uint32_t TableIndex = TableBase;
auto HandleRelocs = [&](InputChunk *Chunk) {
if (!Chunk->Live)
return;
@ -926,6 +955,10 @@ void Writer::assignIndexes() {
}
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::calculateInitFunctions() {
}
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");