[WebAssembly] Refactor handling of weak undefined functions. NFC.

Also add to the docs.

This is refactor in preparation for https://reviews.llvm.org/D57909

Differential Revision: https://reviews.llvm.org/D57920

llvm-svn: 353478
This commit is contained in:
Sam Clegg 2019-02-07 22:42:16 +00:00
parent 81f859d169
commit 230dc11d24
9 changed files with 85 additions and 57 deletions

View File

@ -124,6 +124,18 @@ The symbols which are preserved by default are:
- Any symbol which is to be exported.
- Any symbol transitively referenced by the above.
Weak Undefined Functions
~~~~~~~~~~~~~~~~~~~~~~~~
On native platforms, calls to weak undefined functions end up as calls to the
null function pointer. With WebAssembly, direct calls must reference a defined
function (with the correct signature). In order to handle this case the linker
will generate function a stub containing only the ``unreachable`` instruction
and use this for any direct references to an undefined weak function.
For example a runtime call to a weak undefined function ``foo`` will up trapping
on ``unreachable`` inside and linker-generated function called
``undefined:foo``.
Missing features
----------------

View File

@ -15,5 +15,5 @@ entry:
ret void
}
; CHECK: Name: undefined function ret32
; CHECK: Name: 'undefined:ret32'
; CHECK-NOT: Name: ret32

View File

@ -58,8 +58,8 @@ define void @_start() {
; CHECK-NEXT: - Index: 0
; CHECK-NEXT: Name: __wasm_call_ctors
; CHECK-NEXT: - Index: 1
; DEMANGLE-NEXT: Name: 'undefined function bar(int)'
; MANGLE-NEXT: Name: undefined function _Z3bari
; DEMANGLE-NEXT: Name: 'undefined:bar(int)'
; MANGLE-NEXT: Name: 'undefined:_Z3bari'
; CHECK-NEXT: - Index: 2
; DEMANGLE-NEXT: Name: 'foo(int)'
; MANGLE-NEXT: Name: _Z3fooi

View File

@ -17,4 +17,4 @@ entry:
ret void
}
; CHECK: Name: undefined function foo
; CHECK: Name: 'undefined:foo'

View File

