diff --git a/llvm/include/llvm/MC/MCSymbolWasm.h b/llvm/include/llvm/MC/MCSymbolWasm.h --- a/llvm/include/llvm/MC/MCSymbolWasm.h +++ b/llvm/include/llvm/MC/MCSymbolWasm.h @@ -111,8 +111,9 @@ } void setGlobalType(wasm::WasmGlobalType GT) { GlobalType = GT; } + bool hasTableType() const { return TableType.hasValue(); } const wasm::ValType &getTableType() const { - assert(TableType.hasValue()); + assert(hasTableType()); return TableType.getValue(); } void setTableType(wasm::ValType TT) { TableType = TT; } diff --git a/llvm/lib/MC/WasmObjectWriter.cpp b/llvm/lib/MC/WasmObjectWriter.cpp --- a/llvm/lib/MC/WasmObjectWriter.cpp +++ b/llvm/lib/MC/WasmObjectWriter.cpp @@ -40,8 +40,8 @@ namespace { -// Went we ceate the indirect function table we start at 1, so that there is -// and emtpy slot at 0 and therefore calling a null function pointer will trap. +// When we create the indirect function table we start at 1, so that there is +// and empty slot at 0 and therefore calling a null function pointer will trap. static const uint32_t InitialTableOffset = 1; // For patching purposes, we need to remember where each section starts, both @@ -218,9 +218,7 @@ SmallVector DataSegments; unsigned NumFunctionImports = 0; unsigned NumGlobalImports = 0; - // NumTableImports is initialized to 1 to account for the hardcoded import of - // __indirect_function_table - unsigned NumTableImports = 1; + unsigned NumTableImports = 0; unsigned NumEventImports = 0; uint32_t SectionCount = 0; @@ -270,7 +268,7 @@ SectionFunctions.clear(); NumFunctionImports = 0; NumGlobalImports = 0; - NumTableImports = 1; + NumTableImports = 0; MCObjectWriter::reset(); } @@ -1196,16 +1194,6 @@ : wasm::WASM_LIMITS_FLAG_NONE; Imports.push_back(MemImport); - // For now, always emit the table section, since indirect calls are not - // valid without it. In the future, we could perhaps be more clever and omit - // it if there are no indirect calls. - wasm::WasmImport TableImport; - TableImport.Module = "env"; - TableImport.Field = "__indirect_function_table"; - TableImport.Kind = wasm::WASM_EXTERNAL_TABLE; - TableImport.Table.ElemType = wasm::WASM_TYPE_FUNCREF; - Imports.push_back(TableImport); - // Populate SignatureIndices, and Imports and WasmIndices for undefined // symbols. This must be done before populating WasmIndices for defined // symbols. @@ -1264,6 +1252,26 @@ Imports.push_back(Import); assert(WasmIndices.count(&WS) == 0); WasmIndices[&WS] = NumEventImports++; + } else if (WS.isTable()) { + if (WS.isWeak()) + report_fatal_error("undefined table symbol cannot be weak"); + + wasm::WasmImport Import; + Import.Module = WS.getImportModule(); + Import.Field = WS.getImportName(); + Import.Kind = wasm::WASM_EXTERNAL_TABLE; + Import.Table.Index = NumTableImports; + // FIXME: We should probably use some other mechanism to attach types + // to forward-declared tables. This case handles a forward-declared + // __indirect_function_table. + wasm::ValType ElemType = + WS.hasTableType() ? WS.getTableType() : wasm::ValType::FUNCREF; + Import.Table.ElemType = uint8_t(ElemType); + // FIXME: ? + Import.Table.Limits = {wasm::WASM_LIMITS_FLAG_NONE, 0, 0}; + Imports.push_back(Import); + assert(WasmIndices.count(&WS) == 0); + WasmIndices[&WS] = NumTableImports++; } } } diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp --- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp +++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp @@ -490,6 +490,7 @@ bool ExpectBlockType = false; bool ExpectFuncType = false; bool ExpectHeapType = false; + Optional SymbolType; if (Name == "block") { push(Block); ExpectBlockType = true; @@ -529,6 +530,7 @@ return true; } else if (Name == "call_indirect" || Name == "return_call_indirect") { ExpectFuncType = true; + SymbolType = wasm::WASM_SYMBOL_TYPE_TABLE; } else if (Name == "ref.null") { ExpectHeapType = true; } @@ -556,6 +558,16 @@ Operands.push_back(std::make_unique( WebAssemblyOperand::Symbol, Loc.getLoc(), Loc.getEndLoc(), WebAssemblyOperand::SymOp{Expr})); + + // Allow additional operands after the signature, notably for + // call_indirect against a named table. + if (Lexer.isNot(AsmToken::EndOfStatement)) { + if (expect(AsmToken::Comma, ",")) + return true; + if (Lexer.is(AsmToken::EndOfStatement)) { + return error("Unexpected trailing comma"); + } + } } while (Lexer.isNot(AsmToken::EndOfStatement)) { @@ -582,7 +594,22 @@ WebAssemblyOperand::IntOp{static_cast(HeapType)})); Parser.Lex(); } else { - // Assume this identifier is a label. + // If we are expecting a symbol of a specific type, check the type of + // any existing symbol. In the case the symbol doesn't exist yet, + // define it with the type that we are expecting. + if (SymbolType) { + auto &Ctx = getStreamer().getContext(); + MCSymbol *Sym = Ctx.lookupSymbol(Id.getString()); + if (Sym) { + auto *WasmSym = cast(Sym); + if (WasmSym->getType() != SymbolType.getValue()) + return error("Symbol defined with incompatible types: ", Id); + } else { + Sym = Ctx.getOrCreateSymbol(Id.getString()); + auto *WasmSym = cast(Sym); + WasmSym->setType(SymbolType.getValue()); + } + } const MCExpr *Val; SMLoc End; if (Parser.parseExpression(Val, End)) diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h @@ -379,8 +379,24 @@ switch (Opc) { case WebAssembly::CALL_INDIRECT: case WebAssembly::CALL_INDIRECT_S: + case WebAssembly::CALL_INDIRECT_TABLE: + case WebAssembly::CALL_INDIRECT_TABLE_S: case WebAssembly::RET_CALL_INDIRECT: case WebAssembly::RET_CALL_INDIRECT_S: + case WebAssembly::RET_CALL_INDIRECT_TABLE: + case WebAssembly::RET_CALL_INDIRECT_TABLE_S: + return true; + default: + return false; + } +} + +inline bool isRetCallIndirect(unsigned Opc) { + switch (Opc) { + case WebAssembly::RET_CALL_INDIRECT: + case WebAssembly::RET_CALL_INDIRECT_S: + case WebAssembly::RET_CALL_INDIRECT_TABLE: + case WebAssembly::RET_CALL_INDIRECT_TABLE_S: return true; default: return false; diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp @@ -26,6 +26,7 @@ #include "llvm/CodeGen/MachineConstantPool.h" #include "llvm/CodeGen/MachineFrameInfo.h" #include "llvm/CodeGen/MachineInstrBuilder.h" +#include "llvm/CodeGen/MachineModuleInfo.h" #include "llvm/CodeGen/MachineRegisterInfo.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/DerivedTypes.h" @@ -853,10 +854,14 @@ } unsigned CalleeReg = 0; + bool IssueTableNumberRelocation = false; if (!IsDirect) { CalleeReg = getRegForValue(Call->getCalledOperand()); if (!CalleeReg) return false; + IssueTableNumberRelocation = Subtarget->hasReferenceTypes(); + if (IssueTableNumberRelocation) + Opc = WebAssembly::CALL_INDIRECT_TABLE; } auto MIB = BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DbgLoc, TII.get(Opc)); @@ -868,6 +873,11 @@ MIB.addGlobalAddress(Func); } else { // Add placeholders for the type index and immediate flags + if (IssueTableNumberRelocation) { + auto &Context = MF->getMMI().getContext(); + auto BaseName = MF->createExternalSymbolName("__indirect_function_table"); + MIB.addSym(Context.getOrCreateSymbol(BaseName)); + } MIB.addImm(0); MIB.addImm(0); } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp @@ -431,8 +431,13 @@ assert(CallResults.getOpcode() == WebAssembly::CALL_RESULTS || CallResults.getOpcode() == WebAssembly::RET_CALL_RESULTS); + MachineFunction &MF = *BB->getParent(); bool IsIndirect = CallParams.getOperand(0).isReg(); bool IsRetCall = CallResults.getOpcode() == WebAssembly::RET_CALL_RESULTS; + bool IndirectCallNeedsTruncate = + IsIndirect && MF.getSubtarget().hasAddr64(); + bool IndirectCallNeedsReloc = + IsIndirect && MF.getSubtarget().hasReferenceTypes(); unsigned CallOp; if (IsIndirect && IsRetCall) { @@ -445,14 +450,26 @@ CallOp = WebAssembly::CALL; } - MachineFunction &MF = *BB->getParent(); + if (IndirectCallNeedsReloc) { + switch (CallOp) { + case WebAssembly::CALL_INDIRECT: + CallOp = WebAssembly::CALL_INDIRECT_TABLE; + break; + case WebAssembly::RET_CALL_INDIRECT: + CallOp = WebAssembly::RET_CALL_INDIRECT_TABLE; + break; + default: + llvm_unreachable("unhandled indirect call kind"); + } + } + const MCInstrDesc &MCID = TII.get(CallOp); MachineInstrBuilder MIB(MF, MF.CreateMachineInstr(MCID, DL)); // See if we must truncate the function pointer. // CALL_INDIRECT takes an i32, but in wasm64 we represent function pointers // as 64-bit for uniformity with other pointer types. - if (IsIndirect && MF.getSubtarget().hasAddr64()) { + if (IndirectCallNeedsTruncate) { Register Reg32 = MF.getRegInfo().createVirtualRegister(&WebAssembly::I32RegClass); auto &FnPtr = CallParams.getOperand(0); @@ -472,6 +489,13 @@ for (auto Def : CallResults.defs()) MIB.add(Def); + // Add a reference to the indirect function table + if (IndirectCallNeedsReloc) { + auto BaseName = MF.createExternalSymbolName("__indirect_function_table"); + auto Sym = MF.getContext().getOrCreateSymbol(BaseName); + MIB.addSym(Sym); + } + // Add placeholders for the type index and immediate flags if (IsIndirect) { MIB.addImm(0); diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td @@ -56,9 +56,19 @@ let variadicOpsAreDefs = 1 in defm CALL_INDIRECT : - I<(outs), (ins TypeIndex:$type, i32imm:$flags, variable_ops), - (outs), (ins TypeIndex:$type, i32imm:$flags), [], - "call_indirect", "call_indirect\t$type", 0x11>; + I<(outs), (ins TypeIndex:$type, i32imm:$flags, variable_ops), + (outs), (ins TypeIndex:$type, i32imm:$flags), [], + "call_indirect", "call_indirect\t$type", 0x11>; + +let variadicOpsAreDefs = 1 in +defm CALL_INDIRECT_TABLE : + I<(outs), + (ins TypeIndex:$type, table32_op:$table, i32imm:$flags, variable_ops), + (outs), + (ins TypeIndex:$type, table32_op:$table, i32imm:$flags), + [], + "call_indirect\t$table", "call_indirect\t$type, $table", 0x11>, + Requires<[HasReferenceTypes]>; let isReturn = 1, isTerminator = 1, hasCtrlDep = 1, isBarrier = 1 in defm RET_CALL : @@ -75,4 +85,12 @@ 0x13>, Requires<[HasTailCall]>; +let isReturn = 1 in +defm RET_CALL_INDIRECT_TABLE : + I<(outs), (ins TypeIndex:$type, table32_op:$table, i32imm:$flags, variable_ops), + (outs), (ins TypeIndex:$type, table32_op:$table, i32imm:$flags), [], + "return_call_indirect\t$table", "return_call_indirect\t$type, $table", + 0x13>, + Requires<[HasTailCall, HasReferenceTypes]>; + } // Uses = [SP32,SP64], isCall = 1 diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp @@ -93,6 +93,14 @@ return WasmSym; } + if (strcmp(Name, "__indirect_function_table") == 0) { + WasmSym->setType(wasm::WASM_SYMBOL_TYPE_TABLE); + WasmSym->setTableType(wasm::ValType::FUNCREF); + // The indirect function table is synthesized by the linker. + WasmSym->setUndefined(); + return WasmSym; + } + SmallVector Returns; SmallVector Params; if (strcmp(Name, "__cpp_exception") == 0) { @@ -158,6 +166,8 @@ report_fatal_error("Global indexes with offsets not supported"); if (WasmSym->isEvent()) report_fatal_error("Event indexes with offsets not supported"); + if (WasmSym->isTable()) + report_fatal_error("Table indexes with offsets not supported"); Expr = MCBinaryExpr::createAdd( Expr, MCConstantExpr::create(MO.getOffset(), Ctx), Ctx); @@ -256,7 +266,7 @@ // return_call_indirect instructions have the return type of the // caller - if (MI->getOpcode() == WebAssembly::RET_CALL_INDIRECT) + if (WebAssembly::isRetCallIndirect(MI->getOpcode())) getFunctionReturns(MI, Returns); MCOp = lowerTypeIndexOperand(std::move(Returns), std::move(Params)); diff --git a/llvm/test/MC/WebAssembly/call-indirect-relocs.s b/llvm/test/MC/WebAssembly/call-indirect-relocs.s new file mode 100644 --- /dev/null +++ b/llvm/test/MC/WebAssembly/call-indirect-relocs.s @@ -0,0 +1,26 @@ +# RUN: llvm-mc -triple=wasm32-unknown-unknown -mattr=+reference-types < %s | FileCheck %s +# not yet: llvm-mc -triple=wasm32-unknown-unknown -filetype=obj -mattr=+reference-types -o %t.o < %s | obj2yaml | FileCheck -check-prefix=CHECK-OBJ %s + +test0: + .functype test0 () -> () + i32.const 42 + f64.const 2.5 + i32.const 0 + call_indirect (i32, f64) -> (), __indirect_function_table + end_function + +.tabletype empty_fref_table, funcref +empty_fref_table: + + +# CHECK: .text +# CHECK-LABEL: test0: +# CHECK-NEXT: .functype test0 () -> () +# CHECK-NEXT: i32.const 42 +# CHECK-NEXT: f64.const 0x1.4p1 +# CHECK-NEXT: i32.const 0 +# CHECK-NEXT: call_indirect (i32, f64) -> (), __indirect_function_table +# CHECK-NEXT: end_function + +# CHECK: .tabletype empty_fref_table, funcref +# CHECK: empty_fref_table: