}
}
-// 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, 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 pointers. 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. synthesizeMVPIndirectFunctionTableSymbolIfNeeded 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<WasmSymbolInfo>();
- 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<WasmSymbol>(*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();
- }
+// As indirect function table table usage in MVP objects cannot be relocated,
+// the linker must ensure that this table gets assigned index zero.
+void ObjFile::addLegacyIndirectFunctionTableIfNeeded(
+ uint32_t tableSymbolCount) {
+ uint32_t tableCount = wasmObj->getNumImportedTables() + tables.size();
+
+ // If there are symbols for all tables, then all is good.
+ if (tableCount == tableSymbolCount)
+ return;
+
+ // It's possible for an input to define tables and also use the indirect
+ // function table, but forget to compile with -mattr=+reference-types.
+ // For these newer files, we require symbols for all tables, and
+ // relocations for all of their uses.
+ if (tableSymbolCount != 0) {
+ error(toString(this) +
+ ": expected one symbol table entry for each of the " +
+ Twine(tableCount) + " table(s) present, but got " +
+ Twine(tableSymbolCount) + " symbol(s) instead.");
+ return;
+ }
+
+ // An MVP object file can have up to one table import, for the indirect
+ // function table, but will have no table definitions.
+ if (tables.size()) {
+ error(toString(this) +
+ ": unexpected table definition(s) without corresponding "
+ "symbol-table entries.");
+ return;
+ }
+
+ // An MVP object file can have only one table import.
+ if (tableCount != 1) {
+ error(toString(this) +
+ ": multiple table imports, but no corresponding symbol-table "
+ "entries.");
+ return;
+ }
+
+ const WasmImport *tableImport = nullptr;
+ for (const auto &import : wasmObj->imports()) {
+ if (import.Kind == WASM_EXTERNAL_TABLE) {
+ assert(!tableImport);
+ tableImport = &import;
}
}
- for (const auto &table : tables) {
- auto *info = make<llvm::wasm::WasmSymbolInfo>();
- // 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<WasmSymbol>(*info, globalType, &table->getType(),
- eventType, signature);
- symbols.push_back(createDefined(*wasmSym));
- // Mark live, for the same reasons as for imported tables.
- symbols.back()->markLive();
+ assert(tableImport);
+
+ // We can only synthesize a symtab entry for the indirect function table; if
+ // it has an unexpected name or type, assume that it's not actually the
+ // indirect function table.
+ if (tableImport->Field != functionTableName ||
+ tableImport->Table.ElemType != uint8_t(ValType::FUNCREF)) {
+ error(toString(this) + ": table import " + Twine(tableImport->Field) +
+ " is missing a symbol table entry.");
+ return;
}
+
+ auto *info = make<WasmSymbolInfo>();
+ info->Name = tableImport->Field;
+ info->Kind = WASM_SYMBOL_TYPE_TABLE;
+ info->ImportModule = tableImport->Module;
+ info->ImportName = tableImport->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<WasmSymbol>(*info, globalType, &tableImport->Table,
+ eventType, signature);
+ Symbol *sym = createUndefined(*wasmSym, false);
+ // We're only sure it's a TableSymbol if the createUndefined succeeded.
+ if (errorCount())
+ return;
+ 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.
+ config->legacyFunctionTable = true;
}
void ObjFile::parse(bool ignoreComdats) {
// Populate `Symbols` based on the symbols in the object.
symbols.reserve(wasmObj->getNumberOfSymbols());
- bool haveTableSymbol = false;
+ uint32_t tableSymbolCount = 0;
for (const SymbolRef &sym : wasmObj->symbols()) {
const WasmSymbol &wasmSym = wasmObj->getWasmSymbol(sym.getRawDataRefImpl());
if (wasmSym.isTypeTable())
- haveTableSymbol = true;
+ tableSymbolCount++;
if (wasmSym.isDefined()) {
// createDefined may fail if the symbol is comdat excluded in which case
// we fall back to creating an undefined symbol
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();
+ addLegacyIndirectFunctionTableIfNeeded(tableSymbolCount);
}
bool ObjFile::isExcludedByComdat(InputChunk *chunk) const {