@ -110,11 +110,11 @@ define i32 @callWeakFuncs() {
; CHECK-NEXT: - Index: 0
; CHECK-NEXT: Name: __wasm_call_ctors
; CHECK-NEXT: - Index: 1
; CHECK-NEXT: Name: undefined function weakFunc1
; CHECK-NEXT: Name: 'undefined:weakFunc1'
; CHECK-NEXT: - Index: 2
; CHECK-NEXT: Name: undefined function weakFunc2
; CHECK-NEXT: Name: 'undefined:weakFunc2'
; CHECK-NEXT: - Index: 3
; CHECK-NEXT: Name: undefined function weakFunc3
; CHECK-NEXT: Name: 'undefined:weakFunc3'
; CHECK-NEXT: - Index: 4
; CHECK-NEXT: Name: callWeakFuncs
; CHECK-NEXT: ...

View File

@ -286,53 +286,6 @@ static StringRef getEntry(opt::InputArgList &Args, StringRef Default) {
return Arg->getValue();
}
static const uint8_t UnreachableFn[] = {
0x03 /* ULEB length */, 0x00 /* ULEB num locals */,
0x00 /* opcode unreachable */, 0x0b /* opcode end */
};
// For weak undefined functions, there may be "call" instructions that reference
// the symbol. In this case, we need to synthesise a dummy/stub function that
// will abort at runtime, so that relocations can still provided an operand to
// the call instruction that passes Wasm validation.
static void handleWeakUndefines() {
for (Symbol *Sym : Symtab->getSymbols()) {
if (!Sym->isUndefWeak())
continue;
const WasmSignature *Sig = nullptr;
if (auto *FuncSym = dyn_cast<FunctionSymbol>(Sym)) {
// It is possible for undefined functions not to have a signature (eg. if
// added via "--undefined"), but weak undefined ones do have a signature.
assert(FuncSym->Signature);
Sig = FuncSym->Signature;
} else if (auto *LazySym = dyn_cast<LazySymbol>(Sym)) {
// Lazy symbols may not be functions and therefore can have a null
// signature.
Sig = LazySym->Signature;
}
if (!Sig)
continue;
// Add a synthetic dummy for weak undefined functions. These dummies will
// be GC'd if not used as the target of any "call" instructions.
std::string SymName = toString(*Sym);
StringRef DebugName = Saver.save("undefined function " + SymName);
auto *Func = make<SyntheticFunction>(*Sig, Sym->getName(), DebugName);
Func->setBody(UnreachableFn);
// Ensure it compares equal to the null pointer, and so that table relocs
// don't pull in the stub body (only call-operand relocs should do that).
Func->setTableIndex(0);
Symtab->SyntheticFunctions.emplace_back(Func);
// Hide our dummy to prevent export.
uint32_t Flags = WASM_SYMBOL_VISIBILITY_HIDDEN;
replaceSymbol<DefinedFunction>(Sym, Sym->getName(), Flags, nullptr, Func);
}
}
// Some Config members do not directly correspond to any particular
// command line options, but computed based on other Config values.
// This function initialize such members. See Config.h for the details
@ -620,7 +573,7 @@ void LinkerDriver::link(ArrayRef<const char *> ArgsArr) {
// Add synthetic dummies for weak undefined functions. Must happen
// after LTO otherwise functions may not yet have signatures.
if (!Config->Relocatable)
handleWeakUndefines();
Symtab->handleWeakUndefines();
if (EntrySym)
EntrySym->setHidden(false);

View File

@ -102,7 +102,8 @@ std::pair<Symbol *, bool> SymbolTable::insertName(StringRef Name) {
return {Sym, true};
}
std::pair<Symbol *, bool> SymbolTable::insert(StringRef Name, InputFile *File) {
std::pair<Symbol *, bool> SymbolTable::insert(StringRef Name,
const InputFile *File) {
Symbol *S;
bool WasInserted;
std::tie(S, WasInserted) = insertName(Name);
@ -442,3 +443,58 @@ bool SymbolTable::addComdat(StringRef Name) {
void SymbolTable::trace(StringRef Name) {
SymMap.insert({CachedHashStringRef(Name), -1});
}
static const uint8_t UnreachableFn[] = {
0x03 /* ULEB length */, 0x00 /* ULEB num locals */,
0x00 /* opcode unreachable */, 0x0b /* opcode end */
};
// Replace the given symbol body with an unreachable function.
// This is used by handleWeakUndefines in order to generate a callable
// equivalent of an undefined function.
InputFunction *SymbolTable::replaceWithUnreachable(Symbol *Sym,
const WasmSignature &Sig,
StringRef DebugName) {
auto *Func = make<SyntheticFunction>(Sig, Sym->getName(), DebugName);
Func->setBody(UnreachableFn);
SyntheticFunctions.emplace_back(Func);
replaceSymbol<DefinedFunction>(Sym, Sym->getName(), Sym->getFlags(), nullptr, Func);
return Func;
}
// For weak undefined functions, there may be "call" instructions that reference
// the symbol. In this case, we need to synthesise a dummy/stub function that
// will abort at runtime, so that relocations can still provided an operand to
// the call instruction that passes Wasm validation.
void SymbolTable::handleWeakUndefines() {
for (Symbol *Sym : getSymbols()) {
if (!Sym->isUndefWeak())
continue;
const WasmSignature *Sig = nullptr;
if (auto *FuncSym = dyn_cast<FunctionSymbol>(Sym)) {
// It is possible for undefined functions not to have a signature (eg. if
// added via "--undefined"), but weak undefined ones do have a signature.
assert(FuncSym->Signature);
Sig = FuncSym->Signature;
} else if (auto *LazySym = dyn_cast<LazySymbol>(Sym)) {
// Lazy symbols may not be functions and therefore can have a null
// signature.
Sig = LazySym->Signature;
}
if (!Sig)
continue;
// Add a synthetic dummy for weak undefined functions. These dummies will
// be GC'd if not used as the target of any "call" instructions.
StringRef DebugName = Saver.save("undefined:" + toString(*Sym));
InputFunction* Func = replaceWithUnreachable(Sym, *Sig, DebugName);
// Ensure it compares equal to the null pointer, and so that table relocs
// don't pull in the stub body (only call-operand relocs should do that).
Func->setTableIndex(0);
// Hide our dummy to prevent export.
Sym->setHidden(true);
}
}

View File

@ -79,10 +79,15 @@ public:
DefinedFunction *addSyntheticFunction(StringRef Name, uint32_t Flags,
InputFunction *Function);
void handleWeakUndefines();
private:
std::pair<Symbol *, bool> insert(StringRef Name, InputFile *File);
std::pair<Symbol *, bool> insert(StringRef Name, const InputFile *File);
std::pair<Symbol *, bool> insertName(StringRef Name);
InputFunction *replaceWithUnreachable(Symbol *Sym, const WasmSignature &Sig,
StringRef DebugName);
// Maps symbol names to index into the SymVector. -1 means that symbols
// is to not yet in the vector but it should have tracing enabled if it is
// ever added.

View File

@ -76,6 +76,8 @@ public:
// Returns the file from which this symbol was created.
InputFile *getFile() const { return File; }
uint32_t getFlags() const { return Flags; }
InputChunk *getChunk() const;
// Indicates that the section or import for this symbol will be included in