[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:
parent
ae533d31af
commit
bfb75348e2
@ -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
|
||||
|
||||
@ -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
70
lld/test/wasm/shared.ll
Normal 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'
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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">;
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user