diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -788,7 +788,8 @@ symtab->wrap(w.sym, w.real, w.wrap); } -static TableSymbol *createDefinedIndirectFunctionTable(StringRef name) { +static TableSymbol *createDefinedIndirectFunctionTable(StringRef name, + Symbol *existing) { const uint32_t invalidIndex = -1; WasmLimits limits{0, 0, 0}; // Set by the writer. WasmTableType type{uint8_t(ValType::FUNCREF), limits}; @@ -798,6 +799,14 @@ TableSymbol *sym = symtab->addSyntheticTable(name, flags, table); sym->markLive(); sym->forceExport = config->exportTable; + // If the table has unrelocatable references, make sure the table number in + // the output is the same as in the input(s). + if (existing) { + if (TableSymbol *ts = dyn_cast(existing)) { + if (ts->hasTableNumber()) + table->setTableNumber(ts->getTableNumber()); + } + } return sym; } @@ -841,7 +850,7 @@ // 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); + return createDefinedIndirectFunctionTable(functionTableName, existingTable); } // An indirect function table will only be present in the symbol table if diff --git a/lld/wasm/InputFiles.cpp b/lld/wasm/InputFiles.cpp --- a/lld/wasm/InputFiles.cpp +++ b/lld/wasm/InputFiles.cpp @@ -310,30 +310,48 @@ } } -// Since LLVM 12, we expect that if an input file defines or uses a table, it -// declares the tables using symbols and records each use with a relocation. -// This way when the linker combines inputs, it can collate the tables used by -// the inputs, assigning them distinct table numbers, and renumber all the uses -// as appropriate. At the same time, the linker has special logic to build the +// An object file can have two approaches to tables. With the reference-types +// feature enabled, input files that define or use tables declare the tables +// using symbols, and record each use with a relocation. This way when the +// linker combines inputs, it can collate the tables used by the inputs, +// assigning them distinct table numbers, and renumber all the uses as +// appropriate. At the same time, the linker has special logic to build the // indirect function table if it is needed. // -// However, object files produced by LLVM 11 and earlier neither write table -// symbols nor record relocations, and yet still use tables via call_indirect, -// and via function pointer bitcasts. We can detect these object files, as they -// declare tables as imports or define them locally, but don't have table -// symbols. synthesizeTableSymbols serves as a shim when loading these older -// input files, defining the missing symbols to allow the indirect function -// table to be built. +// However, MVP object files (those that target WebAssembly 1.0) neither write +// table symbols nor record relocations, and yet still use tables via +// call_indirect, and via function pointer bitcasts. We can detect these object +// files, as they declare tables as imports or define them locally, but don't +// have table symbols. synthesizeTableSymbols serves as a shim when loading +// these input files, defining the missing symbols to allow the indirect +// function table to be built. // -// Table uses in these older files won't be relocated, as they have no -// relocations. In practice this isn't a problem, as these object files -// typically just declare a single table named __indirect_function_table and -// having table number 0, so relocation would be idempotent anyway. +// Table uses in these MVP inputs won't be relocated, as they have no +// relocations. The linker must ensure that they have the same numbers in the +// output as in the input. void ObjFile::synthesizeTableSymbols() { uint32_t tableNumber = 0; const WasmGlobalType *globalType = nullptr; const WasmEventType *eventType = nullptr; const WasmSignature *signature = nullptr; + auto addTable = [&](Symbol *s) { + symbols.push_back(s); + // Because there are no TABLE_NUMBER relocs, we can't compute accurate + // liveness info; instead, just mark the symbol as always live. + s->markLive(); + // We assume that this compilation unit has unrelocatable references to this + // table, using its table number. Make sure any existing symbol is + // compatible with this "precoloring", or otherwise precolor the symbol if + // it's new. + if (auto *ts = dyn_cast(s)) { + if (ts->hasTableNumber()) { + if (ts->getTableNumber() != tableNumber) + error(toString(this) + ": incompatible precoloring"); + } else { + ts->setTableNumber(tableNumber); + } + } + }; if (wasmObj->getNumImportedTables()) { for (const auto &import : wasmObj->imports()) { if (import.Kind == WASM_EXTERNAL_TABLE) { @@ -344,16 +362,13 @@ info->ImportName = import.Field; info->Flags = WASM_SYMBOL_UNDEFINED; info->Flags |= WASM_SYMBOL_NO_STRIP; - info->ElementIndex = tableNumber++; + info->ElementIndex = tableNumber; LLVM_DEBUG(dbgs() << "Synthesizing symbol for table import: " << info->Name << "\n"); auto *wasmSym = make(*info, globalType, &import.Table, eventType, signature); - symbols.push_back(createUndefined(*wasmSym, false)); - // Because there are no TABLE_NUMBER relocs in this case, we can't - // compute accurate liveness info; instead, just mark the symbol as - // always live. - symbols.back()->markLive(); + addTable(createUndefined(*wasmSym, false)); + tableNumber++; } } } @@ -364,14 +379,13 @@ info->Flags = WASM_SYMBOL_BINDING_LOCAL; info->Flags |= WASM_SYMBOL_VISIBILITY_HIDDEN; info->Flags |= WASM_SYMBOL_NO_STRIP; - info->ElementIndex = tableNumber++; + info->ElementIndex = tableNumber; LLVM_DEBUG(dbgs() << "Synthesizing symbol for table definition: " << info->Name << "\n"); auto *wasmSym = make(*info, globalType, &table->getType(), eventType, signature); - symbols.push_back(createDefined(*wasmSym)); - // Mark live, for the same reasons as for imported tables. - symbols.back()->markLive(); + addTable(createDefined(*wasmSym)); + tableNumber++; } } @@ -506,10 +520,18 @@ symbols.push_back(createUndefined(wasmSym, isCalledDirectly[idx])); } - // As a stopgap measure while implementing table support, if the object file - // has table definitions or imports but no table symbols, synthesize symbols - // for those tables. Mark as NO_STRIP to ensure they reach the output file, - // even if there are no TABLE_NUMBER relocs against them. + // MVP object files can use tables, but they do not have table symbols or + // TABLE_NUMBER relocations. In order to treat tables in a uniform way in the + // linker, we detect MVP table uses if the object file has table definitions + // or imports but no table symbols. In that case we synthesize symbols for + // those tables. These table symbols are marked as NO_STRIP to ensure they + // reach the output file, even though the input has no TABLE_NUMBER relocs + // against them. + // + // As the table references in MVP object files are not relocatable, the linker + // must preserve the table number for MVP tables. In some cases this is not + // possible, and the linker will abort, but we only detect this later when + // assigning final table numbers. if (!haveTableSymbol) synthesizeTableSymbols(); } diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp --- a/lld/wasm/SymbolTable.cpp +++ b/lld/wasm/SymbolTable.cpp @@ -208,6 +208,21 @@ // FIXME: No assertions currently on the limits. } +static void precolorTableNumber(Symbol *existing, const InputFile *file, + uint32_t newNumber) { + if (TableSymbol *ts = dyn_cast(existing)) { + if (!ts->hasTableNumber()) { + ts->setTableNumber(newNumber); + } else if (newNumber != ts->getTableNumber()) { + error("Table number mismatch: " + ts->getName() + + "\n>>> assigned nonrelocatable table number " + + Twine(ts->getTableNumber()) + " in " + toString(ts->getFile()) + + "\n>>> assigned nonrelocatable table number " + Twine(newNumber) + + " in " + toString(file)); + } + } +} + static void checkDataType(const Symbol *existing, const InputFile *file) { if (!isa(existing)) reportTypeError(existing, file, WASM_SYMBOL_TYPE_DATA); @@ -457,6 +472,8 @@ } checkTableType(s, file, &table->getType()); + if (table->hasTableNumber()) + precolorTableNumber(s, file, table->getTableNumber()); if (shouldReplace(s, file, flags)) replaceSym(); diff --git a/lld/wasm/Symbols.cpp b/lld/wasm/Symbols.cpp --- a/lld/wasm/Symbols.cpp +++ b/lld/wasm/Symbols.cpp @@ -368,6 +368,8 @@ } void TableSymbol::setTableNumber(uint32_t number) { + if (const auto *t = dyn_cast(this)) + return t->table->setTableNumber(number); LLVM_DEBUG(dbgs() << "setTableNumber " << name << " -> " << number << "\n"); assert(tableNumber == INVALID_INDEX); tableNumber = number; diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp --- a/lld/wasm/SyntheticSections.cpp +++ b/lld/wasm/SyntheticSections.cpp @@ -109,6 +109,21 @@ gotSymbols.push_back(sym); } +// Some tables have "precolored" numbers -- their table numbers can't be +// relocated because their uses don't have relocations. +template +static void assignTableNumber(Table *t, uint32_t tableNumber) { + if (t->hasTableNumber()) { + if (t->getTableNumber() != tableNumber) + error("failed to assign table number " + Twine(tableNumber) + + " for table " + Twine(t->getTableNumber()) + + "; try recompiling all inputs " + "with -mattr=+reference-types"); + } else { + t->setTableNumber(tableNumber); + } +} + void ImportSection::addImport(Symbol *sym) { assert(!isSealed); importedSymbols.emplace_back(sym); @@ -119,7 +134,7 @@ else if (auto *e = dyn_cast(sym)) e->setEventIndex(numImportedEvents++); else - cast(sym)->setTableNumber(numImportedTables++); + assignTableNumber(cast(sym), numImportedTables++); } void ImportSection::writeBody() { @@ -223,7 +238,7 @@ uint32_t tableNumber = out.importSec->getNumImportedTables() + inputTables.size(); inputTables.push_back(table); - table->setTableNumber(tableNumber); + assignTableNumber(table, tableNumber); } void MemorySection::writeBody() { diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -582,6 +582,7 @@ } void Writer::calculateImports() { + std::vector deferredTables; for (Symbol *sym : symtab->getSymbols()) { if (!sym->isUndefined()) continue; @@ -591,10 +592,20 @@ continue; if (!sym->isUsedInRegularObj) continue; - if (shouldImport(sym)) { - LLVM_DEBUG(dbgs() << "import: " << sym->getName() << "\n"); - out.importSec->addImport(sym); + if (!shouldImport(sym)) + continue; + if (auto *ts = dyn_cast(sym)) { + if (!ts->hasTableNumber()) { + deferredTables.push_back(sym); + continue; + } } + LLVM_DEBUG(dbgs() << "import: " << sym->getName() << "\n"); + out.importSec->addImport(sym); + } + for (Symbol *sym : deferredTables) { + LLVM_DEBUG(dbgs() << "import: " << sym->getName() << "\n"); + out.importSec->addImport(sym); } }