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/Object/WasmObjectFile.cpp b/llvm/lib/Object/WasmObjectFile.cpp --- a/llvm/lib/Object/WasmObjectFile.cpp +++ b/llvm/lib/Object/WasmObjectFile.cpp @@ -484,9 +484,11 @@ std::vector ImportedGlobals; std::vector ImportedFunctions; std::vector ImportedEvents; + std::vector ImportedTables; ImportedGlobals.reserve(Imports.size()); ImportedFunctions.reserve(Imports.size()); ImportedEvents.reserve(Imports.size()); + ImportedTables.reserve(Imports.size()); for (auto &I : Imports) { if (I.Kind == wasm::WASM_EXTERNAL_FUNCTION) ImportedFunctions.emplace_back(&I); @@ -494,6 +496,8 @@ ImportedGlobals.emplace_back(&I); else if (I.Kind == wasm::WASM_EXTERNAL_EVENT) ImportedEvents.emplace_back(&I); + else if (I.Kind == wasm::WASM_EXTERNAL_TABLE) + ImportedTables.emplace_back(&I); } while (Count--) { @@ -562,7 +566,6 @@ Info.Name = Import.Field; } GlobalType = &Import.Global; - Info.ImportName = Import.Field; if (!Import.Module.empty()) { Info.ImportModule = Import.Module; } @@ -585,8 +588,18 @@ wasm::WasmTable &Table = Tables[TableIndex]; TableType = Table.ElemType; } else { - return make_error("undefined table symbol", - object_error::parse_failed); + wasm::WasmImport &Import = *ImportedTables[Info.ElementIndex]; + if ((Info.Flags & wasm::WASM_SYMBOL_EXPLICIT_NAME) != 0) { + Info.Name = readString(Ctx); + Info.ImportName = Import.Field; + } else { + Info.Name = Import.Field; + } + TableType = Import.Table.ElemType; + // FIXME: Parse limits here too. + if (!Import.Module.empty()) { + Info.ImportModule = Import.Module; + } } break; 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" @@ -766,7 +767,8 @@ return false; FunctionType *FuncTy = Call->getFunctionType(); - unsigned Opc = IsDirect ? WebAssembly::CALL : WebAssembly::CALL_INDIRECT; + unsigned Opc = + IsDirect ? WebAssembly::CALL : WebAssembly::CALL_INDIRECT_TABLE; bool IsVoid = FuncTy->getReturnType()->isVoidTy(); unsigned ResultReg; if (!IsVoid) { @@ -867,7 +869,11 @@ if (IsDirect) { MIB.addGlobalAddress(Func); } else { - // Add placeholders for the type index and immediate flags + // Add a reference to the indirect function table, and placeholders for the + // type index and immediate flags. + 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 @@ -436,9 +436,9 @@ unsigned CallOp; if (IsIndirect && IsRetCall) { - CallOp = WebAssembly::RET_CALL_INDIRECT; + CallOp = WebAssembly::RET_CALL_INDIRECT_TABLE; } else if (IsIndirect) { - CallOp = WebAssembly::CALL_INDIRECT; + CallOp = WebAssembly::CALL_INDIRECT_TABLE; } else if (IsRetCall) { CallOp = WebAssembly::RET_CALL; } else { @@ -472,8 +472,12 @@ for (auto Def : CallResults.defs()) MIB.add(Def); - // Add placeholders for the type index and immediate flags + // Add a reference to the indirect function table, and placeholders for the + // type index and immediate flags. if (IsIndirect) { + auto BaseName = MF.createExternalSymbolName("__indirect_function_table"); + auto Sym = MF.getContext().getOrCreateSymbol(BaseName); + MIB.addSym(Sym); MIB.addImm(0); 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,18 @@ 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>; let isReturn = 1, isTerminator = 1, hasCtrlDep = 1, isBarrier = 1 in defm RET_CALL : @@ -75,4 +84,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]>; + } // 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/lib/Target/WebAssembly/WebAssemblyUtilities.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp @@ -92,6 +92,11 @@ case WebAssembly::RET_CALL_INDIRECT: case WebAssembly::RET_CALL_INDIRECT_S: return MI.getOperand(MI.getNumOperands() - 1); + case WebAssembly::CALL_INDIRECT_TABLE: + case WebAssembly::CALL_INDIRECT_TABLE_S: + case WebAssembly::RET_CALL_INDIRECT_TABLE: + case WebAssembly::RET_CALL_INDIRECT_TABLE_S: + return MI.getOperand(MI.getNumOperands() - 2); default: llvm_unreachable("Not a call instruction"); } 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 < %s | FileCheck %s +# not yet: llvm-mc -triple=wasm32-unknown-unknown -filetype=obj -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: