mirror of
https://github.com/Gericom/teak-llvm.git
synced 2025-06-19 11:35:51 -04:00

Undefined symbols in WebAssembly can come with custom `import-module` and `import-field` attributes. However when reading symbols from bitcode object files during LTO those curtom attributes are not available. Once we compile the LTO object and read in the symbol table from the object file we have access to these custom attributes. In this case, when undefined symbols are added and a symbol already exists in the SymbolTable we can't simple return it, we may need to update the symbol's attributes. Fixes: PR43211 Differential Revision: https://reviews.llvm.org/D68959 llvm-svn: 375081
730 lines
25 KiB
C++
730 lines
25 KiB
C++
//===- SymbolTable.cpp ----------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "SymbolTable.h"
|
|
#include "Config.h"
|
|
#include "InputChunks.h"
|
|
#include "InputEvent.h"
|
|
#include "InputGlobal.h"
|
|
#include "WriterUtils.h"
|
|
#include "lld/Common/ErrorHandler.h"
|
|
#include "lld/Common/Memory.h"
|
|
#include "llvm/ADT/SetVector.h"
|
|
|
|
#define DEBUG_TYPE "lld"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::wasm;
|
|
using namespace llvm::object;
|
|
|
|
namespace lld {
|
|
namespace wasm {
|
|
SymbolTable *symtab;
|
|
|
|
void SymbolTable::addFile(InputFile *file) {
|
|
log("Processing: " + toString(file));
|
|
|
|
// .a file
|
|
if (auto *f = dyn_cast<ArchiveFile>(file)) {
|
|
f->parse();
|
|
return;
|
|
}
|
|
|
|
// .so file
|
|
if (auto *f = dyn_cast<SharedFile>(file)) {
|
|
sharedFiles.push_back(f);
|
|
return;
|
|
}
|
|
|
|
if (config->trace)
|
|
message(toString(file));
|
|
|
|
// LLVM bitcode file
|
|
if (auto *f = dyn_cast<BitcodeFile>(file)) {
|
|
f->parse();
|
|
bitcodeFiles.push_back(f);
|
|
return;
|
|
}
|
|
|
|
// Regular object file
|
|
auto *f = cast<ObjFile>(file);
|
|
f->parse(false);
|
|
objectFiles.push_back(f);
|
|
}
|
|
|
|
// This function is where all the optimizations of link-time
|
|
// optimization happens. When LTO is in use, some input files are
|
|
// not in native object file format but in the LLVM bitcode format.
|
|
// This function compiles bitcode files into a few big native files
|
|
// using LLVM functions and replaces bitcode symbols with the results.
|
|
// Because all bitcode files that the program consists of are passed
|
|
// to the compiler at once, it can do whole-program optimization.
|
|
void SymbolTable::addCombinedLTOObject() {
|
|
if (bitcodeFiles.empty())
|
|
return;
|
|
|
|
// Compile bitcode files and replace bitcode symbols.
|
|
lto.reset(new BitcodeCompiler);
|
|
for (BitcodeFile *f : bitcodeFiles)
|
|
lto->add(*f);
|
|
|
|
for (StringRef filename : lto->compile()) {
|
|
auto *obj = make<ObjFile>(MemoryBufferRef(filename, "lto.tmp"), "");
|
|
obj->parse(true);
|
|
objectFiles.push_back(obj);
|
|
}
|
|
}
|
|
|
|
Symbol *SymbolTable::find(StringRef name) {
|
|
auto it = symMap.find(CachedHashStringRef(name));
|
|
if (it == symMap.end() || it->second == -1)
|
|
return nullptr;
|
|
return symVector[it->second];
|
|
}
|
|
|
|
void SymbolTable::replace(StringRef name, Symbol* sym) {
|
|
auto it = symMap.find(CachedHashStringRef(name));
|
|
symVector[it->second] = sym;
|
|
}
|
|
|
|
std::pair<Symbol *, bool> SymbolTable::insertName(StringRef name) {
|
|
bool trace = false;
|
|
auto p = symMap.insert({CachedHashStringRef(name), (int)symVector.size()});
|
|
int &symIndex = p.first->second;
|
|
bool isNew = p.second;
|
|
if (symIndex == -1) {
|
|
symIndex = symVector.size();
|
|
trace = true;
|
|
isNew = true;
|
|
}
|
|
|
|
if (!isNew)
|
|
return {symVector[symIndex], false};
|
|
|
|
Symbol *sym = reinterpret_cast<Symbol *>(make<SymbolUnion>());
|
|
sym->isUsedInRegularObj = false;
|
|
sym->canInline = true;
|
|
sym->traced = trace;
|
|
symVector.emplace_back(sym);
|
|
return {sym, true};
|
|
}
|
|
|
|
std::pair<Symbol *, bool> SymbolTable::insert(StringRef name,
|
|
const InputFile *file) {
|
|
Symbol *s;
|
|
bool wasInserted;
|
|
std::tie(s, wasInserted) = insertName(name);
|
|
|
|
if (!file || file->kind() == InputFile::ObjectKind)
|
|
s->isUsedInRegularObj = true;
|
|
|
|
return {s, wasInserted};
|
|
}
|
|
|
|
static void reportTypeError(const Symbol *existing, const InputFile *file,
|
|
llvm::wasm::WasmSymbolType type) {
|
|
error("symbol type mismatch: " + toString(*existing) + "\n>>> defined as " +
|
|
toString(existing->getWasmType()) + " in " +
|
|
toString(existing->getFile()) + "\n>>> defined as " + toString(type) +
|
|
" in " + toString(file));
|
|
}
|
|
|
|
// Check the type of new symbol matches that of the symbol is replacing.
|
|
// Returns true if the function types match, false is there is a singature
|
|
// mismatch.
|
|
static bool signatureMatches(FunctionSymbol *existing,
|
|
const WasmSignature *newSig) {
|
|
const WasmSignature *oldSig = existing->signature;
|
|
|
|
// If either function is missing a signature (this happend for bitcode
|
|
// symbols) then assume they match. Any mismatch will be reported later
|
|
// when the LTO objects are added.
|
|
if (!newSig || !oldSig)
|
|
return true;
|
|
|
|
return *newSig == *oldSig;
|
|
}
|
|
|
|
static void checkGlobalType(const Symbol *existing, const InputFile *file,
|
|
const WasmGlobalType *newType) {
|
|
if (!isa<GlobalSymbol>(existing)) {
|
|
reportTypeError(existing, file, WASM_SYMBOL_TYPE_GLOBAL);
|
|
return;
|
|
}
|
|
|
|
const WasmGlobalType *oldType = cast<GlobalSymbol>(existing)->getGlobalType();
|
|
if (*newType != *oldType) {
|
|
error("Global type mismatch: " + existing->getName() + "\n>>> defined as " +
|
|
toString(*oldType) + " in " + toString(existing->getFile()) +
|
|
"\n>>> defined as " + toString(*newType) + " in " + toString(file));
|
|
}
|
|
}
|
|
|
|
static void checkEventType(const Symbol *existing, const InputFile *file,
|
|
const WasmEventType *newType,
|
|
const WasmSignature *newSig) {
|
|
auto existingEvent = dyn_cast<EventSymbol>(existing);
|
|
if (!isa<EventSymbol>(existing)) {
|
|
reportTypeError(existing, file, WASM_SYMBOL_TYPE_EVENT);
|
|
return;
|
|
}
|
|
|
|
const WasmEventType *oldType = cast<EventSymbol>(existing)->getEventType();
|
|
const WasmSignature *oldSig = existingEvent->signature;
|
|
if (newType->Attribute != oldType->Attribute)
|
|
error("Event type mismatch: " + existing->getName() + "\n>>> defined as " +
|
|
toString(*oldType) + " in " + toString(existing->getFile()) +
|
|
"\n>>> defined as " + toString(*newType) + " in " + toString(file));
|
|
if (*newSig != *oldSig)
|
|
warn("Event signature mismatch: " + existing->getName() +
|
|
"\n>>> defined as " + toString(*oldSig) + " in " +
|
|
toString(existing->getFile()) + "\n>>> defined as " +
|
|
toString(*newSig) + " in " + toString(file));
|
|
}
|
|
|
|
static void checkDataType(const Symbol *existing, const InputFile *file) {
|
|
if (!isa<DataSymbol>(existing))
|
|
reportTypeError(existing, file, WASM_SYMBOL_TYPE_DATA);
|
|
}
|
|
|
|
DefinedFunction *SymbolTable::addSyntheticFunction(StringRef name,
|
|
uint32_t flags,
|
|
InputFunction *function) {
|
|
LLVM_DEBUG(dbgs() << "addSyntheticFunction: " << name << "\n");
|
|
assert(!find(name));
|
|
syntheticFunctions.emplace_back(function);
|
|
return replaceSymbol<DefinedFunction>(insertName(name).first, name,
|
|
flags, nullptr, function);
|
|
}
|
|
|
|
// Adds an optional, linker generated, data symbols. The symbol will only be
|
|
// added if there is an undefine reference to it, or if it is explictly exported
|
|
// via the --export flag. Otherwise we don't add the symbol and return nullptr.
|
|
DefinedData *SymbolTable::addOptionalDataSymbol(StringRef name,
|
|
uint32_t value) {
|
|
Symbol *s = find(name);
|
|
if (!s && (config->exportAll || config->exportedSymbols.count(name) != 0))
|
|
s = insertName(name).first;
|
|
else if (!s || s->isDefined())
|
|
return nullptr;
|
|
LLVM_DEBUG(dbgs() << "addOptionalDataSymbol: " << name << "\n");
|
|
auto *rtn = replaceSymbol<DefinedData>(s, name, WASM_SYMBOL_VISIBILITY_HIDDEN);
|
|
rtn->setVirtualAddress(value);
|
|
rtn->referenced = true;
|
|
return rtn;
|
|
}
|
|
|
|
DefinedData *SymbolTable::addSyntheticDataSymbol(StringRef name,
|
|
uint32_t flags) {
|
|
LLVM_DEBUG(dbgs() << "addSyntheticDataSymbol: " << name << "\n");
|
|
assert(!find(name));
|
|
return replaceSymbol<DefinedData>(insertName(name).first, name, flags);
|
|
}
|
|
|
|
DefinedGlobal *SymbolTable::addSyntheticGlobal(StringRef name, uint32_t flags,
|
|
InputGlobal *global) {
|
|
LLVM_DEBUG(dbgs() << "addSyntheticGlobal: " << name << " -> " << global
|
|
<< "\n");
|
|
assert(!find(name));
|
|
syntheticGlobals.emplace_back(global);
|
|
return replaceSymbol<DefinedGlobal>(insertName(name).first, name, flags,
|
|
nullptr, global);
|
|
}
|
|
|
|
static bool shouldReplace(const Symbol *existing, InputFile *newFile,
|
|
uint32_t newFlags) {
|
|
// If existing symbol is undefined, replace it.
|
|
if (!existing->isDefined()) {
|
|
LLVM_DEBUG(dbgs() << "resolving existing undefined symbol: "
|
|
<< existing->getName() << "\n");
|
|
return true;
|
|
}
|
|
|
|
// Now we have two defined symbols. If the new one is weak, we can ignore it.
|
|
if ((newFlags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_WEAK) {
|
|
LLVM_DEBUG(dbgs() << "existing symbol takes precedence\n");
|
|
return false;
|
|
}
|
|
|
|
// If the existing symbol is weak, we should replace it.
|
|
if (existing->isWeak()) {
|
|
LLVM_DEBUG(dbgs() << "replacing existing weak symbol\n");
|
|
return true;
|
|
}
|
|
|
|
// Neither symbol is week. They conflict.
|
|
error("duplicate symbol: " + toString(*existing) + "\n>>> defined in " +
|
|
toString(existing->getFile()) + "\n>>> defined in " +
|
|
toString(newFile));
|
|
return true;
|
|
}
|
|
|
|
Symbol *SymbolTable::addDefinedFunction(StringRef name, uint32_t flags,
|
|
InputFile *file,
|
|
InputFunction *function) {
|
|
LLVM_DEBUG(dbgs() << "addDefinedFunction: " << name << " ["
|
|
<< (function ? toString(function->signature) : "none")
|
|
<< "]\n");
|
|
Symbol *s;
|
|
bool wasInserted;
|
|
std::tie(s, wasInserted) = insert(name, file);
|
|
|
|
auto replaceSym = [&](Symbol *sym) {
|
|
// If the new defined function doesn't have signture (i.e. bitcode
|
|
// functions) but the old symbol does, then preserve the old signature
|
|
const WasmSignature *oldSig = s->getSignature();
|
|
auto* newSym = replaceSymbol<DefinedFunction>(sym, name, flags, file, function);
|
|
if (!newSym->signature)
|
|
newSym->signature = oldSig;
|
|
};
|
|
|
|
if (wasInserted || s->isLazy()) {
|
|
replaceSym(s);
|
|
return s;
|
|
}
|
|
|
|
auto existingFunction = dyn_cast<FunctionSymbol>(s);
|
|
if (!existingFunction) {
|
|
reportTypeError(s, file, WASM_SYMBOL_TYPE_FUNCTION);
|
|
return s;
|
|
}
|
|
|
|
bool checkSig = true;
|
|
if (auto ud = dyn_cast<UndefinedFunction>(existingFunction))
|
|
checkSig = ud->isCalledDirectly;
|
|
|
|
if (checkSig && function && !signatureMatches(existingFunction, &function->signature)) {
|
|
Symbol* variant;
|
|
if (getFunctionVariant(s, &function->signature, file, &variant))
|
|
// New variant, always replace
|
|
replaceSym(variant);
|
|
else if (shouldReplace(s, file, flags))
|
|
// Variant already exists, replace it after checking shouldReplace
|
|
replaceSym(variant);
|
|
|
|
// This variant we found take the place in the symbol table as the primary
|
|
// variant.
|
|
replace(name, variant);
|
|
return variant;
|
|
}
|
|
|
|
// Existing function with matching signature.
|
|
if (shouldReplace(s, file, flags))
|
|
replaceSym(s);
|
|
|
|
return s;
|
|
}
|
|
|
|
Symbol *SymbolTable::addDefinedData(StringRef name, uint32_t flags,
|
|
InputFile *file, InputSegment *segment,
|
|
uint32_t address, uint32_t size) {
|
|
LLVM_DEBUG(dbgs() << "addDefinedData:" << name << " addr:" << address
|
|
<< "\n");
|
|
Symbol *s;
|
|
bool wasInserted;
|
|
std::tie(s, wasInserted) = insert(name, file);
|
|
|
|
auto replaceSym = [&]() {
|
|
replaceSymbol<DefinedData>(s, name, flags, file, segment, address, size);
|
|
};
|
|
|
|
if (wasInserted || s->isLazy()) {
|
|
replaceSym();
|
|
return s;
|
|
}
|
|
|
|
checkDataType(s, file);
|
|
|
|
if (shouldReplace(s, file, flags))
|
|
replaceSym();
|
|
return s;
|
|
}
|
|
|
|
Symbol *SymbolTable::addDefinedGlobal(StringRef name, uint32_t flags,
|
|
InputFile *file, InputGlobal *global) {
|
|
LLVM_DEBUG(dbgs() << "addDefinedGlobal:" << name << "\n");
|
|
|
|
Symbol *s;
|
|
bool wasInserted;
|
|
std::tie(s, wasInserted) = insert(name, file);
|
|
|
|
auto replaceSym = [&]() {
|
|
replaceSymbol<DefinedGlobal>(s, name, flags, file, global);
|
|
};
|
|
|
|
if (wasInserted || s->isLazy()) {
|
|
replaceSym();
|
|
return s;
|
|
}
|
|
|
|
checkGlobalType(s, file, &global->getType());
|
|
|
|
if (shouldReplace(s, file, flags))
|
|
replaceSym();
|
|
return s;
|
|
}
|
|
|
|
Symbol *SymbolTable::addDefinedEvent(StringRef name, uint32_t flags,
|
|
InputFile *file, InputEvent *event) {
|
|
LLVM_DEBUG(dbgs() << "addDefinedEvent:" << name << "\n");
|
|
|
|
Symbol *s;
|
|
bool wasInserted;
|
|
std::tie(s, wasInserted) = insert(name, file);
|
|
|
|
auto replaceSym = [&]() {
|
|
replaceSymbol<DefinedEvent>(s, name, flags, file, event);
|
|
};
|
|
|
|
if (wasInserted || s->isLazy()) {
|
|
replaceSym();
|
|
return s;
|
|
}
|
|
|
|
checkEventType(s, file, &event->getType(), &event->signature);
|
|
|
|
if (shouldReplace(s, file, flags))
|
|
replaceSym();
|
|
return s;
|
|
}
|
|
|
|
// This function get called when an undefined symbol is added, and there is
|
|
// already an existing one in the symbols table. In this case we check that
|
|
// custom 'import-module' and 'import-field' symbol attributes agree.
|
|
// With LTO these attributes are not avialable when the bitcode is read and only
|
|
// become available when the LTO object is read. In this case we silently
|
|
// replace the empty attributes with the valid ones.
|
|
template <typename T>
|
|
static void setImportAttributes(T *existing, StringRef importName,
|
|
StringRef importModule, InputFile *file) {
|
|
if (!importName.empty()) {
|
|
if (existing->importName.empty())
|
|
existing->importName = importName;
|
|
if (existing->importName != importName)
|
|
error("import name mismatch for symbol: " + toString(*existing) +
|
|
"\n>>> defined as " + existing->importName + " in " +
|
|
toString(existing->getFile()) + "\n>>> defined as " + importName +
|
|
" in " + toString(file));
|
|
}
|
|
|
|
if (!importModule.empty()) {
|
|
if (existing->importModule.empty())
|
|
existing->importModule = importModule;
|
|
if (existing->importModule != importModule)
|
|
error("import module mismatch for symbol: " + toString(*existing) +
|
|
"\n>>> defined as " + existing->importModule + " in " +
|
|
toString(existing->getFile()) + "\n>>> defined as " + importModule +
|
|
" in " + toString(file));
|
|
}
|
|
}
|
|
|
|
Symbol *SymbolTable::addUndefinedFunction(StringRef name, StringRef importName,
|
|
StringRef importModule,
|
|
uint32_t flags, InputFile *file,
|
|
const WasmSignature *sig,
|
|
bool isCalledDirectly) {
|
|
LLVM_DEBUG(dbgs() << "addUndefinedFunction: " << name << " ["
|
|
<< (sig ? toString(*sig) : "none")
|
|
<< "] IsCalledDirectly:" << isCalledDirectly << "\n");
|
|
assert(flags & WASM_SYMBOL_UNDEFINED);
|
|
|
|
Symbol *s;
|
|
bool wasInserted;
|
|
std::tie(s, wasInserted) = insert(name, file);
|
|
if (s->traced)
|
|
printTraceSymbolUndefined(name, file);
|
|
|
|
auto replaceSym = [&]() {
|
|
replaceSymbol<UndefinedFunction>(s, name, importName, importModule, flags,
|
|
file, sig, isCalledDirectly);
|
|
};
|
|
|
|
if (wasInserted)
|
|
replaceSym();
|
|
else if (auto *lazy = dyn_cast<LazySymbol>(s))
|
|
lazy->fetch();
|
|
else {
|
|
auto existingFunction = dyn_cast<FunctionSymbol>(s);
|
|
if (!existingFunction) {
|
|
reportTypeError(s, file, WASM_SYMBOL_TYPE_FUNCTION);
|
|
return s;
|
|
}
|
|
auto *existingUndefined = dyn_cast<UndefinedFunction>(existingFunction);
|
|
if (!existingFunction->signature && sig)
|
|
existingFunction->signature = sig;
|
|
if (isCalledDirectly && !signatureMatches(existingFunction, sig)) {
|
|
// If the existing undefined functions is not called direcltly then let
|
|
// this one take precedence. Otherwise the existing function is either
|
|
// direclty called or defined, in which case we need a function variant.
|
|
if (existingUndefined && !existingUndefined->isCalledDirectly)
|
|
replaceSym();
|
|
else if (getFunctionVariant(s, sig, file, &s))
|
|
replaceSym();
|
|
}
|
|
if (existingUndefined)
|
|
setImportAttributes(existingUndefined, importName, importModule, file);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
Symbol *SymbolTable::addUndefinedData(StringRef name, uint32_t flags,
|
|
InputFile *file) {
|
|
LLVM_DEBUG(dbgs() << "addUndefinedData: " << name << "\n");
|
|
assert(flags & WASM_SYMBOL_UNDEFINED);
|
|
|
|
Symbol *s;
|
|
bool wasInserted;
|
|
std::tie(s, wasInserted) = insert(name, file);
|
|
if (s->traced)
|
|
printTraceSymbolUndefined(name, file);
|
|
|
|
if (wasInserted)
|
|
replaceSymbol<UndefinedData>(s, name, flags, file);
|
|
else if (auto *lazy = dyn_cast<LazySymbol>(s))
|
|
lazy->fetch();
|
|
else if (s->isDefined())
|
|
checkDataType(s, file);
|
|
return s;
|
|
}
|
|
|
|
Symbol *SymbolTable::addUndefinedGlobal(StringRef name, StringRef importName,
|
|
StringRef importModule, uint32_t flags,
|
|
InputFile *file,
|
|
const WasmGlobalType *type) {
|
|
LLVM_DEBUG(dbgs() << "addUndefinedGlobal: " << name << "\n");
|
|
assert(flags & WASM_SYMBOL_UNDEFINED);
|
|
|
|
Symbol *s;
|
|
bool wasInserted;
|
|
std::tie(s, wasInserted) = insert(name, file);
|
|
if (s->traced)
|
|
printTraceSymbolUndefined(name, file);
|
|
|
|
if (wasInserted)
|
|
replaceSymbol<UndefinedGlobal>(s, name, importName, importModule, flags,
|
|
file, type);
|
|
else if (auto *lazy = dyn_cast<LazySymbol>(s))
|
|
lazy->fetch();
|
|
else if (s->isDefined())
|
|
checkGlobalType(s, file, type);
|
|
return s;
|
|
}
|
|
|
|
void SymbolTable::addLazy(ArchiveFile *file, const Archive::Symbol *sym) {
|
|
LLVM_DEBUG(dbgs() << "addLazy: " << sym->getName() << "\n");
|
|
StringRef name = sym->getName();
|
|
|
|
Symbol *s;
|
|
bool wasInserted;
|
|
std::tie(s, wasInserted) = insertName(name);
|
|
|
|
if (wasInserted) {
|
|
replaceSymbol<LazySymbol>(s, name, 0, file, *sym);
|
|
return;
|
|
}
|
|
|
|
if (!s->isUndefined())
|
|
return;
|
|
|
|
// The existing symbol is undefined, load a new one from the archive,
|
|
// unless the the existing symbol is weak in which case replace the undefined
|
|
// symbols with a LazySymbol.
|
|
if (s->isWeak()) {
|
|
const WasmSignature *oldSig = nullptr;
|
|
// In the case of an UndefinedFunction we need to preserve the expected
|
|
// signature.
|
|
if (auto *f = dyn_cast<UndefinedFunction>(s))
|
|
oldSig = f->signature;
|
|
LLVM_DEBUG(dbgs() << "replacing existing weak undefined symbol\n");
|
|
auto newSym = replaceSymbol<LazySymbol>(s, name, WASM_SYMBOL_BINDING_WEAK,
|
|
file, *sym);
|
|
newSym->signature = oldSig;
|
|
return;
|
|
}
|
|
|
|
LLVM_DEBUG(dbgs() << "replacing existing undefined\n");
|
|
file->addMember(sym);
|
|
}
|
|
|
|
bool SymbolTable::addComdat(StringRef name) {
|
|
return comdatGroups.insert(CachedHashStringRef(name)).second;
|
|
}
|
|
|
|
// The new signature doesn't match. Create a variant to the symbol with the
|
|
// signature encoded in the name and return that instead. These symbols are
|
|
// then unified later in handleSymbolVariants.
|
|
bool SymbolTable::getFunctionVariant(Symbol* sym, const WasmSignature *sig,
|
|
const InputFile *file, Symbol **out) {
|
|
LLVM_DEBUG(dbgs() << "getFunctionVariant: " << sym->getName() << " -> "
|
|
<< " " << toString(*sig) << "\n");
|
|
Symbol *variant = nullptr;
|
|
|
|
// Linear search through symbol variants. Should never be more than two
|
|
// or three entries here.
|
|
auto &variants = symVariants[CachedHashStringRef(sym->getName())];
|
|
if (variants.empty())
|
|
variants.push_back(sym);
|
|
|
|
for (Symbol* v : variants) {
|
|
if (*v->getSignature() == *sig) {
|
|
variant = v;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool wasAdded = !variant;
|
|
if (wasAdded) {
|
|
// Create a new variant;
|
|
LLVM_DEBUG(dbgs() << "added new variant\n");
|
|
variant = reinterpret_cast<Symbol *>(make<SymbolUnion>());
|
|
variants.push_back(variant);
|
|
} else {
|
|
LLVM_DEBUG(dbgs() << "variant already exists: " << toString(*variant) << "\n");
|
|
assert(*variant->getSignature() == *sig);
|
|
}
|
|
|
|
*out = variant;
|
|
return wasAdded;
|
|
}
|
|
|
|
// Set a flag for --trace-symbol so that we can print out a log message
|
|
// if a new symbol with the same name is inserted into the symbol table.
|
|
void SymbolTable::trace(StringRef name) {
|
|
symMap.insert({CachedHashStringRef(name), -1});
|
|
}
|
|
|
|
void SymbolTable::wrap(Symbol *sym, Symbol *real, Symbol *wrap) {
|
|
// Swap symbols as instructed by -wrap.
|
|
int &origIdx = symMap[CachedHashStringRef(sym->getName())];
|
|
int &realIdx= symMap[CachedHashStringRef(real->getName())];
|
|
int &wrapIdx = symMap[CachedHashStringRef(wrap->getName())];
|
|
LLVM_DEBUG(dbgs() << "wrap: " << sym->getName() << "\n");
|
|
|
|
// Anyone looking up __real symbols should get the original
|
|
realIdx = origIdx;
|
|
// Anyone looking up the original should get the __wrap symbol
|
|
origIdx = wrapIdx;
|
|
}
|
|
|
|
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 and also handleSymbolVariants for
|
|
// undefined functions that don't match the signature of the definition.
|
|
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 = sym->getSignature();
|
|
if (!sig) {
|
|
// It is possible for undefined functions not to have a signature (eg. if
|
|
// added via "--undefined"), but weak undefined ones do have a signature.
|
|
// Lazy symbols may not be functions and therefore Sig can still be null
|
|
// in some circumstantce.
|
|
assert(!isa<FunctionSymbol>(sym));
|
|
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);
|
|
}
|
|
}
|
|
|
|
static void reportFunctionSignatureMismatch(StringRef symName,
|
|
FunctionSymbol *a,
|
|
FunctionSymbol *b, bool isError) {
|
|
std::string msg = ("function signature mismatch: " + symName +
|
|
"\n>>> defined as " + toString(*a->signature) + " in " +
|
|
toString(a->getFile()) + "\n>>> defined as " +
|
|
toString(*b->signature) + " in " + toString(b->getFile()))
|
|
.str();
|
|
if (isError)
|
|
error(msg);
|
|
else
|
|
warn(msg);
|
|
}
|
|
|
|
// Remove any variant symbols that were created due to function signature
|
|
// mismatches.
|
|
void SymbolTable::handleSymbolVariants() {
|
|
for (auto pair : symVariants) {
|
|
// Push the initial symbol onto the list of variants.
|
|
StringRef symName = pair.first.val();
|
|
std::vector<Symbol *> &variants = pair.second;
|
|
|
|
#ifndef NDEBUG
|
|
LLVM_DEBUG(dbgs() << "symbol with (" << variants.size()
|
|
<< ") variants: " << symName << "\n");
|
|
for (auto *s: variants) {
|
|
auto *f = cast<FunctionSymbol>(s);
|
|
LLVM_DEBUG(dbgs() << " variant: " + f->getName() << " "
|
|
<< toString(*f->signature) << "\n");
|
|
}
|
|
#endif
|
|
|
|
// Find the one definition.
|
|
DefinedFunction *defined = nullptr;
|
|
for (auto *symbol : variants) {
|
|
if (auto f = dyn_cast<DefinedFunction>(symbol)) {
|
|
defined = f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If there are no definitions, and the undefined symbols disagree on
|
|
// the signature, there is not we can do since we don't know which one
|
|
// to use as the signature on the import.
|
|
if (!defined) {
|
|
reportFunctionSignatureMismatch(symName,
|
|
cast<FunctionSymbol>(variants[0]),
|
|
cast<FunctionSymbol>(variants[1]), true);
|
|
return;
|
|
}
|
|
|
|
for (auto *symbol : variants) {
|
|
if (symbol != defined) {
|
|
auto *f = cast<FunctionSymbol>(symbol);
|
|
reportFunctionSignatureMismatch(symName, f, defined, false);
|
|
StringRef debugName = saver.save("unreachable:" + toString(*f));
|
|
replaceWithUnreachable(f, *f->signature, debugName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace wasm
|
|
} // namespace lld
|