Skip to content

Commit 861dbe1

Browse files
committed
[WebAssembly] call_indirect issues table number relocs
If the reference-types feature is enabled, call_indirect will explicitly reference its corresponding function table via `TABLE_NUMBER` relocations against a table symbol. Also, as before, address-taken functions can also cause the function table to be created, only with reference-types they additionally cause a symbol table entry to be emitted. We abuse the used-in-reloc flag on symbols to indicate which tables should end up in the symbol table. We do this because unfortunately older wasm-ld will carp if it see a table symbol. Differential Revision: https://reviews.llvm.org/D90948
1 parent f101373 commit 861dbe1

22 files changed

+574
-102
lines changed

lld/test/wasm/compress-relocs.ll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
; RUN: llc -filetype=obj %s -o %t.o
2-
; RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %p/Inputs/call-indirect.s -o %t2.o
2+
; RUN: llvm-mc -mattr=+reference-types -filetype=obj -triple=wasm32-unknown-unknown %p/Inputs/call-indirect.s -o %t2.o
33
; RUN: wasm-ld --export-dynamic -o %t.wasm %t2.o %t.o
44
; RUN: obj2yaml %t.wasm | FileCheck %s
55
; RUN: wasm-ld --export-dynamic -O2 -o %t-opt.wasm %t2.o %t.o
@@ -22,5 +22,5 @@ entry:
2222

2323
; ERROR: wasm-ld: error: --compress-relocations is incompatible with output debug information. Please pass --strip-debug or --strip-all
2424

25-
; CHECK: Body: 410028028088808000118080808000001A410028028488808000118180808000001A0B
25+
; CHECK: Body: 41002802808880800011808080800080808080001A41002802848880800011818080800080808080001A0B
2626
; COMPRESS: Body: 4100280280081100001A4100280284081101001A0B

