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,10 @@ TableSymbol *sym = symtab->addSyntheticTable(name, flags, table); sym->markLive(); sym->forceExport = config->exportTable; + // If the indirect function table is used by MVP inputs, make sure it is + // assigned table number 0. + if (existing && cast(existing)->hasPreassignedTableNumberZero()) + sym->preassignTableNumberZero(); return sym; } @@ -841,7 +846,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.h b/lld/wasm/InputFiles.h --- a/lld/wasm/InputFiles.h +++ b/lld/wasm/InputFiles.h @@ -157,7 +157,7 @@ Symbol *createUndefined(const WasmSymbol &sym, bool isCalledDirectly); bool isExcludedByComdat(InputChunk *chunk) const; - void synthesizeTableSymbols(); + void synthesizeMVPIndirectFunctionTableSymbol(); std::unique_ptr wasmObj; }; diff --git a/lld/wasm/InputFiles.cpp b/lld/wasm/InputFiles.cpp --- a/lld/wasm/InputFiles.cpp +++ b/lld/wasm/InputFiles.cpp @@ -310,68 +310,79 @@ } } -// 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 +// However, MVP object files (those that target WebAssembly 1.0, the "minimum +// viable product" version of WebAssembly) neither write table symbols nor +// record relocations. These files can have at most one table, the indirect +// function table used by call_indirect and which is the address space for +// function points. If this table is present, it is always an import. If we +// have a file with a table import but no table symbols, it is an MVP object +// file. synthesizeIndirectFunctionTableSymbol serves as a shim when loading +// these input files, defining the missing symbol 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. -void ObjFile::synthesizeTableSymbols() { - uint32_t tableNumber = 0; - const WasmGlobalType *globalType = nullptr; - const WasmEventType *eventType = nullptr; - const WasmSignature *signature = nullptr; - if (wasmObj->getNumImportedTables()) { - for (const auto &import : wasmObj->imports()) { - if (import.Kind == WASM_EXTERNAL_TABLE) { - auto *info = make(); - info->Name = import.Field; - info->Kind = WASM_SYMBOL_TYPE_TABLE; - info->ImportModule = import.Module; - info->ImportName = import.Field; - info->Flags = WASM_SYMBOL_UNDEFINED; - info->Flags |= WASM_SYMBOL_NO_STRIP; - 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(); +// 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::synthesizeMVPIndirectFunctionTableSymbol() { + if (errorCount()) + return; + + bool haveTable = false; + // An MVP input has at most one table input. + for (const auto &import : wasmObj->imports()) { + if (import.Kind == WASM_EXTERNAL_TABLE) { + if (haveTable) { + error(toString(this) + + ": multiple table imports, but no " + "corresponding symbol-table entries. Recompile this input with " + "-mattr=+reference-types."); + continue; } + haveTable = true; + + auto *info = make(); + info->Name = import.Field; + info->Kind = WASM_SYMBOL_TYPE_TABLE; + info->ImportModule = import.Module; + info->ImportName = import.Field; + info->Flags = WASM_SYMBOL_UNDEFINED; + info->Flags |= WASM_SYMBOL_NO_STRIP; + info->ElementIndex = 0; + LLVM_DEBUG(dbgs() << "Synthesizing symbol for table import: " + << info->Name << "\n"); + const WasmGlobalType *globalType = nullptr; + const WasmEventType *eventType = nullptr; + const WasmSignature *signature = nullptr; + auto *wasmSym = make(*info, globalType, &import.Table, + eventType, signature); + Symbol *sym = createUndefined(*wasmSym, false); + // We're only sure it's a TableSymbol if the createUndefined succeeded. + if (errorCount()) + continue; + symbols.push_back(sym); + // Because there are no TABLE_NUMBER relocs, we can't compute accurate + // liveness info; instead, just mark the symbol as always live. + sym->markLive(); + // We assume that this compilation unit has unrelocatable references to + // this table, so preassign this table's table number. + cast(sym)->preassignTableNumberZero(); } } - for (const auto &table : tables) { - auto *info = make(); - // Empty name. - info->Kind = WASM_SYMBOL_TYPE_TABLE; - info->Flags = WASM_SYMBOL_BINDING_LOCAL; - info->Flags |= WASM_SYMBOL_VISIBILITY_HIDDEN; - info->Flags |= WASM_SYMBOL_NO_STRIP; - 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(); + + // An MVP input has no table definitions. + if (!tables.empty()) { + error(toString(this) + + ": unexpected table definition(s) without " + "corresponding symbol-table entry. Recompile this input with " + "-mattr=+reference-types."); } } @@ -506,12 +517,8 @@ 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. if (!haveTableSymbol) - synthesizeTableSymbols(); + synthesizeMVPIndirectFunctionTableSymbol(); } bool ObjFile::isExcludedByComdat(InputChunk *chunk) const { diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h --- a/lld/wasm/Symbols.h +++ b/lld/wasm/Symbols.h @@ -378,6 +378,9 @@ void setTableNumber(uint32_t number); bool hasTableNumber() const; + void preassignTableNumberZero(); + bool hasPreassignedTableNumberZero() const; + protected: TableSymbol(StringRef name, Kind k, uint32_t flags, InputFile *f, const WasmTableType *type) 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; @@ -379,6 +381,19 @@ return tableNumber != INVALID_INDEX; } +void TableSymbol::preassignTableNumberZero() { + if (!hasPreassignedTableNumberZero()) + setTableNumber(0); +} + +bool TableSymbol::hasPreassignedTableNumberZero() const { + if (hasTableNumber()) { + assert(getTableNumber() == 0); + return true; + } + return false; +} + DefinedTable::DefinedTable(StringRef name, uint32_t flags, InputFile *file, InputTable *table) : TableSymbol(name, DefinedTableKind, flags, file, diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp --- a/lld/wasm/SyntheticSections.cpp +++ b/lld/wasm/SyntheticSections.cpp @@ -118,8 +118,16 @@ g->setGlobalIndex(numImportedGlobals++); else if (auto *e = dyn_cast(sym)) e->setEventIndex(numImportedEvents++); - else - cast(sym)->setTableNumber(numImportedTables++); + else { + // It is always possible to preserve preassigned table number 0 for imported + // tables, as imported tables are always numbered from 0. + TableSymbol *t = cast(sym); + if (t->hasPreassignedTableNumberZero()) + assert(numImportedTables == 0); + else + t->setTableNumber(numImportedTables); + numImportedTables++; + } } void ImportSection::writeBody() { @@ -223,7 +231,27 @@ uint32_t tableNumber = out.importSec->getNumImportedTables() + inputTables.size(); inputTables.push_back(table); - table->setTableNumber(tableNumber); + if (table->hasTableNumber()) { + // If a reftypes input has imported tables, any indirect function table + // referenced by an MVP input and preassigned to table number 0 will no + // longer work as the imported tables get the lowest table numbers (unless + // for some reason the indirect function table is itself imported). + assert(table->getTableNumber() == 0); + if (tableNumber != 0) { + // Of the declarations of __indirect_function_table in the various inputs, + // we don't know which one is responsible for preassigning the table + // number. Probably here we should grovel the imports to determine which + // input file imported a table. + error(toString(table->file) + ": " + table->getName() + + ": Cannot " + "preassign table number 0 to indirect function table, because " + "object files compiled with -mattr=+reference-types declared " + "table imports. Suggest recompiling all inputs with " + "-mattr=+reference-types."); + } + } else { + table->setTableNumber(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,22 @@ 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)) { + // Ensure that an indirect function table with a preassigned table number + // of 0 gets written as the first table import. + if (!ts->hasPreassignedTableNumberZero()) { + 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); } }