| //===- 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 "InputElement.h" |
| #include "WriterUtils.h" |
| #include "lld/Common/CommonLinkerContext.h" |
| #include <optional> |
| |
| #define DEBUG_TYPE "lld" |
| |
| using namespace llvm; |
| using namespace llvm::wasm; |
| using namespace llvm::object; |
| |
| namespace lld::wasm { |
| SymbolTable *symtab; |
| |
| void SymbolTable::addFile(InputFile *file, StringRef symName) { |
| log("Processing: " + toString(file)); |
| |
| // Lazy object file |
| if (file->lazy) { |
| if (auto *f = dyn_cast<BitcodeFile>(file)) { |
| f->parseLazy(); |
| } else { |
| cast<ObjFile>(file)->parseLazy(); |
| } |
| return; |
| } |
| |
| // .so file |
| if (auto *f = dyn_cast<SharedFile>(file)) { |
| // If we are not reporting undefined symbols that we don't actualy |
| // parse the shared library symbol table. |
| f->parse(); |
| ctx.sharedFiles.push_back(f); |
| return; |
| } |
| |
| // stub file |
| if (auto *f = dyn_cast<StubFile>(file)) { |
| f->parse(); |
| ctx.stubFiles.push_back(f); |
| return; |
| } |
| |
| if (config->trace) |
| message(toString(file)); |
| |
| // LLVM bitcode file |
| if (auto *f = dyn_cast<BitcodeFile>(file)) { |
| // This order, first adding to `bitcodeFiles` and then parsing is necessary. |
| // See https://github.com/llvm/llvm-project/pull/73095 |
| ctx.bitcodeFiles.push_back(f); |
| f->parse(symName); |
| return; |
| } |
| |
| // Regular object file |
| auto *f = cast<ObjFile>(file); |
| f->parse(false); |
| ctx.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::compileBitcodeFiles() { |
| // Prevent further LTO objects being included |
| BitcodeFile::doneLTO = true; |
| |
| if (ctx.bitcodeFiles.empty()) |
| return; |
| |
| // Compile bitcode files and replace bitcode symbols. |
| lto.reset(new BitcodeCompiler); |
| for (BitcodeFile *f : ctx.bitcodeFiles) |
| lto->add(*f); |
| |
| for (StringRef filename : lto->compile()) { |
| auto *obj = make<ObjFile>(MemoryBufferRef(filename, "lto.tmp"), ""); |
| obj->parse(true); |
| ctx.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; |
| sym->forceExport = false; |
| sym->referenced = !config->gcSections; |
| 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 signature |
| // mismatch. |
| static bool signatureMatches(FunctionSymbol *existing, |
| const WasmSignature *newSig) { |
| const WasmSignature *oldSig = existing->signature; |
| |
| // If either function is missing a signature (this happens 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 checkTagType(const Symbol *existing, const InputFile *file, |
| const WasmSignature *newSig) { |
| const auto *existingTag = dyn_cast<TagSymbol>(existing); |
| if (!isa<TagSymbol>(existing)) { |
| reportTypeError(existing, file, WASM_SYMBOL_TYPE_TAG); |
| return; |
| } |
| |
| const WasmSignature *oldSig = existingTag->signature; |
| if (*newSig != *oldSig) |
| warn("Tag signature mismatch: " + existing->getName() + |
| "\n>>> defined as " + toString(*oldSig) + " in " + |
| toString(existing->getFile()) + "\n>>> defined as " + |
| toString(*newSig) + " in " + toString(file)); |
| } |
| |
| static void checkTableType(const Symbol *existing, const InputFile *file, |
| const WasmTableType *newType) { |
| if (!isa<TableSymbol>(existing)) { |
| reportTypeError(existing, file, WASM_SYMBOL_TYPE_TABLE); |
| return; |
| } |
| |
| const WasmTableType *oldType = cast<TableSymbol>(existing)->getTableType(); |
| if (newType->ElemType != oldType->ElemType) { |
| error("Table type mismatch: " + existing->getName() + "\n>>> defined as " + |
| toString(*oldType) + " in " + toString(existing->getFile()) + |
| "\n>>> defined as " + toString(*newType) + " in " + toString(file)); |
| } |
| // FIXME: No assertions currently on the limits. |
| } |
| |
| 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)); |
| ctx.syntheticFunctions.emplace_back(function); |
| return replaceSymbol<DefinedFunction>(insertName(name).first, name, |
| flags, nullptr, function); |
| } |
| |
| // Adds an optional, linker generated, data symbol. The symbol will only be |
| // added if there is an undefine reference to it, or if it is explicitly |
| // exported via the --export flag. Otherwise we don't add the symbol and return |
| // nullptr. |
| DefinedData *SymbolTable::addOptionalDataSymbol(StringRef name, |
| uint64_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 | WASM_SYMBOL_ABSOLUTE); |
| rtn->setVA(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 | WASM_SYMBOL_ABSOLUTE); |
| } |
| |
| DefinedGlobal *SymbolTable::addSyntheticGlobal(StringRef name, uint32_t flags, |
| InputGlobal *global) { |
| LLVM_DEBUG(dbgs() << "addSyntheticGlobal: " << name << " -> " << global |
| << "\n"); |
| assert(!find(name)); |
| ctx.syntheticGlobals.emplace_back(global); |
| return replaceSymbol<DefinedGlobal>(insertName(name).first, name, flags, |
| nullptr, global); |
| } |
| |
| DefinedGlobal *SymbolTable::addOptionalGlobalSymbol(StringRef name, |
| InputGlobal *global) { |
| Symbol *s = find(name); |
| if (!s || s->isDefined()) |
| return nullptr; |
| LLVM_DEBUG(dbgs() << "addOptionalGlobalSymbol: " << name << " -> " << global |
| << "\n"); |
| ctx.syntheticGlobals.emplace_back(global); |
| return replaceSymbol<DefinedGlobal>(s, name, WASM_SYMBOL_VISIBILITY_HIDDEN, |
| nullptr, global); |
| } |
| |
| DefinedTable *SymbolTable::addSyntheticTable(StringRef name, uint32_t flags, |
| InputTable *table) { |
| LLVM_DEBUG(dbgs() << "addSyntheticTable: " << name << " -> " << table |
| << "\n"); |
| Symbol *s = find(name); |
| assert(!s || s->isUndefined()); |
| if (!s) |
| s = insertName(name).first; |
| ctx.syntheticTables.emplace_back(table); |
| return replaceSymbol<DefinedTable>(s, name, flags, nullptr, table); |
| } |
| |
| 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; |
| } |
| |
| // Similarly with shared symbols |
| if (existing->isShared()) { |
| LLVM_DEBUG(dbgs() << "replacing existing shared symbol\n"); |
| return true; |
| } |
| |
| // Neither symbol is week. They conflict. |
| if (config->allowMultipleDefinition) |
| return false; |
| |
| errorOrWarn("duplicate symbol: " + toString(*existing) + "\n>>> defined in " + |
| toString(existing->getFile()) + "\n>>> defined in " + |
| toString(newFile)); |
| return true; |
| } |
| |
| static void reportFunctionSignatureMismatch(StringRef symName, |
| FunctionSymbol *sym, |
| const WasmSignature *signature, |
| InputFile *file, |
| bool isError = true) { |
| std::string msg = |
| ("function signature mismatch: " + symName + "\n>>> defined as " + |
| toString(*sym->signature) + " in " + toString(sym->getFile()) + |
| "\n>>> defined as " + toString(*signature) + " in " + toString(file)) |
| .str(); |
| if (isError) |
| error(msg); |
| else |
| warn(msg); |
| } |
| |
| static void reportFunctionSignatureMismatch(StringRef symName, |
| FunctionSymbol *a, |
| FunctionSymbol *b, |
| bool isError = true) { |
| reportFunctionSignatureMismatch(symName, a, b->signature, b->getFile(), |
| isError); |
| } |
| |
| Symbol *SymbolTable::addSharedFunction(StringRef name, uint32_t flags, |
| InputFile *file, |
| const WasmSignature *sig) { |
| LLVM_DEBUG(dbgs() << "addSharedFunction: " << name << " [" << toString(*sig) |
| << "]\n"); |
| Symbol *s; |
| bool wasInserted; |
| std::tie(s, wasInserted) = insert(name, file); |
| |
| auto replaceSym = [&](Symbol *sym) { |
| replaceSymbol<SharedFunctionSymbol>(sym, name, flags, file, sig); |
| }; |
| |
| if (wasInserted) { |
| replaceSym(s); |
| return s; |
| } |
| |
| auto existingFunction = dyn_cast<FunctionSymbol>(s); |
| if (!existingFunction) { |
| reportTypeError(s, file, WASM_SYMBOL_TYPE_FUNCTION); |
| return s; |
| } |
| |
| // Shared symbols should never replace locally-defined ones |
| if (s->isDefined()) { |
| return s; |
| } |
| |
| LLVM_DEBUG(dbgs() << "resolving existing undefined symbol: " << s->getName() |
| << "\n"); |
| |
| bool checkSig = true; |
| if (auto ud = dyn_cast<UndefinedFunction>(existingFunction)) |
| checkSig = ud->isCalledDirectly; |
| |
| if (checkSig && !signatureMatches(existingFunction, sig)) { |
| if (config->shlibSigCheck) { |
| reportFunctionSignatureMismatch(name, existingFunction, sig, file); |
| } else { |
| // With --no-shlib-sigcheck we ignore the signature of the function as |
| // defined by the shared library and instead use the signature as |
| // expected by the program being linked. |
| sig = existingFunction->signature; |
| } |
| } |
| |
| replaceSym(s); |
| return s; |
| } |
| |
| Symbol *SymbolTable::addSharedData(StringRef name, uint32_t flags, |
| InputFile *file) { |
| LLVM_DEBUG(dbgs() << "addSharedData: " << name << "\n"); |
| Symbol *s; |
| bool wasInserted; |
| std::tie(s, wasInserted) = insert(name, file); |
| |
| if (wasInserted || s->isUndefined()) { |
| replaceSymbol<SharedData>(s, name, flags, file); |
| } |
| |
| return s; |
| } |
| |
| 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 signature (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, InputChunk *segment, |
| uint64_t address, uint64_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::addDefinedTag(StringRef name, uint32_t flags, |
| InputFile *file, InputTag *tag) { |
| LLVM_DEBUG(dbgs() << "addDefinedTag:" << name << "\n"); |
| |
| Symbol *s; |
| bool wasInserted; |
| std::tie(s, wasInserted) = insert(name, file); |
| |
| auto replaceSym = [&]() { |
| replaceSymbol<DefinedTag>(s, name, flags, file, tag); |
| }; |
| |
| if (wasInserted || s->isLazy()) { |
| replaceSym(); |
| return s; |
| } |
| |
| checkTagType(s, file, &tag->signature); |
| |
| if (shouldReplace(s, file, flags)) |
| replaceSym(); |
| return s; |
| } |
| |
| Symbol *SymbolTable::addDefinedTable(StringRef name, uint32_t flags, |
| InputFile *file, InputTable *table) { |
| LLVM_DEBUG(dbgs() << "addDefinedTable:" << name << "\n"); |
| |
| Symbol *s; |
| bool wasInserted; |
| std::tie(s, wasInserted) = insert(name, file); |
| |
| auto replaceSym = [&]() { |
| replaceSymbol<DefinedTable>(s, name, flags, file, table); |
| }; |
| |
| if (wasInserted || s->isLazy()) { |
| replaceSym(); |
| return s; |
| } |
| |
| checkTableType(s, file, &table->getType()); |
| |
| 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 available 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, |
| std::optional<StringRef> importName, |
| std::optional<StringRef> importModule, |
| uint32_t flags, InputFile *file) { |
| if (importName) { |
| if (!existing->importName) |
| 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) { |
| if (!existing->importModule) |
| 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)); |
| } |
| |
| // Update symbol binding, if the existing symbol is weak |
| uint32_t binding = flags & WASM_SYMBOL_BINDING_MASK; |
| if (existing->isWeak() && binding != WASM_SYMBOL_BINDING_WEAK) { |
| existing->flags = (existing->flags & ~WASM_SYMBOL_BINDING_MASK) | binding; |
| } |
| } |
| |
| Symbol *SymbolTable::addUndefinedFunction(StringRef name, |
| std::optional<StringRef> importName, |
| std::optional<StringRef> importModule, |
| uint32_t flags, InputFile *file, |
| const WasmSignature *sig, |
| bool isCalledDirectly) { |
| LLVM_DEBUG(dbgs() << "addUndefinedFunction: " << name << " [" |
| << (sig ? toString(*sig) : "none") |
| << "] IsCalledDirectly:" << isCalledDirectly << " flags=0x" |
| << utohexstr(flags) << "\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)) { |
| if ((flags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_WEAK) { |
| lazy->setWeak(); |
| lazy->signature = sig; |
| } else { |
| lazy->extract(); |
| if (!config->whyExtract.empty()) |
| ctx.whyExtractRecords.emplace_back(toString(file), s->getFile(), *s); |
| } |
| } else { |
| auto existingFunction = dyn_cast<FunctionSymbol>(s); |
| if (!existingFunction) { |
| reportTypeError(s, file, WASM_SYMBOL_TYPE_FUNCTION); |
| return s; |
| } |
| if (!existingFunction->signature && sig) |
| existingFunction->signature = sig; |
| auto *existingUndefined = dyn_cast<UndefinedFunction>(existingFunction); |
| if (isCalledDirectly && !signatureMatches(existingFunction, sig)) { |
| if (existingFunction->isShared()) { |
| // Special handling for when the existing function is a shared symbol |
| if (config->shlibSigCheck) { |
| reportFunctionSignatureMismatch(name, existingFunction, sig, file); |
| } else { |
| existingFunction->signature = sig; |
| } |
| } |
| // If the existing undefined functions is not called directly then let |
| // this one take precedence. Otherwise the existing function is either |
| // directly called or defined, in which case we need a function variant. |
| else if (existingUndefined && !existingUndefined->isCalledDirectly) |
| replaceSym(); |
| else if (getFunctionVariant(s, sig, file, &s)) |
| replaceSym(); |
| } |
| if (existingUndefined) { |
| setImportAttributes(existingUndefined, importName, importModule, flags, |
| file); |
| if (isCalledDirectly) |
| existingUndefined->isCalledDirectly = true; |
| if (s->isWeak()) |
| s->flags = flags; |
| } |
| } |
| |
| 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)) { |
| if ((flags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_WEAK) |
| lazy->setWeak(); |
| else |
| lazy->extract(); |
| } else if (s->isDefined()) { |
| checkDataType(s, file); |
| } else if (s->isWeak()) { |
| s->flags = flags; |
| } |
| return s; |
| } |
| |
| Symbol *SymbolTable::addUndefinedGlobal(StringRef name, |
| std::optional<StringRef> importName, |
| std::optional<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->extract(); |
| else if (s->isDefined()) |
| checkGlobalType(s, file, type); |
| else if (s->isWeak()) |
| s->flags = flags; |
| return s; |
| } |
| |
| Symbol *SymbolTable::addUndefinedTable(StringRef name, |
| std::optional<StringRef> importName, |
| std::optional<StringRef> importModule, |
| uint32_t flags, InputFile *file, |
| const WasmTableType *type) { |
| LLVM_DEBUG(dbgs() << "addUndefinedTable: " << 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<UndefinedTable>(s, name, importName, importModule, flags, |
| file, type); |
| else if (auto *lazy = dyn_cast<LazySymbol>(s)) |
| lazy->extract(); |
| else if (s->isDefined()) |
| checkTableType(s, file, type); |
| else if (s->isWeak()) |
| s->flags = flags; |
| return s; |
| } |
| |
| Symbol *SymbolTable::addUndefinedTag(StringRef name, |
| std::optional<StringRef> importName, |
| std::optional<StringRef> importModule, |
| uint32_t flags, InputFile *file, |
| const WasmSignature *sig) { |
| LLVM_DEBUG(dbgs() << "addUndefinedTag: " << 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<UndefinedTag>(s, name, importName, importModule, flags, file, |
| sig); |
| else if (auto *lazy = dyn_cast<LazySymbol>(s)) |
| lazy->extract(); |
| else if (s->isDefined()) |
| checkTagType(s, file, sig); |
| else if (s->isWeak()) |
| s->flags = flags; |
| return s; |
| } |
| |
| TableSymbol *SymbolTable::createUndefinedIndirectFunctionTable(StringRef name) { |
| WasmLimits limits{0, 0, 0}; // Set by the writer. |
| WasmTableType *type = make<WasmTableType>(); |
| type->ElemType = ValType::FUNCREF; |
| type->Limits = limits; |
| uint32_t flags = config->exportTable ? 0 : WASM_SYMBOL_VISIBILITY_HIDDEN; |
| flags |= WASM_SYMBOL_UNDEFINED; |
| Symbol *sym = |
| addUndefinedTable(name, name, defaultModule, flags, nullptr, type); |
| sym->markLive(); |
| sym->forceExport = config->exportTable; |
| return cast<TableSymbol>(sym); |
| } |
| |
| TableSymbol *SymbolTable::createDefinedIndirectFunctionTable(StringRef name) { |
| const uint32_t invalidIndex = -1; |
| WasmLimits limits{0, 0, 0}; // Set by the writer. |
| WasmTableType type{ValType::FUNCREF, limits}; |
| WasmTable desc{invalidIndex, type, name}; |
| InputTable *table = make<InputTable>(desc, nullptr); |
| uint32_t flags = config->exportTable ? 0 : WASM_SYMBOL_VISIBILITY_HIDDEN; |
| TableSymbol *sym = addSyntheticTable(name, flags, table); |
| sym->markLive(); |
| sym->forceExport = config->exportTable; |
| return sym; |
| } |
| |
| // Whether or not we need an indirect function table is usually a function of |
| // whether an input declares a need for it. However sometimes it's possible for |
| // no input to need the indirect function table, but then a late |
| // addInternalGOTEntry causes a function to be allocated an address. In that |
| // case address we synthesize a definition at the last minute. |
| TableSymbol *SymbolTable::resolveIndirectFunctionTable(bool required) { |
| Symbol *existing = find(functionTableName); |
| if (existing) { |
| if (!isa<TableSymbol>(existing)) { |
| error(Twine("reserved symbol must be of type table: `") + |
| functionTableName + "`"); |
| return nullptr; |
| } |
| if (existing->isDefined()) { |
| error(Twine("reserved symbol must not be defined in input files: `") + |
| functionTableName + "`"); |
| return nullptr; |
| } |
| } |
| |
| if (config->importTable) { |
| if (existing) { |
| existing->importModule = defaultModule; |
| existing->importName = functionTableName; |
| return cast<TableSymbol>(existing); |
| } |
| if (required) |
| return createUndefinedIndirectFunctionTable(functionTableName); |
| } else if ((existing && existing->isLive()) || config->exportTable || |
| required) { |
| // A defined table is required. Either because the user request an exported |
| // table or because the table symbol is already live. The existing table is |
| // guaranteed to be undefined due to the check above. |
| return createDefinedIndirectFunctionTable(functionTableName); |
| } |
| |
| // An indirect function table will only be present in the symbol table if |
| // needed by a reloc; if we get here, we don't need one. |
| return nullptr; |
| } |
| |
| void SymbolTable::addLazy(StringRef name, InputFile *file) { |
| LLVM_DEBUG(dbgs() << "addLazy: " << name << "\n"); |
| |
| Symbol *s; |
| bool wasInserted; |
| std::tie(s, wasInserted) = insertName(name); |
| |
| if (wasInserted) { |
| replaceSymbol<LazySymbol>(s, name, 0, file); |
| return; |
| } |
| |
| if (!s->isUndefined()) |
| return; |
| |
| // The existing symbol is undefined, load a new one from the archive, |
| // unless 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); |
| newSym->signature = oldSig; |
| return; |
| } |
| |
| LLVM_DEBUG(dbgs() << "replacing existing undefined\n"); |
| const InputFile *oldFile = s->getFile(); |
| LazySymbol(name, 0, file).extract(); |
| if (!config->whyExtract.empty()) |
| ctx.whyExtractRecords.emplace_back(toString(oldFile), s->getFile(), *s); |
| } |
| |
| 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>()); |
| variant->isUsedInRegularObj = |
| !file || file->kind() == InputFile::ObjectKind; |
| variant->canInline = true; |
| variant->traced = false; |
| variant->forceExport = false; |
| 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); |
| ctx.syntheticFunctions.emplace_back(func); |
| // Mark new symbols as local. For relocatable output we don't want them |
| // to be exported outside the object file. |
| replaceSymbol<DefinedFunction>(sym, debugName, WASM_SYMBOL_BINDING_LOCAL, |
| nullptr, func); |
| // Ensure the stub function doesn't get a table entry. Its address |
| // should always compare equal to the null pointer. |
| sym->isStub = true; |
| return func; |
| } |
| |
| void SymbolTable::replaceWithUndefined(Symbol *sym) { |
| // 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_weak:" + toString(*sym)); |
| replaceWithUnreachable(sym, *sym->getSignature(), debugName); |
| // Hide our dummy to prevent export. |
| sym->setHidden(true); |
| } |
| |
| // 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 : symbols()) { |
| if (sym->isUndefWeak() && sym->isUsedInRegularObj) { |
| if (sym->getSignature()) { |
| replaceWithUndefined(sym); |
| } else { |
| // 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 circumstance. |
| assert(!isa<FunctionSymbol>(sym)); |
| } |
| } |
| } |
| } |
| |
| DefinedFunction *SymbolTable::createUndefinedStub(const WasmSignature &sig) { |
| if (stubFunctions.count(sig)) |
| return stubFunctions[sig]; |
| LLVM_DEBUG(dbgs() << "createUndefinedStub: " << toString(sig) << "\n"); |
| auto *sym = reinterpret_cast<DefinedFunction *>(make<SymbolUnion>()); |
| sym->isUsedInRegularObj = true; |
| sym->canInline = true; |
| sym->traced = false; |
| sym->forceExport = false; |
| sym->signature = &sig; |
| replaceSymbol<DefinedFunction>( |
| sym, "undefined_stub", WASM_SYMBOL_VISIBILITY_HIDDEN, nullptr, nullptr); |
| replaceWithUnreachable(sym, sig, "undefined_stub"); |
| stubFunctions[sig] = sym; |
| return sym; |
| } |
| |
| // 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])); |
| return; |
| } |
| |
| for (auto *symbol : variants) { |
| if (symbol != defined) { |
| auto *f = cast<FunctionSymbol>(symbol); |
| reportFunctionSignatureMismatch(symName, f, defined, false); |
| StringRef debugName = |
| saver().save("signature_mismatch:" + toString(*f)); |
| replaceWithUnreachable(f, *f->signature, debugName); |
| } |
| } |
| } |
| } |
| |
| } // namespace wasm::lld |