diff --git a/lld/test/wasm/alias.s b/lld/test/wasm/alias.s --- a/lld/test/wasm/alias.s +++ b/lld/test/wasm/alias.s @@ -22,14 +22,6 @@ # CHECK-NEXT: ReturnTypes: [] # CHECK-NEXT: - Type: FUNCTION # CHECK-NEXT: FunctionTypes: [ 0 ] -# CHECK-NEXT: - Type: TABLE -# CHECK-NEXT: Tables: -# CHECK-NEXT: - Index: 0 -# CHECK-NEXT: ElemType: FUNCREF -# CHECK-NEXT: Limits: -# CHECK-NEXT: Flags: [ HAS_MAX ] -# CHECK-NEXT: Initial: 0x1 -# CHECK-NEXT: Maximum: 0x1 # CHECK-NEXT: - Type: MEMORY # CHECK-NEXT: Memories: # CHECK-NEXT: - Initial: 0x2 diff --git a/lld/test/wasm/export-table-explicit.test b/lld/test/wasm/export-table-explicit.test new file mode 100644 --- /dev/null +++ b/lld/test/wasm/export-table-explicit.test @@ -0,0 +1,31 @@ +# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %p/Inputs/start.s -o %t.start.o +# RUN: wasm-ld --export-table -o %t.wasm %t.start.o +# RUN: obj2yaml %t.wasm | FileCheck %s + +# Verify the interaction between --export-table and declared tables + +.globl __indirect_function_table +.tabletype __indirect_function_table,externref + +# CHECK: - Type: TABLE +# CHECK-NEXT: Tables: +# CHECK-NEXT: - Index: 0 +# CHECK-NEXT: ElemType: FUNCREF +# CHECK-NEXT: Limits: +# CHECK-NEXT: Flags: [ HAS_MAX ] +# CHECK-NEXT: Initial: 0x1 +# CHECK-NEXT: Maximum: 0x1 +# CHECK-NEXT: - Type: + +# CHECK: - Type: EXPORT +# CHECK-NEXT: Exports: +# CHECK-NEXT: - Name: memory +# CHECK-NEXT: Kind: MEMORY +# CHECK-NEXT: Index: 0 +# CHECK-NEXT: - Name: _start +# CHECK-NEXT: Kind: FUNCTION +# CHECK-NEXT: Index: 0 +# CHECK-NEXT: - Name: __indirect_function_table +# CHECK-NEXT: Kind: TABLE +# CHECK-NEXT: Index: 0 +# CHECK-NEXT: - Type: diff --git a/lld/test/wasm/init-fini.ll b/lld/test/wasm/init-fini.ll --- a/lld/test/wasm/init-fini.ll +++ b/lld/test/wasm/init-fini.ll @@ -139,15 +139,15 @@ ; RELOC-NEXT: InitFunctions [ ; RELOC-NEXT: 0 (priority=101) ; RELOC-NEXT: 1 (priority=101) -; RELOC-NEXT: 14 (priority=101) -; RELOC-NEXT: 10 (priority=101) -; RELOC-NEXT: 20 (priority=101) -; RELOC-NEXT: 10 (priority=202) -; RELOC-NEXT: 22 (priority=202) +; RELOC-NEXT: 15 (priority=101) +; RELOC-NEXT: 11 (priority=101) +; RELOC-NEXT: 21 (priority=101) +; RELOC-NEXT: 11 (priority=202) +; RELOC-NEXT: 23 (priority=202) ; RELOC-NEXT: 0 (priority=1001) -; RELOC-NEXT: 16 (priority=1001) -; RELOC-NEXT: 10 (priority=2002) -; RELOC-NEXT: 24 (priority=2002) +; RELOC-NEXT: 17 (priority=1001) +; RELOC-NEXT: 11 (priority=2002) +; RELOC-NEXT: 25 (priority=2002) ; RELOC-NEXT: 9 (priority=4000) -; RELOC-NEXT: 18 (priority=4000) +; RELOC-NEXT: 19 (priority=4000) ; RELOC-NEXT: ] diff --git a/lld/test/wasm/local-symbols.ll b/lld/test/wasm/local-symbols.ll --- a/lld/test/wasm/local-symbols.ll +++ b/lld/test/wasm/local-symbols.ll @@ -35,14 +35,6 @@ ; CHECK-NEXT: ReturnTypes: [] ; CHECK-NEXT: - Type: FUNCTION ; CHECK-NEXT: FunctionTypes: [ 0, 1 ] -; CHECK-NEXT: - Type: TABLE -; CHECK-NEXT: Tables: -; CHECK-NEXT: - Index: 0 -; CHECK-NEXT: ElemType: FUNCREF -; CHECK-NEXT: Limits: -; CHECK-NEXT: Flags: [ HAS_MAX ] -; CHECK-NEXT: Initial: 0x1 -; CHECK-NEXT: Maximum: 0x1 ; CHECK-NEXT: - Type: MEMORY ; CHECK-NEXT: Memories: ; CHECK-NEXT: - Initial: 0x2 diff --git a/lld/test/wasm/locals-duplicate.test b/lld/test/wasm/locals-duplicate.test --- a/lld/test/wasm/locals-duplicate.test +++ b/lld/test/wasm/locals-duplicate.test @@ -254,40 +254,40 @@ ; RELOC-NEXT: - Type: CODE ; RELOC-NEXT: Relocations: ; RELOC-NEXT: - Type: R_WASM_MEMORY_ADDR_SLEB -; RELOC-NEXT: Index: 18 +; RELOC-NEXT: Index: 19 ; RELOC-NEXT: Offset: 0x13 ; RELOC-NEXT: - Type: R_WASM_MEMORY_ADDR_SLEB ; RELOC-NEXT: Index: 3 ; RELOC-NEXT: Offset: 0x1C ; RELOC-NEXT: - Type: R_WASM_MEMORY_ADDR_SLEB -; RELOC-NEXT: Index: 19 +; RELOC-NEXT: Index: 20 ; RELOC-NEXT: Offset: 0x25 ; RELOC-NEXT: - Type: R_WASM_TABLE_INDEX_SLEB -; RELOC-NEXT: Index: 16 +; RELOC-NEXT: Index: 17 ; RELOC-NEXT: Offset: 0x2E ; RELOC-NEXT: - Type: R_WASM_TABLE_INDEX_SLEB ; RELOC-NEXT: Index: 0 ; RELOC-NEXT: Offset: 0x37 ; RELOC-NEXT: - Type: R_WASM_TABLE_INDEX_SLEB -; RELOC-NEXT: Index: 17 +; RELOC-NEXT: Index: 18 ; RELOC-NEXT: Offset: 0x40 ; RELOC-NEXT: - Type: R_WASM_MEMORY_ADDR_SLEB -; RELOC-NEXT: Index: 10 +; RELOC-NEXT: Index: 11 ; RELOC-NEXT: Offset: 0x58 ; RELOC-NEXT: - Type: R_WASM_MEMORY_ADDR_SLEB -; RELOC-NEXT: Index: 22 +; RELOC-NEXT: Index: 23 ; RELOC-NEXT: Offset: 0x61 ; RELOC-NEXT: - Type: R_WASM_MEMORY_ADDR_SLEB -; RELOC-NEXT: Index: 23 +; RELOC-NEXT: Index: 24 ; RELOC-NEXT: Offset: 0x6A ; RELOC-NEXT: - Type: R_WASM_TABLE_INDEX_SLEB -; RELOC-NEXT: Index: 8 +; RELOC-NEXT: Index: 9 ; RELOC-NEXT: Offset: 0x73 ; RELOC-NEXT: - Type: R_WASM_TABLE_INDEX_SLEB -; RELOC-NEXT: Index: 20 +; RELOC-NEXT: Index: 21 ; RELOC-NEXT: Offset: 0x7C ; RELOC-NEXT: - Type: R_WASM_TABLE_INDEX_SLEB -; RELOC-NEXT: Index: 21 +; RELOC-NEXT: Index: 22 ; RELOC-NEXT: Offset: 0x85 ; RELOC-NEXT: Functions: ; RELOC-NEXT: - Index: 0 @@ -410,87 +410,92 @@ ; RELOC-NEXT: Flags: [ ] ; RELOC-NEXT: Function: 8 ; RELOC-NEXT: - Index: 8 +; RELOC-NEXT: Kind: TABLE +; RELOC-NEXT: Name: __indirect_function_table +; RELOC-NEXT: Flags: [ VISIBILITY_HIDDEN ] +; RELOC-NEXT: Table: 0 +; RELOC-NEXT: - Index: 9 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: colliding_func1 ; RELOC-NEXT: Flags: [ ] ; RELOC-NEXT: Function: 9 -; RELOC-NEXT: - Index: 9 +; RELOC-NEXT: - Index: 10 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: get_global1B ; RELOC-NEXT: Flags: [ ] ; RELOC-NEXT: Function: 12 -; RELOC-NEXT: - Index: 10 +; RELOC-NEXT: - Index: 11 ; RELOC-NEXT: Kind: DATA ; RELOC-NEXT: Name: colliding_global1 ; RELOC-NEXT: Flags: [ ] ; RELOC-NEXT: Segment: 0 ; RELOC-NEXT: Offset: 4 ; RELOC-NEXT: Size: 4 -; RELOC-NEXT: - Index: 11 +; RELOC-NEXT: - Index: 12 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: get_global2B ; RELOC-NEXT: Flags: [ ] ; RELOC-NEXT: Function: 13 -; RELOC-NEXT: - Index: 12 +; RELOC-NEXT: - Index: 13 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: get_global3B ; RELOC-NEXT: Flags: [ ] ; RELOC-NEXT: Function: 14 -; RELOC-NEXT: - Index: 13 +; RELOC-NEXT: - Index: 14 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: get_func1B ; RELOC-NEXT: Flags: [ ] ; RELOC-NEXT: Function: 15 -; RELOC-NEXT: - Index: 14 +; RELOC-NEXT: - Index: 15 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: get_func2B ; RELOC-NEXT: Flags: [ ] ; RELOC-NEXT: Function: 16 -; RELOC-NEXT: - Index: 15 +; RELOC-NEXT: - Index: 16 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: get_func3B ; RELOC-NEXT: Flags: [ ] ; RELOC-NEXT: Function: 17 -; RELOC-NEXT: - Index: 16 +; RELOC-NEXT: - Index: 17 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: colliding_func1 ; RELOC-NEXT: Flags: [ BINDING_LOCAL ] ; RELOC-NEXT: Function: 0 -; RELOC-NEXT: - Index: 17 +; RELOC-NEXT: - Index: 18 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: colliding_func3 ; RELOC-NEXT: Flags: [ BINDING_LOCAL ] ; RELOC-NEXT: Function: 2 -; RELOC-NEXT: - Index: 18 +; RELOC-NEXT: - Index: 19 ; RELOC-NEXT: Kind: DATA ; RELOC-NEXT: Name: colliding_global1 ; RELOC-NEXT: Flags: [ BINDING_LOCAL ] ; RELOC-NEXT: Segment: 0 ; RELOC-NEXT: Size: 4 -; RELOC-NEXT: - Index: 19 +; RELOC-NEXT: - Index: 20 ; RELOC-NEXT: Kind: DATA ; RELOC-NEXT: Name: colliding_global3 ; RELOC-NEXT: Flags: [ BINDING_LOCAL ] ; RELOC-NEXT: Segment: 2 ; RELOC-NEXT: Size: 4 -; RELOC-NEXT: - Index: 20 +; RELOC-NEXT: - Index: 21 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: colliding_func2 ; RELOC-NEXT: Flags: [ BINDING_LOCAL ] ; RELOC-NEXT: Function: 10 -; RELOC-NEXT: - Index: 21 +; RELOC-NEXT: - Index: 22 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: colliding_func3 ; RELOC-NEXT: Flags: [ BINDING_LOCAL ] ; RELOC-NEXT: Function: 11 -; RELOC-NEXT: - Index: 22 +; RELOC-NEXT: - Index: 23 ; RELOC-NEXT: Kind: DATA ; RELOC-NEXT: Name: colliding_global2 ; RELOC-NEXT: Flags: [ BINDING_LOCAL ] ; RELOC-NEXT: Segment: 1 ; RELOC-NEXT: Offset: 4 ; RELOC-NEXT: Size: 4 -; RELOC-NEXT: - Index: 23 +; RELOC-NEXT: - Index: 24 ; RELOC-NEXT: Kind: DATA ; RELOC-NEXT: Name: colliding_global3 ; RELOC-NEXT: Flags: [ BINDING_LOCAL ] diff --git a/lld/test/wasm/pie.ll b/lld/test/wasm/pie.ll --- a/lld/test/wasm/pie.ll +++ b/lld/test/wasm/pie.ll @@ -41,14 +41,6 @@ ; CHECK: - Type: IMPORT ; CHECK-NEXT: Imports: ; CHECK-NEXT: - Module: env -; CHECK-NEXT: Field: __indirect_function_table -; CHECK-NEXT: Kind: TABLE -; CHECK-NEXT: Table: -; CHECK-NEXT: Index: 0 -; CHECK-NEXT: ElemType: FUNCREF -; CHECK-NEXT: Limits: -; CHECK-NEXT: Initial: 0x1 -; CHECK-NEXT: - Module: env ; CHECK-NEXT: Field: __stack_pointer ; CHECK-NEXT: Kind: GLOBAL ; CHECK-NEXT: GlobalType: I32 @@ -63,6 +55,14 @@ ; CHECK-NEXT: Kind: GLOBAL ; CHECK-NEXT: GlobalType: I32 ; CHECK-NEXT: GlobalMutable: false +; CHECK-NEXT: - Module: env +; CHECK-NEXT: Field: __indirect_function_table +; CHECK-NEXT: Kind: TABLE +; CHECK-NEXT: Table: +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: ElemType: FUNCREF +; CHECK-NEXT: Limits: +; CHECK-NEXT: Initial: 0x1 ; CHECK: - Type: START ; CHECK-NEXT: StartFunction: 2 diff --git a/lld/test/wasm/section-symbol-relocs.yaml b/lld/test/wasm/section-symbol-relocs.yaml --- a/lld/test/wasm/section-symbol-relocs.yaml +++ b/lld/test/wasm/section-symbol-relocs.yaml @@ -54,8 +54,8 @@ # RELOC-NEXT: - Index: 0 # RELOC-NEXT: Kind: SECTION # RELOC-NEXT: Flags: [ BINDING_LOCAL ] -# RELOC-NEXT: Section: 2 +# RELOC-NEXT: Section: 1 # RELOC-NEXT: - Index: 1 # RELOC-NEXT: Kind: SECTION # RELOC-NEXT: Flags: [ BINDING_LOCAL ] -# RELOC-NEXT: Section: 3 +# RELOC-NEXT: Section: 2 diff --git a/lld/test/wasm/shared.ll b/lld/test/wasm/shared.ll --- a/lld/test/wasm/shared.ll +++ b/lld/test/wasm/shared.ll @@ -69,14 +69,6 @@ ; CHECK-NEXT: Memory: ; CHECK-NEXT: Initial: 0x1 ; CHECK-NEXT: - Module: env -; CHECK-NEXT: Field: __indirect_function_table -; CHECK-NEXT: Kind: TABLE -; CHECK-NEXT: Table: -; CHECK-NEXT: Index: 0 -; CHECK-NEXT: ElemType: FUNCREF -; CHECK-NEXT: Limits: -; CHECK-NEXT: Initial: 0x2 -; CHECK-NEXT: - Module: env ; CHECK-NEXT: Field: __stack_pointer ; CHECK-NEXT: Kind: GLOBAL ; CHECK-NEXT: GlobalType: I32 @@ -95,6 +87,14 @@ ; CHECK-NEXT: Field: func_external ; CHECK-NEXT: Kind: FUNCTION ; CHECK-NEXT: SigIndex: 1 +; CHECK-NEXT: - Module: env +; CHECK-NEXT: Field: __indirect_function_table +; CHECK-NEXT: Kind: TABLE +; CHECK-NEXT: Table: +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: ElemType: FUNCREF +; CHECK-NEXT: Limits: +; CHECK-NEXT: Initial: 0x2 ; CHECK-NEXT: - Module: GOT.mem ; CHECK-NEXT: Field: indirect_func ; CHECK-NEXT: Kind: GLOBAL diff --git a/lld/test/wasm/signature-mismatch.ll b/lld/test/wasm/signature-mismatch.ll --- a/lld/test/wasm/signature-mismatch.ll +++ b/lld/test/wasm/signature-mismatch.ll @@ -80,17 +80,22 @@ ; RELOC-NEXT: Segment: 0 ; RELOC-NEXT: Size: 4 ; RELOC-NEXT: - Index: 3 +; RELOC-NEXT: Kind: TABLE +; RELOC-NEXT: Name: __indirect_function_table +; RELOC-NEXT: Flags: [ VISIBILITY_HIDDEN ] +; RELOC-NEXT: Table: 0 +; RELOC-NEXT: - Index: 4 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: call_ret32 ; RELOC-NEXT: Flags: [ ] ; RELOC-NEXT: Function: 3 -; RELOC-NEXT: - Index: 4 +; RELOC-NEXT: - Index: 5 ; RELOC-NEXT: Kind: DATA ; RELOC-NEXT: Name: ret32_address ; RELOC-NEXT: Flags: [ ] ; RELOC-NEXT: Segment: 1 ; RELOC-NEXT: Size: 4 -; RELOC-NEXT: - Index: 5 +; RELOC-NEXT: - Index: 6 ; RELOC-NEXT: Kind: FUNCTION ; RELOC-NEXT: Name: 'signature_mismatch:ret32' ; RELOC-NEXT: Flags: [ BINDING_LOCAL ] diff --git a/lld/test/wasm/stack-pointer.ll b/lld/test/wasm/stack-pointer.ll --- a/lld/test/wasm/stack-pointer.ll +++ b/lld/test/wasm/stack-pointer.ll @@ -30,14 +30,6 @@ ; CHECK-NEXT: GlobalMutable: true ; CHECK-NEXT: - Type: FUNCTION ; CHECK-NEXT: FunctionTypes: [ 0 ] -; CHECK-NEXT: - Type: TABLE -; CHECK-NEXT: Tables: -; CHECK-NEXT: - Index: 0 -; CHECK-NEXT: ElemType: FUNCREF -; CHECK-NEXT: Limits: -; CHECK-NEXT: Flags: [ HAS_MAX ] -; CHECK-NEXT: Initial: 0x1 -; CHECK-NEXT: Maximum: 0x1 ; CHECK-NEXT: - Type: MEMORY ; CHECK-NEXT: Memories: ; CHECK-NEXT: - Initial: 0x0 diff --git a/lld/test/wasm/weak-alias.ll b/lld/test/wasm/weak-alias.ll --- a/lld/test/wasm/weak-alias.ll +++ b/lld/test/wasm/weak-alias.ll @@ -276,6 +276,11 @@ ; RELOC-NEXT: Name: call_direct_ptr ; RELOC-NEXT: Flags: [ ] ; RELOC-NEXT: Function: 5 +; RELOC-NEXT: - Index: 8 +; RELOC-NEXT: Kind: TABLE +; RELOC-NEXT: Name: __indirect_function_table +; RELOC-NEXT: Flags: [ VISIBILITY_HIDDEN ] +; RELOC-NEXT: Table: 0 ; RELOC-NEXT: - Type: CUSTOM ; RELOC-NEXT: Name: name ; RELOC-NEXT: FunctionNames: diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -10,6 +10,7 @@ #include "Config.h" #include "InputChunks.h" #include "InputGlobal.h" +#include "InputTable.h" #include "MarkLive.h" #include "SymbolTable.h" #include "Writer.h" @@ -787,6 +788,58 @@ symtab->wrap(w.sym, w.real, w.wrap); } +static TableSymbol *createDefinedIndirectFunctionTable(StringRef name) { + const uint32_t invalidIndex = -1; + WasmLimits limits{0, 0, 0}; // Set by the writer. + WasmTableType type{uint8_t(ValType::FUNCREF), limits}; + WasmTable desc{invalidIndex, type, name}; + InputTable *table = make(desc, nullptr); + uint32_t flags = config->exportTable ? 0 : WASM_SYMBOL_VISIBILITY_HIDDEN; + TableSymbol *sym = symtab->addSyntheticTable(name, flags, table); + sym->markLive(); + sym->forceExport = config->exportTable; + return sym; +} + +static TableSymbol *createUndefinedIndirectFunctionTable(StringRef name) { + WasmLimits limits{0, 0, 0}; // Set by the writer. + WasmTableType *type = make(); + type->ElemType = uint8_t(ValType::FUNCREF); + type->Limits = limits; + StringRef module(defaultModule); + uint32_t flags = config->exportTable ? 0 : WASM_SYMBOL_VISIBILITY_HIDDEN; + flags |= WASM_SYMBOL_UNDEFINED; + Symbol *sym = + symtab->addUndefinedTable(name, name, module, flags, nullptr, type); + sym->markLive(); + sym->forceExport = config->exportTable; + return cast(sym); +} + +static TableSymbol *resolveIndirectFunctionTable() { + // Even though we may not need a table, if the user explicitly specified + // --import-table or --export-table, ensure a table is residualized. + if (config->importTable) + return createUndefinedIndirectFunctionTable(functionTableName); + if (config->exportTable) + return createDefinedIndirectFunctionTable(functionTableName); + + // Otherwise, check to the symtab to find the indirect function table. + if (Symbol *sym = symtab->find(functionTableName)) { + if (sym->isLive()) { + if (auto *t = dyn_cast(sym)) { + return t->isDefined() + ? t + : 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 LinkerDriver::linkerMain(ArrayRef argsArr) { WasmOptTable parser; opt::InputArgList args = parser.parse(argsArr.slice(1)); @@ -976,6 +1029,12 @@ // Do size optimizations: garbage collection markLive(); + // Provide the indirect funciton table if needed. + WasmSym::indirectFunctionTable = resolveIndirectFunctionTable(); + + if (errorCount()) + return; + // Write the result to the file. writeResult(); } diff --git a/lld/wasm/InputFiles.h b/lld/wasm/InputFiles.h --- a/lld/wasm/InputFiles.h +++ b/lld/wasm/InputFiles.h @@ -157,6 +157,7 @@ Symbol *createUndefined(const WasmSymbol &sym, bool isCalledDirectly); bool isExcludedByComdat(InputChunk *chunk) const; + void synthesizeTableSymbols(); 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,6 +310,71 @@ } } +// 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 +// 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. +// +// 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(); + } + } + } + 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(); + } +} + void ObjFile::parse(bool ignoreComdats) { // Parse a memory buffer as a wasm file. LLVM_DEBUG(dbgs() << "Parsing object: " << toString(this) << "\n"); @@ -424,8 +489,11 @@ // Populate `Symbols` based on the symbols in the object. symbols.reserve(wasmObj->getNumberOfSymbols()); + bool haveTableSymbol = false; for (const SymbolRef &sym : wasmObj->symbols()) { const WasmSymbol &wasmSym = wasmObj->getWasmSymbol(sym.getRawDataRefImpl()); + if (wasmSym.isTypeTable()) + haveTableSymbol = true; if (wasmSym.isDefined()) { // createDefined may fail if the symbol is comdat excluded in which case // we fall back to creating an undefined symbol @@ -437,6 +505,13 @@ size_t idx = symbols.size(); 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(); } bool ObjFile::isExcludedByComdat(InputChunk *chunk) const { diff --git a/lld/wasm/MarkLive.cpp b/lld/wasm/MarkLive.cpp --- a/lld/wasm/MarkLive.cpp +++ b/lld/wasm/MarkLive.cpp @@ -177,6 +177,9 @@ for (InputGlobal *g : symtab->syntheticGlobals) if (!g->live) message("removing unused section " + toString(g)); + for (InputTable *t : symtab->syntheticTables) + if (!t->live) + message("removing unused section " + toString(t)); } } diff --git a/lld/wasm/SymbolTable.h b/lld/wasm/SymbolTable.h --- a/lld/wasm/SymbolTable.h +++ b/lld/wasm/SymbolTable.h @@ -93,6 +93,8 @@ DefinedData *addOptionalDataSymbol(StringRef name, uint64_t value = 0); DefinedGlobal *addOptionalGlobalSymbols(StringRef name, uint32_t flags, InputGlobal *global); + DefinedTable *addSyntheticTable(StringRef name, uint32_t flags, + InputTable *global); void handleSymbolVariants(); void handleWeakUndefines(); @@ -103,6 +105,7 @@ std::vector bitcodeFiles; std::vector syntheticFunctions; std::vector syntheticGlobals; + std::vector syntheticTables; private: std::pair insert(StringRef name, const InputFile *file); diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp --- a/lld/wasm/SymbolTable.cpp +++ b/lld/wasm/SymbolTable.cpp @@ -270,6 +270,18 @@ return replaceSymbol(s, name, flags, 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; + syntheticTables.emplace_back(table); + return replaceSymbol(s, name, flags, nullptr, table); +} + static bool shouldReplace(const Symbol *existing, InputFile *newFile, uint32_t newFlags) { // If existing symbol is undefined, replace it. diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h --- a/lld/wasm/Symbols.h +++ b/lld/wasm/Symbols.h @@ -568,6 +568,11 @@ // Used in PIC code for offset of global data static UndefinedGlobal *memoryBase; static DefinedData *definedMemoryBase; + + // __indirect_function_table + // Used as an address space for function pointers, with each function that is + // used as a function pointer being allocated a slot. + static TableSymbol *indirectFunctionTable; }; // A buffer class that is large enough to hold any Symbol-derived diff --git a/lld/wasm/Symbols.cpp b/lld/wasm/Symbols.cpp --- a/lld/wasm/Symbols.cpp +++ b/lld/wasm/Symbols.cpp @@ -91,6 +91,7 @@ DefinedData *WasmSym::definedTableBase; UndefinedGlobal *WasmSym::memoryBase; DefinedData *WasmSym::definedMemoryBase; +TableSymbol *WasmSym::indirectFunctionTable; WasmSymbolType Symbol::getWasmType() const { if (isa(this)) diff --git a/lld/wasm/SyntheticSections.h b/lld/wasm/SyntheticSections.h --- a/lld/wasm/SyntheticSections.h +++ b/lld/wasm/SyntheticSections.h @@ -98,7 +98,7 @@ class ImportSection : public SyntheticSection { public: - ImportSection(); + ImportSection() : SyntheticSection(llvm::wasm::WASM_SEC_IMPORT) {} bool isNeeded() const override { return getNumImports() > 0; } void writeBody() override; void addImport(Symbol *sym); @@ -150,16 +150,7 @@ public: TableSection() : SyntheticSection(llvm::wasm::WASM_SEC_TABLE) {} - bool isNeeded() const override { - // The linker currently always writes an indirect function table to the - // output, so unless the indirect function table is imported, we need a - // table section. FIXME: Treat __indirect_function_table as a normal - // symbol, and only residualize a table section as needed. - if (!config->importTable) - return true; - return inputTables.size() > 0; - } - + bool isNeeded() const override { return inputTables.size() > 0; }; void writeBody() override; void addTable(InputTable *table); diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp --- a/lld/wasm/SyntheticSections.cpp +++ b/lld/wasm/SyntheticSections.cpp @@ -92,20 +92,11 @@ writeSig(bodyOutputStream, *sig); } -ImportSection::ImportSection() : SyntheticSection(llvm::wasm::WASM_SEC_IMPORT) { - // FIXME: Remove when we treat __indirect_function_table as any other symbol. - if (config->importTable) { - numImportedTables++; - } -} - uint32_t ImportSection::getNumImports() const { assert(isSealed); uint32_t numImports = importedSymbols.size() + gotSymbols.size(); if (config->importMemory) ++numImports; - if (config->importTable) - ++numImports; return numImports; } @@ -154,17 +145,6 @@ writeImport(os, import); } - if (config->importTable) { - uint32_t tableSize = config->tableBase + out.elemSec->numEntries(); - WasmImport import; - import.Module = defaultModule; - import.Field = functionTableName; - import.Kind = WASM_EXTERNAL_TABLE; - import.Table.ElemType = WASM_TYPE_FUNCREF; - import.Table.Limits = {0, tableSize, 0}; - writeImport(os, import); - } - for (const Symbol *sym : importedSymbols) { WasmImport import; if (auto *f = dyn_cast(sym)) { @@ -230,26 +210,9 @@ } void TableSection::writeBody() { - bool hasIndirectFunctionTable = !config->importTable; - - uint32_t tableCount = inputTables.size(); - if (hasIndirectFunctionTable) - tableCount++; - raw_ostream &os = bodyOutputStream; - writeUleb128(os, tableCount, "table count"); - - if (hasIndirectFunctionTable) { - uint32_t tableSize = config->tableBase + out.elemSec->numEntries(); - WasmLimits limits; - if (config->growableTable) - limits = {0, tableSize, 0}; - else - limits = {WASM_LIMITS_FLAG_HAS_MAX, tableSize, tableSize}; - writeTableType(os, WasmTableType{WASM_TYPE_FUNCREF, limits}); - } - + writeUleb128(os, inputTables.size(), "table count"); for (const InputTable *table : inputTables) writeTableType(os, table->getType()); } diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -606,10 +606,6 @@ out.exportSec->exports.push_back( WasmExport{"memory", WASM_EXTERNAL_MEMORY, 0}); - if (!config->relocatable && config->exportTable) - out.exportSec->exports.push_back( - WasmExport{functionTableName, WASM_EXTERNAL_TABLE, 0}); - unsigned globalIndex = out.importSec->getNumImportedGlobals() + out.globalSec->numGlobals(); @@ -745,6 +741,19 @@ } } +static void finalizeIndirectFunctionTable() { + if (!WasmSym::indirectFunctionTable) + return; + + uint32_t tableSize = config->tableBase + out.elemSec->numEntries(); + WasmLimits limits = {0, tableSize, 0}; + if (WasmSym::indirectFunctionTable->isDefined() && !config->growableTable) { + limits.Flags |= WASM_LIMITS_FLAG_HAS_MAX; + limits.Maximum = limits.Initial; + } + WasmSym::indirectFunctionTable->setLimits(limits); +} + static void scanRelocations() { for (ObjFile *file : symtab->objectFiles) { LLVM_DEBUG(dbgs() << "scanRelocations: " << file->getName() << "\n"); @@ -792,6 +801,9 @@ out.tableSec->addTable(table); } + for (InputTable *table : symtab->syntheticTables) + out.tableSec->addTable(table); + out.globalSec->assignIndexes(); } @@ -1341,6 +1353,8 @@ log("-- scanRelocations"); scanRelocations(); + log("-- finalizeIndirectFunctionTable"); + finalizeIndirectFunctionTable(); log("-- createSyntheticInitFunctions"); createSyntheticInitFunctions(); log("-- assignIndexes");