Skip to content

Commit 4fc2557

Browse files
committed
[WebAssembly][lld] Preassign table number 0 to indirect function table for MVP inputs
MVP object files may import at most one table, and if they do, it must be assigned table number zero in the output, as the references to that table are not relocatable. Ensure that this is the case, even if some inputs define other tables. Differential Revision: https://reviews.llvm.org/D96001
1 parent 0fe4701 commit 4fc2557

File tree

10 files changed

+190
-95
lines changed

10 files changed

+190
-95
lines changed

lld/test/wasm/invalid-mvp-table-use.s

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
2+
#
3+
# If any table is defined or declared besides the __indirect_function_table,
4+
# the compilation unit should be compiled with -mattr=+reference-types,
5+
# causing symbol table entries to be emitted for all tables.
6+
# RUN: not wasm-ld --no-entry %t.o -o %t.wasm 2>&1 | FileCheck -check-prefix=CHECK-ERR %s
7+
8+
.global call_indirect
9+
call_indirect:
10+
.functype call_indirect () -> ()
11+
i32.const 1
12+
call_indirect () -> ()
13+
end_function
14+
15+
.globl table
16+
table:
17+
.tabletype table, externref
18+
19+
# CHECK-ERR: expected one symbol table entry for each of the 2 table(s) present, but got 1 symbol(s) instead.

lld/test/wasm/shared.ll

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ declare void @func_external()
6969
; CHECK-NEXT: Memory:
7070
; CHECK-NEXT: Initial: 0x1
7171
; CHECK-NEXT: - Module: env
72+
; CHECK-NEXT: Field: __indirect_function_table
73+
; CHECK-NEXT: Kind: TABLE
74+
; CHECK-NEXT: Table:
75+
; CHECK-NEXT: Index: 0
76+
; CHECK-NEXT: ElemType: FUNCREF
77+
; CHECK-NEXT: Limits:
78+
; CHECK-NEXT: Initial: 0x2
79+
; CHECK-NEXT: - Module: env
7280
; CHECK-NEXT: Field: __stack_pointer
7381
; CHECK-NEXT: Kind: GLOBAL
7482
; CHECK-NEXT: GlobalType: I32
@@ -87,14 +95,6 @@ declare void @func_external()
8795
; CHECK-NEXT: Field: func_external
8896
; CHECK-NEXT: Kind: FUNCTION
8997
; CHECK-NEXT: SigIndex: 1
90-
; CHECK-NEXT: - Module: env
91-
; CHECK-NEXT: Field: __indirect_function_table
92-
; CHECK-NEXT: Kind: TABLE
93-
; CHECK-NEXT: Table:
94-
; CHECK-NEXT: Index: 0
95-
; CHECK-NEXT: ElemType: FUNCREF
96-
; CHECK-NEXT: Limits:
97-
; CHECK-NEXT: Initial: 0x2
9898
; CHECK-NEXT: - Module: GOT.mem
9999
; CHECK-NEXT: Field: indirect_func
100100
; CHECK-NEXT: Kind: GLOBAL

