mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-12-13 02:21:45 +00:00
[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:
parent
81f859d169
commit
230dc11d24
@ -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
|
||||
----------------
|
||||
|
@ -15,5 +15,5 @@ entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; CHECK: Name: undefined function ret32
|
||||
; CHECK: Name: 'undefined:ret32'
|
||||
; CHECK-NOT: Name: ret32
|
||||
|
@ -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
|
||||
|
@ -17,4 +17,4 @@ entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; CHECK: Name: undefined function foo
|
||||
; CHECK: Name: 'undefined:foo'
|
||||
|
@ -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: ...
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user