[WebAssembly] Define __funcref_call_table in generated asm and objects (#180900)

Currently, calls into funcref (`call addrspace(20)`) are lowered into a
store of the ref into a special single-element `__funcref_call_table`, a
call_indirect into said table, then a store of null into it.

It functions just fine when `-filetype=asm` (default), but when emitting
an object file directly from llc it fails with `LLVM ERROR: undefined
table symbol cannot be weak`

This fixes said error by "defining" it during `WebAssemblyAsmPrinter`,
and ensuring it is definitely emitted as weak (so duplicate tables in
multiple objects are merged into a single one).
This commit is contained in:
Demetrius Kanios 2026-06-30 00:26:14 -07:00 committed by GitHub
parent fc20c57067
commit d79df25b12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 34 additions and 0 deletions

View file

@ -333,6 +333,39 @@ void WebAssemblyAsmPrinter::emitDecls(const Module &M) {
emitSymbolType(Sym);
}
// We handle `__funcref_call_table` specially here.
//
// Unlike most table symbols, which are attached to a `GlobalVariable`
// this one is a directly created, freestanding MCSymbol, much like
// `__indirect_function_table`. However, given that the table is always
// identical (single element, default initialized), we declare it
// weak in each object, and define it here rather than in the linker.
//
// TODO: consider moving this definition elsewhere, or doing away with
// the table entirely (in favor of `call_ref` exclusively).
{
StringRef Name = "__funcref_call_table";
auto *Sym = static_cast<MCSymbolWasm *>(OutContext.lookupSymbol(Name));
if (Sym) {
if (!Sym->isFunctionTable())
OutContext.reportError(SMLoc(), "symbol is not a wasm funcref table");
// symbol is declared weak in `getOrCreateFuncrefCallTableSymbol`
assert(Sym->isWeak());
OutStreamer->emitSymbolAttribute(Sym, MCSA_Weak);
// Make sure we haven't already emitted it for whatever reason.
assert(!Sym->isDefined());
// Actually define the symbol.
// Confusingly enough, `emitLabel` is what "defines" a MCSymbol.
// Provides it a fragment, so that it `!isUndefined`
OutStreamer->emitLabel(Sym);
// No initializer needed. Default ref.null is good
OutStreamer->addBlankLine();
}
}
DenseSet<MCSymbol *> InvokeSymbols;
for (const auto &F : M) {
if (F.isIntrinsic())

View file

@ -1,5 +1,6 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -fast-isel=0 -mattr=+reference-types | FileCheck %s
; RUN: llc < %s --mtriple=wasm32-unknown-unknown --filetype=obj
; Use -fast-isel-abort=3 (never fall back to SelectionDAG) to make sure that the
; funcref call is selected by FastISel