lld/wasm/Config.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ struct Configuration {
8383
// True if we are creating position-independent code.
8484
bool isPic;
8585

86+
// True if we have an MVP input that uses __indirect_function_table and which
87+
// requires it to be allocated to table number 0.
88+
bool legacyFunctionTable = false;
89+
8690
// The table offset at which to place function addresses. We reserve zero
8791
// for the null function pointer. This gets set to 1 for executables and 0
8892
// for shared libraries (since they always added to a dynamic offset at

lld/wasm/Driver.cpp

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -815,27 +815,26 @@ static TableSymbol *createUndefinedIndirectFunctionTable(StringRef name) {
815815
}
816816

817817
static TableSymbol *resolveIndirectFunctionTable() {
818-
Symbol *existingTable = symtab->find(functionTableName);
819-
if (existingTable) {
820-
if (!isa<TableSymbol>(existingTable)) {
818+
Symbol *existing = symtab->find(functionTableName);
819+
if (existing) {
820+
if (!isa<TableSymbol>(existing)) {
821821
error(Twine("reserved symbol must be of type table: `") +
822822
functionTableName + "`");
823823
return nullptr;
824824
}
825-
if (existingTable->isDefined()) {
825+
if (existing->isDefined()) {
826826
error(Twine("reserved symbol must not be defined in input files: `") +
827827
functionTableName + "`");
828828
return nullptr;
829829
}
830830
}
831831

832832
if (config->importTable) {
833-
if (existingTable)
834-
return cast<TableSymbol>(existingTable);
833+
if (existing)
834+
return cast<TableSymbol>(existing);
835835
else
836836
return createUndefinedIndirectFunctionTable(functionTableName);
837-
} else if ((existingTable && existingTable->isLive()) ||
838-
config->exportTable) {
837+
} else if ((existing && existing->isLive()) || config->exportTable) {
839838
// A defined table is required. Either because the user request an exported
840839
// table or because the table symbol is already live. The existing table is
841840
// guaranteed to be undefined due to the check above.

lld/wasm/InputFiles.cpp

Lines changed: 100 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -308,69 +308,109 @@ static void setRelocs(const std::vector<T *> &chunks,
308308
}
309309
}
310310

311-
// Since LLVM 12, we expect that if an input file defines or uses a table, it
312-
// declares the tables using symbols and records each use with a relocation.
313-
// This way when the linker combines inputs, it can collate the tables used by
314-
// the inputs, assigning them distinct table numbers, and renumber all the uses
315-
// as appropriate. At the same time, the linker has special logic to build the
311+
// An object file can have two approaches to tables. With the reference-types
312+
// feature enabled, input files that define or use tables declare the tables
313+
// using symbols, and record each use with a relocation. This way when the
314+
// linker combines inputs, it can collate the tables used by the inputs,
315+
// assigning them distinct table numbers, and renumber all the uses as
316+
// appropriate. At the same time, the linker has special logic to build the
316317
// indirect function table if it is needed.
317318
//
318-
// However, object files produced by LLVM 11 and earlier neither write table
319-
// symbols nor record relocations, and yet still use tables via call_indirect,
320-
// and via function pointer bitcasts. We can detect these object files, as they
321-
// declare tables as imports or define them locally, but don't have table
322-
// symbols. synthesizeTableSymbols serves as a shim when loading these older
323-
// input files, defining the missing symbols to allow the indirect function
324-
// table to be built.
319+
// However, MVP object files (those that target WebAssembly 1.0, the "minimum
320+
// viable product" version of WebAssembly) neither write table symbols nor
321+
// record relocations. These files can have at most one table, the indirect
322+
// function table used by call_indirect and which is the address space for
323+
// function pointers. If this table is present, it is always an import. If we
324+
// have a file with a table import but no table symbols, it is an MVP object
325+
// file. synthesizeMVPIndirectFunctionTableSymbolIfNeeded serves as a shim when
326+
// loading these input files, defining the missing symbol to allow the indirect
327+
// function table to be built.
325328
//
326-
// Table uses in these older files won't be relocated, as they have no
327-
// relocations. In practice this isn't a problem, as these object files
328-
// typically just declare a single table named __indirect_function_table and
329-
// having table number 0, so relocation would be idempotent anyway.
330-
void ObjFile::synthesizeTableSymbols() {
331-
uint32_t tableNumber = 0;
332-
const WasmGlobalType *globalType = nullptr;
333-
const WasmEventType *eventType = nullptr;
334-
const WasmSignature *signature = nullptr;
335-
if (wasmObj->getNumImportedTables()) {
336-
for (const auto &import : wasmObj->imports()) {
337-
if (import.Kind == WASM_EXTERNAL_TABLE) {
338-
auto *info = make<WasmSymbolInfo>();
339-
info->Name = import.Field;
340-
info->Kind = WASM_SYMBOL_TYPE_TABLE;
341-
info->ImportModule = import.Module;
342-
info->ImportName = import.Field;
343-
info->Flags = WASM_SYMBOL_UNDEFINED;
344-
info->Flags |= WASM_SYMBOL_NO_STRIP;
345-
info->ElementIndex = tableNumber++;
346-
LLVM_DEBUG(dbgs() << "Synthesizing symbol for table import: "
347-
<< info->Name << "\n");
348-
auto *wasmSym = make<WasmSymbol>(*info, globalType, &import.Table,
349-
eventType, signature);
350-
symbols.push_back(createUndefined(*wasmSym, false));
351-
// Because there are no TABLE_NUMBER relocs in this case, we can't
352-
// compute accurate liveness info; instead, just mark the symbol as
353-
// always live.
354-
symbols.back()->markLive();
355-
}
329+
// As indirect function table table usage in MVP objects cannot be relocated,
330+
// the linker must ensure that this table gets assigned index zero.
331+
void ObjFile::addLegacyIndirectFunctionTableIfNeeded(
332+
uint32_t tableSymbolCount) {
333+
uint32_t tableCount = wasmObj->getNumImportedTables() + tables.size();
334+
335+
// If there are symbols for all tables, then all is good.
336+
if (tableCount == tableSymbolCount)
337+
return;
338+
339+
// It's possible for an input to define tables and also use the indirect
340+
// function table, but forget to compile with -mattr=+reference-types.
341+
// For these newer files, we require symbols for all tables, and
342+
// relocations for all of their uses.
343+
if (tableSymbolCount != 0) {
344+
error(toString(this) +
345+
": expected one symbol table entry for each of the " +
346+
Twine(tableCount) + " table(s) present, but got " +
347+
Twine(tableSymbolCount) + " symbol(s) instead.");
348+
return;
349+
}
350+
351+
// An MVP object file can have up to one table import, for the indirect
352+
// function table, but will have no table definitions.
353+
if (tables.size()) {
354+
error(toString(this) +
355+
": unexpected table definition(s) without corresponding "
356+
"symbol-table entries.");
357+
return;
358+
}
359+
360+
// An MVP object file can have only one table import.
361+
if (tableCount != 1) {
362+
error(toString(this) +
363+
": multiple table imports, but no corresponding symbol-table "
364+
"entries.");
365+
return;
366+
}
367+
368+
const WasmImport *tableImport = nullptr;
369+
for (const auto &import : wasmObj->imports()) {
370+
if (import.Kind == WASM_EXTERNAL_TABLE) {
371+
assert(!tableImport);
372+
tableImport = &import;
356373
}
357374
}
358-
for (const auto &table : tables) {
359-
auto *info = make<llvm::wasm::WasmSymbolInfo>();
360-
// Empty name.
361-
info->Kind = WASM_SYMBOL_TYPE_TABLE;
362-
info->Flags = WASM_SYMBOL_BINDING_LOCAL;
363-
info->Flags |= WASM_SYMBOL_VISIBILITY_HIDDEN;
364-
info->Flags |= WASM_SYMBOL_NO_STRIP;
365-
info->ElementIndex = tableNumber++;
366-
LLVM_DEBUG(dbgs() << "Synthesizing symbol for table definition: "
367-
<< info->Name << "\n");
368-
auto *wasmSym = make<WasmSymbol>(*info, globalType, &table->getType(),
369-
eventType, signature);
370-
symbols.push_back(createDefined(*wasmSym));
371-
// Mark live, for the same reasons as for imported tables.
372-
symbols.back()->markLive();
375+
assert(tableImport);
376+
377+
// We can only synthesize a symtab entry for the indirect function table; if
378+
// it has an unexpected name or type, assume that it's not actually the
379+
// indirect function table.
380+
if (tableImport->Field != functionTableName ||
381+
tableImport->Table.ElemType != uint8_t(ValType::FUNCREF)) {
382+
error(toString(this) + ": table import " + Twine(tableImport->Field) +
383+
" is missing a symbol table entry.");
384+
return;
373385
}
386+
387+
auto *info = make<WasmSymbolInfo>();
388+
info->Name = tableImport->Field;
389+
info->Kind = WASM_SYMBOL_TYPE_TABLE;
390+
info->ImportModule = tableImport->Module;
391+
info->ImportName = tableImport->Field;
392+
info->Flags = WASM_SYMBOL_UNDEFINED;
393+
info->Flags |= WASM_SYMBOL_NO_STRIP;
394+
info->ElementIndex = 0;
395+
LLVM_DEBUG(dbgs() << "Synthesizing symbol for table import: " << info->Name
396+
<< "\n");
397+
const WasmGlobalType *globalType = nullptr;
398+
const WasmEventType *eventType = nullptr;
399+
const WasmSignature *signature = nullptr;
400+
auto *wasmSym = make<WasmSymbol>(*info, globalType, &tableImport->Table,
401+
eventType, signature);
402+
Symbol *sym = createUndefined(*wasmSym, false);
403+
// We're only sure it's a TableSymbol if the createUndefined succeeded.
404+
if (errorCount())
405+
return;
406+
symbols.push_back(sym);
407+
// Because there are no TABLE_NUMBER relocs, we can't compute accurate
408+
// liveness info; instead, just mark the symbol as always live.
409+
sym->markLive();
410+
411+
// We assume that this compilation unit has unrelocatable references to
412+
// this table.
413+
config->legacyFunctionTable = true;
374414
}
375415

376416
void ObjFile::parse(bool ignoreComdats) {
@@ -487,11 +527,11 @@ void ObjFile::parse(bool ignoreComdats) {
487527

488528
// Populate `Symbols` based on the symbols in the object.
489529
symbols.reserve(wasmObj->getNumberOfSymbols());
490-
bool haveTableSymbol = false;
530+
uint32_t tableSymbolCount = 0;
491531
for (const SymbolRef &sym : wasmObj->symbols()) {
492532
const WasmSymbol &wasmSym = wasmObj->getWasmSymbol(sym.getRawDataRefImpl());
493533
if (wasmSym.isTypeTable())
494-
haveTableSymbol = true;
534+
tableSymbolCount++;
495535
if (wasmSym.isDefined()) {
496536
// createDefined may fail if the symbol is comdat excluded in which case
497537
// we fall back to creating an undefined symbol
@@ -504,12 +544,7 @@ void ObjFile::parse(bool ignoreComdats) {
504544
symbols.push_back(createUndefined(wasmSym, isCalledDirectly[idx]));
505545
}
506546

507-
// As a stopgap measure while implementing table support, if the object file
508-
// has table definitions or imports but no table symbols, synthesize symbols
509-
// for those tables. Mark as NO_STRIP to ensure they reach the output file,
510-
// even if there are no TABLE_NUMBER relocs against them.
511-
if (!haveTableSymbol)
512-
synthesizeTableSymbols();
547+
addLegacyIndirectFunctionTableIfNeeded(tableSymbolCount);
513548
}
514549

515550
bool ObjFile::isExcludedByComdat(InputChunk *chunk) const {

lld/wasm/InputFiles.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ class ObjFile : public InputFile {
157157
Symbol *createUndefined(const WasmSymbol &sym, bool isCalledDirectly);
158158

159159
bool isExcludedByComdat(InputChunk *chunk) const;
160-
void synthesizeTableSymbols();
160+
void addLegacyIndirectFunctionTableIfNeeded(uint32_t tableSymbolCount);
161161

162162
std::unique_ptr<WasmObjectFile> wasmObj;
163163
};

lld/wasm/Symbols.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,8 @@ uint32_t TableSymbol::getTableNumber() const {
366366
}
367367

368368
void TableSymbol::setTableNumber(uint32_t number) {
369+
if (const auto *t = dyn_cast<DefinedTable>(this))
370+
return t->table->assignIndex(number);
369371
LLVM_DEBUG(dbgs() << "setTableNumber " << name << " -> " << number << "\n");
370372
assert(tableNumber == INVALID_INDEX);
371373
tableNumber = number;

lld/wasm/SyntheticSections.cpp

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,34 @@ void TableSection::writeBody() {
218218
void TableSection::addTable(InputTable *table) {
219219
if (!table->live)
220220
return;
221-
uint32_t tableNumber =
222-
out.importSec->getNumImportedTables() + inputTables.size();
221+
// Some inputs require that the indirect function table be assigned to table
222+
// number 0.
223+
if (config->legacyFunctionTable &&
224+
isa<DefinedTable>(WasmSym::indirectFunctionTable) &&
225+
cast<DefinedTable>(WasmSym::indirectFunctionTable)->table == table) {
226+
if (out.importSec->getNumImportedTables()) {
227+
// Alack! Some other input imported a table, meaning that we are unable
228+
// to assign table number 0 to the indirect function table.
229+
for (const auto *culprit : out.importSec->importedSymbols) {
230+
if (isa<UndefinedTable>(culprit)) {
231+
error("object file not built with 'reference-types' feature "
232+
"conflicts with import of table " +
233+
culprit->getName() + "by file " + toString(culprit->getFile()));
234+
return;
235+
}
236+
}
237+
llvm_unreachable("failed to find conflicting table import");
238+
}
239+
inputTables.insert(inputTables.begin(), table);
240+
return;
241+
}
223242
inputTables.push_back(table);
224-
table->assignIndex(tableNumber);
243+
}
244+
245+
void TableSection::assignIndexes() {
246+
uint32_t tableNumber = out.importSec->getNumImportedTables();
247+
for (InputTable *t : inputTables)
248+
t->assignIndex(tableNumber++);
225249
}
226250

227251
void MemorySection::writeBody() {

lld/wasm/SyntheticSections.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ class TableSection : public SyntheticSection {
151151
TableSection() : SyntheticSection(llvm::wasm::WASM_SEC_TABLE) {}
152152

153153
bool isNeeded() const override { return inputTables.size() > 0; };
154+
void assignIndexes() override;
154155
void writeBody() override;
155156
void addTable(InputTable *table);
156157

0 commit comments

Comments
 (0)