Skip to content

Commit

Permalink
[WebAssembly] Initial support for shared objects (-shared)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sbc100 committed Nov 15, 2018
1 parent ae533d3 commit bfb7534
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 17 deletions.
8 changes: 8 additions & 0 deletions lld/docs/ReleaseNotes.rst
Expand Up @@ -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
2 changes: 2 additions & 0 deletions lld/docs/WebAssembly.rst
Expand Up @@ -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.
Expand Down
70 changes: 70 additions & 0 deletions lld/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: __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'
5 changes: 5 additions & 0 deletions lld/wasm/Config.h
Expand Up @@ -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;
Expand All @@ -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.
Expand Down
44 changes: 42 additions & 2 deletions lld/wasm/Driver.cpp
Expand Up @@ -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");
Expand All @@ -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(
Expand Down Expand Up @@ -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");

Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions lld/wasm/LTO.cpp
Expand Up @@ -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));
Expand Down
8 changes: 7 additions & 1 deletion lld/wasm/Options.td
Expand Up @@ -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">;

Expand Down Expand Up @@ -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">;
Expand Down
15 changes: 12 additions & 3 deletions lld/wasm/OutputSections.cpp
Expand Up @@ -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();

Expand Down
2 changes: 2 additions & 0 deletions lld/wasm/Symbols.cpp
Expand Up @@ -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))
Expand Down
8 changes: 8 additions & 0 deletions lld/wasm/Symbols.h
Expand Up @@ -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
Expand Down

0 comments on commit bfb7534

Please sign in to comment.