diff --git a/lld/docs/WebAssembly.rst b/lld/docs/WebAssembly.rst --- a/lld/docs/WebAssembly.rst +++ b/lld/docs/WebAssembly.rst @@ -92,6 +92,21 @@ this is trivial. For direct function calls, the linker will generate a trapping stub function in place of the undefined function. + import-dynamic: + + Undefined symbols generate WebAssembly imports, including undefined data + symbols. This is somewhat similar to the --import-undefined option but + works all symbol types. This options puts limitations on the type of + relocations that are allowed for imported data symbols. Relocations that + require absolute data addresses (i.e. All R_WASM_MEMORY_ADDR_I32) will + generate an error if they cannot be resolved statically. For clang/llvm + this means inputs should be compiled with `-fPIC` (i.e. `pic` or + `dynamic-no-pic` relocation models). This options is useful for linking + binaries that are themselves static (non-relocatable) but whose undefined + symbols are resolved by a dynamic linker. Since the dynamic linking API is + experimental, this option currently requires `--experimental-pic` to also + be specified. + .. option:: --import-memory Import memory from the environment. diff --git a/lld/test/wasm/shared.s b/lld/test/wasm/shared.s --- a/lld/test/wasm/shared.s +++ b/lld/test/wasm/shared.s @@ -214,32 +214,32 @@ # DIS: <__wasm_apply_data_relocs>: # DIS-EMPTY: -# DIS-NEXT: global.get 1 # DIS-NEXT: i32.const 4 +# DIS-NEXT: global.get 1 # DIS-NEXT: i32.add # DIS-NEXT: global.get 4 # DIS-NEXT: i32.store 0 -# DIS-NEXT: global.get 1 # DIS-NEXT: i32.const 8 +# DIS-NEXT: global.get 1 # DIS-NEXT: i32.add # DIS-NEXT: global.get 2 # DIS-NEXT: i32.const 1 # DIS-NEXT: i32.add # DIS-NEXT: i32.store 0 -# DIS-NEXT: global.get 1 # DIS-NEXT: i32.const 12 +# DIS-NEXT: global.get 1 # DIS-NEXT: i32.add # DIS-NEXT: global.get 1 # DIS-NEXT: i32.const 0 # DIS-NEXT: i32.add # DIS-NEXT: i32.store 0 -# DIS-NEXT: global.get 1 # DIS-NEXT: i32.const 16 +# DIS-NEXT: global.get 1 # DIS-NEXT: i32.add # DIS-NEXT: global.get 5 # DIS-NEXT: i32.store 0 -# DIS-NEXT: global.get 1 # DIS-NEXT: i32.const 20 +# DIS-NEXT: global.get 1 # DIS-NEXT: i32.add # DIS-NEXT: global.get 6 # DIS-NEXT: i32.const 4 diff --git a/lld/test/wasm/shared64.s b/lld/test/wasm/shared64.s --- a/lld/test/wasm/shared64.s +++ b/lld/test/wasm/shared64.s @@ -221,32 +221,32 @@ # DIS: <__wasm_apply_data_relocs>: # DIS-EMPTY: -# DIS-NEXT: global.get 1 # DIS-NEXT: i64.const 4 +# DIS-NEXT: global.get 1 # DIS-NEXT: i64.add # DIS-NEXT: global.get 5 # DIS-NEXT: i64.store 0:p2align=2 -# DIS-NEXT: global.get 1 # DIS-NEXT: i64.const 12 +# DIS-NEXT: global.get 1 # DIS-NEXT: i64.add # DIS-NEXT: global.get 2 # DIS-NEXT: i64.const 1 # DIS-NEXT: i64.add # DIS-NEXT: i64.store 0:p2align=2 -# DIS-NEXT: global.get 1 # DIS-NEXT: i64.const 20 +# DIS-NEXT: global.get 1 # DIS-NEXT: i64.add # DIS-NEXT: global.get 1 # DIS-NEXT: i32.const 0 # DIS-NEXT: i32.add # DIS-NEXT: i32.store 0 -# DIS-NEXT: global.get 1 # DIS-NEXT: i64.const 24 +# DIS-NEXT: global.get 1 # DIS-NEXT: i64.add # DIS-NEXT: global.get 6 # DIS-NEXT: i64.store 0:p2align=2 -# DIS-NEXT: global.get 1 # DIS-NEXT: i64.const 32 +# DIS-NEXT: global.get 1 # DIS-NEXT: i64.add # DIS-NEXT: global.get 7 # DIS-NEXT: i32.const 4 diff --git a/lld/test/wasm/undefined-data.s b/lld/test/wasm/undefined-data.s --- a/lld/test/wasm/undefined-data.s +++ b/lld/test/wasm/undefined-data.s @@ -13,4 +13,4 @@ .size data_external, 4 # UNDEF: error: {{.*}}undefined-data.s.tmp.o: undefined symbol: data_external -# SHARED: error: {{.*}}undefined-data.s.tmp.o: relocation R_WASM_MEMORY_ADDR_LEB cannot be used against symbol data_external; recompile with -fPIC +# SHARED: error: {{.*}}undefined-data.s.tmp.o: relocation R_WASM_MEMORY_ADDR_LEB cannot be used against symbol `data_external`; recompile with -fPIC diff --git a/lld/test/wasm/unresolved-symbols-dynamic.s b/lld/test/wasm/unresolved-symbols-dynamic.s new file mode 100644 --- /dev/null +++ b/lld/test/wasm/unresolved-symbols-dynamic.s @@ -0,0 +1,84 @@ +# Unresolve data symbols are allowing under import-dynamic when GOT +# relocations are used +# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %s -o %t-dynamic.o +# RUN: wasm-ld %t-dynamic.o -o %t.wasm --unresolved-symbols=import-dynamic 2>&1 | FileCheck -check-prefix=WARN %s +# WARN: wasm-ld: warning: dynamic imports are not yet stable (--unresolved-symbols=import-dynamic) +# RUN: obj2yaml %t.wasm | FileCheck %s + +.functype undef () -> () + +.globl get_data_addr +get_data_addr: + .functype get_data_addr () -> (i32) + global.get undef_data@GOT + return + end_function + +.globl get_func_addr +get_func_addr: + .functype get_func_addr () -> (i32) + global.get undef@GOT + return + end_function + +.globl _start +_start: + .functype _start () -> () + call undef + call get_func_addr + drop + call get_data_addr + i32.load data_ptr + drop + end_function + +.section .data.data_ptr,"",@ +data_ptr: + .int32 data_external+42 + .size data_ptr, 4 + +.size data_external, 4 + +# CHECK: - Type: IMPORT +# CHECK-NEXT: Imports: +# CHECK-NEXT: - Module: env +# CHECK-NEXT: Field: undef +# CHECK-NEXT: Kind: FUNCTION +# CHECK-NEXT: SigIndex: 0 +# CHECK-NEXT: - Module: GOT.mem +# CHECK-NEXT: Field: undef_data +# CHECK-NEXT: Kind: GLOBAL +# CHECK-NEXT: GlobalType: I32 +# CHECK-NEXT: GlobalMutable: true +# CHECK-NEXT: - Module: GOT.func +# CHECK-NEXT: Field: undef +# CHECK-NEXT: Kind: GLOBAL +# CHECK-NEXT: GlobalType: I32 +# CHECK-NEXT: GlobalMutable: true + +# CHECK: - Type: CUSTOM +# CHECK-NEXT: Name: name +# CHECK-NEXT: FunctionNames: +# CHECK-NEXT: - Index: 0 +# CHECK-NEXT: Name: undef +# CHECK-NEXT: - Index: 1 +# CHECK-NEXT: Name: __wasm_apply_data_relocs +# CHECK-NEXT: - Index: 2 +# CHECK-NEXT: Name: get_data_addr +# CHECK-NEXT: - Index: 3 +# CHECK-NEXT: Name: get_func_addr +# CHECK-NEXT: - Index: 4 +# CHECK-NEXT: Name: _start +# CHECK-NEXT: GlobalNames: +# CHECK-NEXT: - Index: 0 +# CHECK-NEXT: Name: undef_data +# CHECK-NEXT: - Index: 1 +# CHECK-NEXT: Name: undef +# CHECK-NEXT: - Index: 2 +# CHECK-NEXT: Name: data_external +# CHECK-NEXT: - Index: 3 +# CHECK-NEXT: Name: __stack_pointer +# CHECK-NEXT: DataSegmentNames: +# CHECK-NEXT: - Index: 0 +# CHECK-NEXT: Name: .data +# CHECK-NEXT:... diff --git a/lld/test/wasm/unresolved-symbols.s b/lld/test/wasm/unresolved-symbols.s --- a/lld/test/wasm/unresolved-symbols.s +++ b/lld/test/wasm/unresolved-symbols.s @@ -79,6 +79,11 @@ .functype get_data_addr () -> (i32) .functype get_func_addr () -> (i32) +## import-dynamic should fail due to incompatible relocations. +# RUN: not wasm-ld %t1.o -o %t5.wasm --unresolved-symbols=import-dynamic 2>&1 | FileCheck -check-prefix=ERRNOPIC %s +# ERRNOPIC: relocation R_WASM_MEMORY_ADDR_SLEB cannot be used against symbol `undef_data`; recompile with -fPIC +# ERRNOPIC: relocation R_WASM_TABLE_INDEX_SLEB cannot be used against symbol `undef_func`; recompile with -fPIC + .globl _start _start: .functype _start () -> () diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h --- a/lld/wasm/Config.h +++ b/lld/wasm/Config.h @@ -18,7 +18,7 @@ namespace wasm { // For --unresolved-symbols. -enum class UnresolvedPolicy { ReportError, Warn, Ignore }; +enum class UnresolvedPolicy { ReportError, Warn, Ignore, ImportDynamic }; // This struct contains the global configuration for the linker. // Most fields are direct mapping from the command line options diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -335,6 +335,8 @@ StringRef s = arg->getValue(); if (s == "ignore-all") return UnresolvedPolicy::Ignore; + if (s == "import-dynamic") + return UnresolvedPolicy::ImportDynamic; if (s == "report-all") return errorOrWarn; error("unknown --unresolved-symbols value: " + s); @@ -528,6 +530,11 @@ if (config->pie) { warn("creating PIEs, with -pie, is not yet stable"); } + + if (config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic) { + warn("dynamic imports are not yet stable " + "(--unresolved-symbols=import-dynamic)"); + } } if (config->bsymbolic && !config->shared) { diff --git a/lld/wasm/InputChunks.cpp b/lld/wasm/InputChunks.cpp --- a/lld/wasm/InputChunks.cpp +++ b/lld/wasm/InputChunks.cpp @@ -376,19 +376,26 @@ for (const WasmRelocation &rel : relocations) { uint64_t offset = getVA(rel.Offset) - getInputSectionOffset(); + Symbol *sym = file->getSymbol(rel); + if (!config->isPic && sym->isDefined()) + continue; + LLVM_DEBUG(dbgs() << "gen reloc: type=" << relocTypeToString(rel.Type) << " addend=" << rel.Addend << " index=" << rel.Index << " output offset=" << offset << "\n"); - // Get __memory_base - writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); - writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "memory_base"); - - // Add the offset of the relocation + // Calculate the address at which to apply the relocations writeU8(os, opcode_ptr_const, "CONST"); writeSleb128(os, offset, "offset"); - writeU8(os, opcode_ptr_add, "ADD"); + // In PIC mode we need to add the __memory_base + if (config->isPic) { + writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); + writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "memory_base"); + writeU8(os, opcode_ptr_add, "ADD"); + } + + // Now figure out what we want to store at this location bool is64 = relocIs64(rel.Type); unsigned opcode_reloc_const = is64 ? WASM_OPCODE_I64_CONST : WASM_OPCODE_I32_CONST; @@ -397,8 +404,6 @@ unsigned opcode_reloc_store = is64 ? WASM_OPCODE_I64_STORE : WASM_OPCODE_I32_STORE; - Symbol *sym = file->getSymbol(rel); - // Now figure out what we want to store if (sym->hasGOTIndex()) { writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); writeUleb128(os, sym->getGOTIndex(), "global index"); @@ -408,6 +413,7 @@ writeU8(os, opcode_reloc_add, "ADD"); } } else { + assert(config->isPic); const GlobalSymbol* baseSymbol = WasmSym::memoryBase; if (rel.Type == R_WASM_TABLE_INDEX_I32 || rel.Type == R_WASM_TABLE_INDEX_I64) diff --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp --- a/lld/wasm/Relocations.cpp +++ b/lld/wasm/Relocations.cpp @@ -20,7 +20,8 @@ namespace wasm { static bool requiresGOTAccess(const Symbol *sym) { - if (!config->isPic) + if (!config->isPic && + config->unresolvedSymbols != UnresolvedPolicy::ImportDynamic) return false; if (sym->isHidden() || sym->isLocal()) return false; @@ -66,6 +67,8 @@ } } break; + case UnresolvedPolicy::ImportDynamic: + break; } } } @@ -139,7 +142,9 @@ break; } - if (config->isPic) { + if (config->isPic || + (sym->isUndefined() && + config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic)) { switch (reloc.Type) { case R_WASM_TABLE_INDEX_SLEB: case R_WASM_TABLE_INDEX_SLEB64: @@ -150,8 +155,8 @@ // Certain relocation types can't be used when building PIC output, // since they would require absolute symbol addresses at link time. error(toString(file) + ": relocation " + relocTypeToString(reloc.Type) + - " cannot be used against symbol " + toString(*sym) + - "; recompile with -fPIC"); + " cannot be used against symbol `" + toString(*sym) + + "`; recompile with -fPIC"); break; case R_WASM_TABLE_INDEX_I32: case R_WASM_TABLE_INDEX_I64: diff --git a/lld/wasm/SyntheticSections.h b/lld/wasm/SyntheticSections.h --- a/lld/wasm/SyntheticSections.h +++ b/lld/wasm/SyntheticSections.h @@ -75,7 +75,7 @@ class DylinkSection : public SyntheticSection { public: DylinkSection() : SyntheticSection(llvm::wasm::WASM_SEC_CUSTOM, "dylink.0") {} - bool isNeeded() const override { return config->isPic; } + bool isNeeded() const override; void writeBody() override; uint32_t memAlign = 0; diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp --- a/lld/wasm/SyntheticSections.cpp +++ b/lld/wasm/SyntheticSections.cpp @@ -54,6 +54,12 @@ } // namespace +bool DylinkSection::isNeeded() const { + return config->isPic || + config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic || + !symtab->sharedFiles.empty(); +} + void DylinkSection::writeBody() { raw_ostream &os = bodyOutputStream; diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -597,7 +597,8 @@ return false; } - if (config->isPic || config->relocatable || config->importUndefined) + if (config->isPic || config->relocatable || config->importUndefined || + config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic) return true; if (config->allowUndefinedSymbols.count(sym->getName()) != 0) return true; @@ -1004,21 +1005,22 @@ WasmSym::applyGlobalTLSRelocs->markLive(); } - if (config->isPic) { - // For PIC code we create synthetic functions that apply relocations. - // These get called from __wasm_call_ctors before the user-level - // constructors. + if (config->isPic || + config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic) { + // For PIC code, or when dynamically importing addresses, we create + // synthetic functions that apply relocations. These get called from + // __wasm_call_ctors before the user-level constructors. WasmSym::applyDataRelocs = symtab->addSyntheticFunction( "__wasm_apply_data_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN, make(nullSignature, "__wasm_apply_data_relocs")); WasmSym::applyDataRelocs->markLive(); + } - if (out.globalSec->needsRelocations()) { - WasmSym::applyGlobalRelocs = symtab->addSyntheticFunction( - "__wasm_apply_global_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN, - make(nullSignature, "__wasm_apply_global_relocs")); - WasmSym::applyGlobalRelocs->markLive(); - } + if (config->isPic && out.globalSec->needsRelocations()) { + WasmSym::applyGlobalRelocs = symtab->addSyntheticFunction( + "__wasm_apply_global_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN, + make(nullSignature, "__wasm_apply_global_relocs")); + WasmSym::applyGlobalRelocs->markLive(); } int startCount = 0;