diff --git a/lld/docs/WebAssembly.rst b/lld/docs/WebAssembly.rst --- a/lld/docs/WebAssembly.rst +++ b/lld/docs/WebAssembly.rst @@ -97,6 +97,18 @@ symbols are resolved to zero as in ``ignore-all``. This corresponds to the legacy ``--allow-undefined`` flag. + import-dynamic: + + Like import-functions but also import the addresses of unresolved data + symbols. This puts limitations on the type of relocations that are allowed + for imported data symbols. Any 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 is useful for linking binaries are themselves static but are compiled + with either `pic` or `dynamic-no-pic` reocation models, allowing undefined + symbols to be resolved dynamically. + .. option:: --import-memory Import memory from the environment. diff --git a/lld/test/wasm/shared.ll b/lld/test/wasm/shared.ll --- a/lld/test/wasm/shared.ll +++ b/lld/test/wasm/shared.ll @@ -1,5 +1,5 @@ ; RUN: llc -relocation-model=pic -mattr=+mutable-globals -filetype=obj %s -o %t.o -; RUN: wasm-ld -shared -o %t.wasm %t.o +; RUN: wasm-ld -shared --experimental-pic -o %t.wasm %t.o ; RUN: obj2yaml %t.wasm | FileCheck %s target triple = "wasm32-unknown-emscripten" @@ -142,7 +142,7 @@ ; CHECK-NEXT: Body: 10020B ; CHECK-NEXT: - Index: 2 ; CHECK-NEXT: Locals: [] -; CHECK-NEXT: Body: 230141046A230241016A360200230141086A23043602002301410C6A230141006A360200230141106A2305360200230141146A230641046A3602000B +; CHECK-NEXT: Body: 410423016A230241016A360200410823016A2304360200410C23016A230141006A360200411023016A2305360200411423016A230641046A3602000B ; check the data segment initialized with __memory_base global as offset 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 @@ -12,4 +12,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,78 @@ +## import-dynamic should succeed if that input file contains GOT +# relocations for external sybmols. +# 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 + +.globl _start +_start: + .functype _start () -> () + call undef + call get_data_addr + call get_func_addr + i32.load data_ptr + end_function + +.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 + +.section .data.data_ptr,"",@ +data_ptr: + .int32 data_external+42 + .size data_ptr, 4 + +.size data_external, 4 +.functype undef () -> () + +# 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_relocs +# CHECK-NEXT: - Index: 2 +# CHECK-NEXT: Name: _start +# CHECK-NEXT: - Index: 3 +# CHECK-NEXT: Name: get_data_addr +# CHECK-NEXT: - Index: 4 +# CHECK-NEXT: Name: get_func_addr +# 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:... 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 @@ -69,6 +69,11 @@ # RUN: wasm-ld -r %t1.o -o %t4.wasm --unresolved-symbols=report-all # RUN: llvm-readobj %t4.wasm > /dev/null 2>&1 +## 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`; 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 @@ -21,7 +21,13 @@ // The `ImportFuncs` mode is an additional mode that corresponds to the // --allow-undefined flag which turns undefined functions in imports // as opposed ed to Ignore or Warn which turn them into unreachables. -enum class UnresolvedPolicy { ReportError, Warn, Ignore, ImportFuncs }; +enum class UnresolvedPolicy { + ReportError, + Warn, + Ignore, + ImportFuncs, + 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 @@ -345,6 +345,8 @@ return UnresolvedPolicy::Ignore; if (s == "import-functions") return UnresolvedPolicy::ImportFuncs; + if (s == "import-dynamic") + return UnresolvedPolicy::ImportDynamic; if (s == "report-all") return errorOrWarn; error("unknown --unresolved-symbols value: " + s); @@ -463,12 +465,11 @@ if (config->exportTable) error("-shared/-pie is incompatible with --export-table"); config->importTable = true; + config->unresolvedSymbols = UnresolvedPolicy::ImportDynamic; } - if (config->shared) { + if (config->shared) config->importMemory = true; - config->unresolvedSymbols = UnresolvedPolicy::ImportFuncs; - } } // Some command line options or some combinations of them are not allowed. @@ -516,14 +517,16 @@ // Also, warn about flags which request explicit exports. if (!config->experimentalPic) { // -shared will change meaning when Module Linking is implemented. - if (config->shared) { + if (config->shared) warn("creating shared libraries, with -shared, is not yet stable"); - } // -pie will change meaning when Module Linking is implemented. - if (config->pie) { + 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) { @@ -612,9 +615,11 @@ "__wasm_call_ctors", WASM_SYMBOL_VISIBILITY_HIDDEN, make(nullSignature, "__wasm_call_ctors")); - if (config->isPic) { - // For PIC code we create a synthetic function __wasm_apply_relocs which - // is 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 a + // synthetic function __wasm_apply_relocs which is called from + // __wasm_call_ctors before the user-level constructors. WasmSym::applyRelocs = symtab->addSyntheticFunction( "__wasm_apply_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN, make(nullSignature, "__wasm_apply_relocs")); diff --git a/lld/wasm/InputChunks.cpp b/lld/wasm/InputChunks.cpp --- a/lld/wasm/InputChunks.cpp +++ b/lld/wasm/InputChunks.cpp @@ -366,19 +366,26 @@ uint64_t offset = rel.Offset - getInputSectionOffset(); uint64_t outputOffset = segmentVA + offset; + 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=" << outputOffset << "\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, outputOffset, "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; @@ -387,8 +394,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"); @@ -398,6 +403,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/MarkLive.cpp b/lld/wasm/MarkLive.cpp --- a/lld/wasm/MarkLive.cpp +++ b/lld/wasm/MarkLive.cpp @@ -101,7 +101,7 @@ enqueue(callDtors); // In Emscripten-style PIC, `__wasm_call_ctors` calls `__wasm_apply_relocs`. - if (config->isPic) + if (WasmSym::applyRelocs) enqueue(WasmSym::applyRelocs); if (config->sharedMemory && !config->shared) 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; @@ -69,6 +70,7 @@ } break; case UnresolvedPolicy::ImportFuncs: + case UnresolvedPolicy::ImportDynamic: break; } } @@ -125,7 +127,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: @@ -136,8 +140,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/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -546,7 +546,8 @@ return false; if (config->relocatable || - config->unresolvedSymbols == UnresolvedPolicy::ImportFuncs) + config->unresolvedSymbols == UnresolvedPolicy::ImportFuncs || + config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic) return true; if (config->allowUndefinedSymbols.count(sym->getName()) != 0) return true; @@ -994,9 +995,11 @@ raw_string_ostream os(bodyContent); writeUleb128(os, 0, "num locals"); - // First apply relocations to any internalized GOT entries. These - // are the result of relaxation when building with -Bsymbolic. - out.globalSec->generateRelocationCode(os); + if (config->isPic) { + // First apply relocations to any internalized GOT entries. These + // are the result of relaxation when building with -Bsymbolic. + out.globalSec->generateRelocationCode(os); + } // Next apply any realocation to the data section by reading GOT entry // globals. @@ -1016,7 +1019,8 @@ // If __wasm_call_ctors isn't referenced, there aren't any ctors, and we // aren't calling `__wasm_apply_relocs` for Emscripten-style PIC, don't // define the `__wasm_call_ctors` function. - if (!WasmSym::callCtors->isLive() && initFunctions.empty() && !config->isPic) + if (!WasmSym::callCtors->isLive() && initFunctions.empty() && + !WasmSym::applyRelocs) return; // First write the body's contents to a string. @@ -1025,7 +1029,7 @@ raw_string_ostream os(bodyContent); writeUleb128(os, 0, "num locals"); - if (config->isPic) { + if (WasmSym::applyRelocs) { writeU8(os, WASM_OPCODE_CALL, "CALL"); writeUleb128(os, WasmSym::applyRelocs->getFunctionIndex(), "function index"); @@ -1215,9 +1219,9 @@ if (!config->relocatable) { // Create linker synthesized functions - if (config->isPic) + if (WasmSym::applyRelocs) createApplyRelocationsFunction(); - else if (config->sharedMemory) + if (!config->isPic && config->sharedMemory) createInitMemoryFunction(); createCallCtorsFunction();