[lld][WebAssembly] Add --unresolved-symbols=import-dynamic

This is a new mode for handling unresolved symbols that allows all
symbols to be imported in the same that they would be in the case of
`-fpie` or `-shared`, but generting an otherwise fixed/non-relocatable
binary.

Code linked in this way should still be compiled with `-fPIC` so that
data symbols can be resolved via imports.

This essentially allows the building of static binaries that have
dynamic imports.  See:
https://github.com/emscripten-core/emscripten/issues/12682

As with other uses of the experimental dynamic linking ABI, this
behaviour will produce a warning unless run with `--experimental-pic`.

Differential Revision: https://reviews.llvm.org/D91577
This commit is contained in:
Sam Clegg 2020-11-16 10:11:37 -08:00
parent 7783de7fe3
commit 86c90f9bfd
13 changed files with 166 additions and 36 deletions

View file

@ -92,6 +92,21 @@ WebAssembly-specific options:
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.

View file

@ -214,32 +214,32 @@ get_local_func_address:
# 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

View file

@ -221,32 +221,32 @@ get_local_func_address:
# 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

View file

@ -13,4 +13,4 @@ _start:
.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

View file

@ -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:...

View file

@ -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 () -> ()

View file

@ -18,7 +18,7 @@ namespace lld {
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

View file

@ -335,6 +335,8 @@ static UnresolvedPolicy getUnresolvedSymbolPolicy(opt::InputArgList &args) {
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 @@ static void checkOptions(opt::InputArgList &args) {
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) {

View file

@ -376,19 +376,26 @@ void InputChunk::generateRelocationCode(raw_ostream &os) const {
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 @@ void InputChunk::generateRelocationCode(raw_ostream &os) const {
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 @@ void InputChunk::generateRelocationCode(raw_ostream &os) const {
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)

View file

@ -20,7 +20,8 @@ namespace lld {
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 @@ static void reportUndefined(Symbol *sym) {
}
}
break;
case UnresolvedPolicy::ImportDynamic:
break;
}
}
}
@ -139,7 +142,9 @@ void scanRelocations(InputChunk *chunk) {
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 @@ void scanRelocations(InputChunk *chunk) {
// 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:

View file

@ -54,6 +54,12 @@ public:
} // namespace
bool DylinkSection::isNeeded() const {
return config->isPic ||
config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic ||
!symtab->sharedFiles.empty();
}
void DylinkSection::writeBody() {
raw_ostream &os = bodyOutputStream;

View file

@ -75,7 +75,7 @@ protected:
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;

View file

@ -597,7 +597,8 @@ static bool shouldImport(Symbol *sym) {
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 @@ void Writer::createSyntheticInitFunctions() {
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<SyntheticFunction>(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<SyntheticFunction>(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<SyntheticFunction>(nullSignature, "__wasm_apply_global_relocs"));
WasmSym::applyGlobalRelocs->markLive();
}
int startCount = 0;