llvm/include/llvm/MC/MCSymbolWasm.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class MCSymbolWasm : public MCSymbol {
1818
bool IsWeak = false;
1919
bool IsHidden = false;
2020
bool IsComdat = false;
21+
bool OmitFromLinkingSection = false;
2122
mutable bool IsUsedInInitArray = false;
2223
mutable bool IsUsedInGOT = false;
2324
Optional<StringRef> ImportModule;
@@ -75,6 +76,12 @@ class MCSymbolWasm : public MCSymbol {
7576
bool isComdat() const { return IsComdat; }
7677
void setComdat(bool isComdat) { IsComdat = isComdat; }
7778

79+
// wasm-ld understands a finite set of symbol types. This flag allows the
80+
// compiler to avoid emitting symbol table entries that would confuse the
81+
// linker, unless the user specifically requests the feature.
82+
bool omitFromLinkingSection() const { return OmitFromLinkingSection; }
83+
void setOmitFromLinkingSection() { OmitFromLinkingSection = true; }
84+
7885
bool hasImportModule() const { return ImportModule.hasValue(); }
7986
StringRef getImportModule() const {
8087
if (ImportModule.hasValue())

llvm/lib/MC/WasmObjectWriter.cpp

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -405,12 +405,17 @@ void WasmObjectWriter::writeHeader(const MCAssembler &Asm) {
405405

406406
void WasmObjectWriter::executePostLayoutBinding(MCAssembler &Asm,
407407
const MCAsmLayout &Layout) {
408-
// As a stopgap measure until call_indirect instructions start explicitly
409-
// referencing the indirect function table via TABLE_NUMBER relocs, ensure
410-
// that the indirect function table import makes it to the output if anything
411-
// in the compilation unit has caused it to be present.
412-
if (auto *Sym = Asm.getContext().lookupSymbol("__indirect_function_table"))
413-
Asm.registerSymbol(*Sym);
408+
// Some compilation units require the indirect function table to be present
409+
// but don't explicitly reference it. This is the case for call_indirect
410+
// without the reference-types feature, and also function bitcasts in all
411+
// cases. In those cases the __indirect_function_table has the
412+
// WASM_SYMBOL_NO_STRIP attribute. Here we make sure this symbol makes it to
413+
// the assembler, if needed.
414+
if (auto *Sym = Asm.getContext().lookupSymbol("__indirect_function_table")) {
415+
const auto *WasmSym = static_cast<const MCSymbolWasm *>(Sym);
416+
if (WasmSym->isNoStrip())
417+
Asm.registerSymbol(*Sym);
418+
}
414419

415420
// Build a map of sections to the function that defines them, for use
416421
// in recordRelocation.
@@ -509,21 +514,18 @@ void WasmObjectWriter::recordRelocation(MCAssembler &Asm,
509514
Type == wasm::R_WASM_TABLE_INDEX_I32 ||
510515
Type == wasm::R_WASM_TABLE_INDEX_I64) {
511516
// TABLE_INDEX relocs implicitly use the default indirect function table.
517+
// We require the function table to have already been defined.
512518
auto TableName = "__indirect_function_table";
513519
MCSymbolWasm *Sym = cast_or_null<MCSymbolWasm>(Ctx.lookupSymbol(TableName));
514-
if (Sym) {
515-
if (!Sym->isFunctionTable())
516-
Ctx.reportError(
517-
Fixup.getLoc(),
518-
"symbol '__indirect_function_table' is not a function table");
520+
if (!Sym || !Sym->isFunctionTable()) {
521+
Ctx.reportError(
522+
Fixup.getLoc(),
523+
"symbol '__indirect_function_table' is not a function table");
519524
} else {
520-
Sym = cast<MCSymbolWasm>(Ctx.getOrCreateSymbol(TableName));
521-
Sym->setFunctionTable();
522-
// The default function table is synthesized by the linker.
523-
Sym->setUndefined();
525+
// Ensure that __indirect_function_table reaches the output.
526+
Sym->setNoStrip();
527+
Asm.registerSymbol(*Sym);
524528
}
525-
Sym->setUsedInReloc();
526-
Asm.registerSymbol(*Sym);
527529
}
528530

529531
// Relocation other than R_WASM_TYPE_INDEX_LEB are required to be
@@ -1211,6 +1213,9 @@ static bool isInSymtab(const MCSymbolWasm &Sym) {
12111213
if (Sym.isSection())
12121214
return false;
12131215

1216+
if (Sym.omitFromLinkingSection())
1217+
return false;
1218+
12141219
return true;
12151220
}
12161221

@@ -1674,10 +1679,6 @@ uint64_t WasmObjectWriter::writeOneObject(MCAssembler &Asm,
16741679
WS.setIndex(InvalidIndex);
16751680
continue;
16761681
}
1677-
if (WS.isTable() && WS.getName() == "__indirect_function_table") {
1678-
// For the moment, don't emit table symbols -- wasm-ld can't handle them.
1679-
continue;
1680-
}
16811682
LLVM_DEBUG(dbgs() << "adding to symtab: " << WS << "\n");
16821683

16831684
uint32_t Flags = 0;

llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,6 @@ struct WebAssemblyOperand : public MCParsedAsmOperand {
171171

172172
static MCSymbolWasm *GetOrCreateFunctionTableSymbol(MCContext &Ctx,
173173
const StringRef &Name) {
174-
// FIXME: Duplicates functionality from
175-
// MC/WasmObjectWriter::recordRelocation, as well as WebAssemblyCodegen's
176-
// WebAssembly:getOrCreateFunctionTableSymbol.
177174
MCSymbolWasm *Sym = cast_or_null<MCSymbolWasm>(Ctx.lookupSymbol(Name));
178175
if (Sym) {
179176
if (!Sym->isFunctionTable())
@@ -223,6 +220,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
223220
};
224221
std::vector<NestingType> NestingStack;
225222

223+
MCSymbolWasm *DefaultFunctionTable = nullptr;
226224
MCSymbol *LastFunctionLabel = nullptr;
227225

228226
public:
@@ -233,6 +231,15 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
233231
setAvailableFeatures(ComputeAvailableFeatures(STI.getFeatureBits()));
234232
}
235233

234+
void Initialize(MCAsmParser &Parser) override {
235+
MCAsmParserExtension::Initialize(Parser);
236+
237+
DefaultFunctionTable = GetOrCreateFunctionTableSymbol(
238+
getContext(), "__indirect_function_table");
239+
if (!STI->checkFeatures("+reference-types"))
240+
DefaultFunctionTable->setOmitFromLinkingSection();
241+
}
242+
236243
#define GET_ASSEMBLER_HEADER
237244
#include "WebAssemblyGenAsmMatcher.inc"
238245

@@ -480,6 +487,39 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
480487
WebAssemblyOperand::IntOp{static_cast<int64_t>(BT)}));
481488
}
482489

490+
bool addFunctionTableOperand(OperandVector &Operands, MCSymbolWasm *Sym,
491+
SMLoc StartLoc, SMLoc EndLoc) {
492+
const auto *Val = MCSymbolRefExpr::create(Sym, getContext());
493+
Operands.push_back(std::make_unique<WebAssemblyOperand>(
494+
WebAssemblyOperand::Symbol, StartLoc, EndLoc,
495+
WebAssemblyOperand::SymOp{Val}));
496+
return false;
497+
}
498+
499+
bool addFunctionTableOperand(OperandVector &Operands, StringRef TableName,
500+
SMLoc StartLoc, SMLoc EndLoc) {
501+
return addFunctionTableOperand(
502+
Operands, GetOrCreateFunctionTableSymbol(getContext(), TableName),
503+
StartLoc, EndLoc);
504+
}
505+
506+
bool addDefaultFunctionTableOperand(OperandVector &Operands, SMLoc StartLoc,
507+
SMLoc EndLoc) {
508+
if (STI->checkFeatures("+reference-types")) {
509+
return addFunctionTableOperand(Operands, DefaultFunctionTable, StartLoc,
510+
EndLoc);
511+
} else {
512+
// For the MVP there is at most one table whose number is 0, but we can't
513+
// write a table symbol or issue relocations. Instead we just ensure the
514+
// table is live and write a zero.
515+
getStreamer().emitSymbolAttribute(DefaultFunctionTable, MCSA_NoDeadStrip);
516+
Operands.push_back(std::make_unique<WebAssemblyOperand>(
517+
WebAssemblyOperand::Integer, StartLoc, EndLoc,
518+
WebAssemblyOperand::IntOp{0}));
519+
return false;
520+
}
521+
}
522+
483523
bool ParseInstruction(ParseInstructionInfo & /*Info*/, StringRef Name,
484524
SMLoc NameLoc, OperandVector &Operands) override {
485525
// Note: Name does NOT point into the sourcecode, but to a local, so
@@ -516,6 +556,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
516556
bool ExpectBlockType = false;
517557
bool ExpectFuncType = false;
518558
bool ExpectHeapType = false;
559+
bool ExpectFunctionTable = false;
519560
if (Name == "block") {
520561
push(Block);
521562
ExpectBlockType = true;
@@ -562,15 +603,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
562603
return true;
563604
} else if (Name == "call_indirect" || Name == "return_call_indirect") {
564605
ExpectFuncType = true;
565-
// Ensure that the object file has a __indirect_function_table import, as
566-
// we call_indirect against it.
567-
auto &Ctx = getStreamer().getContext();
568-
MCSymbolWasm *Sym =
569-
GetOrCreateFunctionTableSymbol(Ctx, "__indirect_function_table");
570-
// Until call_indirect emits TABLE_NUMBER relocs against this symbol, mark
571-
// it as NO_STRIP so as to ensure that the indirect function table makes
572-
// it to linked output.
573-
Sym->setNoStrip();
606+
ExpectFunctionTable = true;
574607
} else if (Name == "ref.null") {
575608
ExpectHeapType = true;
576609
}
@@ -586,7 +619,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
586619
return true;
587620
// Got signature as block type, don't need more
588621
ExpectBlockType = false;
589-
auto &Ctx = getStreamer().getContext();
622+
auto &Ctx = getContext();
590623
// The "true" here will cause this to be a nameless symbol.
591624
MCSymbol *Sym = Ctx.createTempSymbol("typeindex", true);
592625
auto *WasmSym = cast<MCSymbolWasm>(Sym);
@@ -598,6 +631,16 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
598631
Operands.push_back(std::make_unique<WebAssemblyOperand>(
599632
WebAssemblyOperand::Symbol, Loc.getLoc(), Loc.getEndLoc(),
600633
WebAssemblyOperand::SymOp{Expr}));
634+
635+
// Allow additional operands after the signature, notably for
636+
// call_indirect against a named table.
637+
if (Lexer.isNot(AsmToken::EndOfStatement)) {
638+
if (expect(AsmToken::Comma, ","))
639+
return true;
640+
if (Lexer.is(AsmToken::EndOfStatement)) {
641+
return error("Unexpected trailing comma");
642+
}
643+
}
601644
}
602645

603646
while (Lexer.isNot(AsmToken::EndOfStatement)) {
@@ -623,6 +666,11 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
623666
WebAssemblyOperand::Integer, Id.getLoc(), Id.getEndLoc(),
624667
WebAssemblyOperand::IntOp{static_cast<int64_t>(HeapType)}));
625668
Parser.Lex();
669+
} else if (ExpectFunctionTable) {
670+
if (addFunctionTableOperand(Operands, Id.getString(), Id.getLoc(),
671+
Id.getEndLoc()))
672+
return true;
673+
Parser.Lex();
626674
} else {
627675
// Assume this identifier is a label.
628676
const MCExpr *Val;
@@ -689,6 +737,12 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
689737
// Support blocks with no operands as default to void.
690738
addBlockTypeOperand(Operands, NameLoc, WebAssembly::BlockType::Void);
691739
}
740+
if (ExpectFunctionTable && Operands.size() == 2) {
741+
// If call_indirect doesn't specify a target table, supply one.
742+
if (addDefaultFunctionTableOperand(Operands, NameLoc,
743+
SMLoc::getFromPointer(Name.end())))
744+
return true;
745+
}
692746
Parser.Lex();
693747
return false;
694748
}

llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyInstPrinter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ void WebAssemblyInstPrinter::printInst(const MCInst *MI, uint64_t Address,
6868
for (auto I = Start, E = MI->getNumOperands(); I < E; ++I) {
6969
if (MI->getOpcode() == WebAssembly::CALL_INDIRECT &&
7070
I - Start == NumVariadicDefs) {
71-
// Skip type and flags arguments when printing for tests
71+
// Skip type and table arguments when printing for tests.
7272
++I;
7373
continue;
7474
}

llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "WebAssemblyMachineFunctionInfo.h"
2424
#include "WebAssemblyRegisterInfo.h"
2525
#include "WebAssemblyTargetMachine.h"
26+
#include "WebAssemblyUtilities.h"
2627
#include "llvm/ADT/SmallSet.h"
2728
#include "llvm/ADT/StringExtras.h"
2829
#include "llvm/BinaryFormat/Wasm.h"
@@ -171,19 +172,25 @@ MCSymbolWasm *WebAssemblyAsmPrinter::getMCSymbolForFunction(
171172

172173
void WebAssemblyAsmPrinter::emitEndOfAsmFile(Module &M) {
173174
for (auto &It : OutContext.getSymbols()) {
174-
// Emit a .globaltype and .eventtype declaration.
175+
// Emit .globaltype, .eventtype, or .tabletype declarations.
175176
auto Sym = cast<MCSymbolWasm>(It.getValue());
176177
if (Sym->getType() == wasm::WASM_SYMBOL_TYPE_GLOBAL)
177178
getTargetStreamer()->emitGlobalType(Sym);
178179
else if (Sym->getType() == wasm::WASM_SYMBOL_TYPE_EVENT)
179180
getTargetStreamer()->emitEventType(Sym);
181+
else if (Sym->getType() == wasm::WASM_SYMBOL_TYPE_TABLE)
182+
getTargetStreamer()->emitTableType(Sym);
180183
}
181184

182185
DenseSet<MCSymbol *> InvokeSymbols;
186+
bool HasAddressTakenFunction = false;
183187
for (const auto &F : M) {
184188
if (F.isIntrinsic())
185189
continue;
186190

191+
if (F.hasAddressTaken())
192+
HasAddressTakenFunction = true;
193+
187194
// Emit function type info for all undefined functions
188195
if (F.isDeclarationForLinker()) {
189196
SmallVector<MVT, 4> Results;
@@ -242,6 +249,18 @@ void WebAssemblyAsmPrinter::emitEndOfAsmFile(Module &M) {
242249
}
243250
}
244251

252+
// When a function's address is taken, a TABLE_INDEX relocation is emitted
253+
// against the function symbol at the use site. However the relocation
254+
// doesn't explicitly refer to the table. In the future we may want to
255+
// define a new kind of reloc against both the function and the table, so
256+
// that the linker can see that the function symbol keeps the table alive,
257+
// but for now manually mark the table as live.
258+
if (HasAddressTakenFunction) {
259+
MCSymbolWasm *FunctionTable =
260+
WebAssembly::getOrCreateFunctionTableSymbol(OutContext, Subtarget);
261+
OutStreamer->emitSymbolAttribute(FunctionTable, MCSA_NoDeadStrip);
262+
}
263+
245264
for (const auto &G : M.globals()) {
246265
if (!G.hasInitializer() && G.hasExternalLinkage()) {
247266
if (G.getValueType()->isSized()) {

llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -869,19 +869,20 @@ bool WebAssemblyFastISel::selectCall(const Instruction *I) {
869869
if (IsDirect) {
870870
MIB.addGlobalAddress(Func);
871871
} else {
872-
// Add placeholders for the type index and immediate flags
872+
// Placehoder for the type index.
873873
MIB.addImm(0);
874-
MIB.addImm(0);
875-
876-
// Ensure that the object file has a __indirect_function_table import, as we
877-
// call_indirect against it.
878-
MCSymbolWasm *Sym = WebAssembly::getOrCreateFunctionTableSymbol(
879-
MF->getMMI().getContext(), "__indirect_function_table");
880-
// Until call_indirect emits TABLE_NUMBER relocs against this symbol, mark
881-
// it as NO_STRIP so as to ensure that the indirect function table makes it
882-
// to linked output.
883-
Sym->setNoStrip();
884-
874+
// The table into which this call_indirect indexes.
875+
MCSymbolWasm *Table = WebAssembly::getOrCreateFunctionTableSymbol(
876+
MF->getMMI().getContext(), Subtarget);
877+
if (Subtarget->hasReferenceTypes()) {
878+
MIB.addSym(Table);
879+
} else {
880+
// Otherwise for the MVP there is at most one table whose number is 0, but
881+
// we can't write a table symbol or issue relocations. Instead we just
882+
// ensure the table is live.
883+
Table->setNoStrip();
884+
MIB.addImm(0);
885+
}
885886
// See if we must truncate the function pointer.
886887
// CALL_INDIRECT takes an i32, but in wasm64 we represent function pointers
887888
// as 64-bit for uniformity with other pointer types.

0 commit comments

Comments
 (0)