diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISD.def b/llvm/lib/Target/WebAssembly/WebAssemblyISD.def --- a/llvm/lib/Target/WebAssembly/WebAssemblyISD.def +++ b/llvm/lib/Target/WebAssembly/WebAssemblyISD.def @@ -48,4 +48,5 @@ HANDLE_MEM_NODETYPE(LOAD_SPLAT) HANDLE_MEM_NODETYPE(GLOBAL_GET) HANDLE_MEM_NODETYPE(GLOBAL_SET) +HANDLE_MEM_NODETYPE(TABLE_GET) HANDLE_MEM_NODETYPE(TABLE_SET) 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 @@ -92,6 +92,8 @@ setOperationAction(ISD::LOAD, T, Custom); setOperationAction(ISD::STORE, T, Custom); } + setOperationAction(ISD::LOAD, MVT::Other, Custom); + setOperationAction(ISD::STORE, MVT::Other, Custom); } setOperationAction(ISD::GlobalAddress, MVTPtr, Custom); @@ -1418,6 +1420,24 @@ return WebAssemblyFrameLowering::getLocalForStackObject(MF, FI->getIndex()); } +// This function will accept as Op any access to a table, so Op can +// be the actual table or an offset into the table. +static bool IsWebAssemblyTable(SDValue Op) { + if (Op->getOpcode() == ISD::ADD && Op->getNumOperands() == 2) + return IsWebAssemblyTable(Op->getOperand(0)); + if (IsWebAssemblyGlobal(Op)) { + const GlobalAddressSDNode *GA = dyn_cast(Op); + const GlobalValue *Value = GA->getGlobal(); + const Type *Ty = Value->getValueType(); + + if (Ty->isArrayTy() && + (WebAssembly::isFuncrefType(Ty->getArrayElementType()) || + WebAssembly::isExternrefType(Ty->getArrayElementType()))) + return true; + } + return false; +} + SDValue WebAssemblyTargetLowering::LowerStore(SDValue Op, SelectionDAG &DAG) const { SDLoc DL(Op); @@ -1448,6 +1468,46 @@ return DAG.getNode(WebAssemblyISD::LOCAL_SET, DL, Tys, Ops); } + if (IsWebAssemblyTable(Base)) { + if (!Offset->isUndef()) + report_fatal_error( + "unexpected offset when storing from webassembly table", false); + + if (Base->getOpcode() != ISD::ADD || Base->getNumOperands() != 2) + report_fatal_error("unexpected base when storing from webassembly table", + false); + + GlobalAddressSDNode *GA = + dyn_cast(Base->getOperand(0)); + if (!GA) + report_fatal_error( + "unexpected table address when storing from webassembly table", + false); + + const SDNode *PtrOffset = dyn_cast(Base->getOperand(1)); + if (!PtrOffset || PtrOffset->getOpcode() != ISD::SHL) + report_fatal_error( + "unexpected ptr offset when storing from webassembly table", false); + + SDNode *Idx = dyn_cast(PtrOffset->getOperand(0)); + if (!Idx) + report_fatal_error("unexpected index when storing from webassembly table", + false); + + if (GA->getNumValues() != 1 || Idx->getNumValues() != 1) + report_fatal_error( + "unexpected number of values when storing from webassembly table", + false); + + SDVTList Tys = DAG.getVTList(MVT::Other); + SDValue TableSetOps[] = {SN->getChain(), SDValue(GA, 0), SDValue(Idx, 0), + Value}; + SDValue TableSet = + DAG.getMemIntrinsicNode(WebAssemblyISD::TABLE_SET, DL, Tys, TableSetOps, + SN->getMemoryVT(), SN->getMemOperand()); + return TableSet; + } + return Op; } @@ -1483,6 +1543,45 @@ return Result; } + if (IsWebAssemblyTable(Base)) { + if (!Offset->isUndef()) + report_fatal_error( + "unexpected offset when loading from webassembly table", false); + + if (Base->getOpcode() != ISD::ADD || Base->getNumOperands() != 2) + report_fatal_error("unexpected base when loading from webassembly table", + false); + + GlobalAddressSDNode *GA = + dyn_cast(Base->getOperand(0)); + if (!GA) + report_fatal_error( + "unexpected table address when loading from webassembly table", + false); + + const SDNode *PtrOffset = dyn_cast(Base->getOperand(1)); + if (!PtrOffset || PtrOffset->getOpcode() != ISD::SHL) + report_fatal_error( + "unexpected ptr offset when loading from webassembly table", false); + + SDNode *Idx = dyn_cast(PtrOffset->getOperand(0)); + if (!Idx) + report_fatal_error("unexpected index when loading from webassembly table", + false); + + if (GA->getNumValues() != 1 || Idx->getNumValues() != 1) + report_fatal_error( + "unexpected number of values when loading from webassembly table", + false); + + SDVTList Tys = DAG.getVTList(LN->getValueType(0), MVT::Other); + SDValue TableGetOps[] = {LN->getChain(), SDValue(GA, 0), SDValue(Idx, 0)}; + SDValue TableGet = + DAG.getMemIntrinsicNode(WebAssemblyISD::TABLE_GET, DL, Tys, TableGetOps, + LN->getMemoryVT(), LN->getMemOperand()); + return TableGet; + } + return Op; } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrTable.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrTable.td --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrTable.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrTable.td @@ -11,9 +11,18 @@ /// Instructions that handle tables //===----------------------------------------------------------------------===// -multiclass TABLE { +def WebAssemblyTableSet_t : SDTypeProfile<0, 3, [SDTCisPtrTy<1>]>; +def WebAssemblyTableSet : SDNode<"WebAssemblyISD::TABLE_SET", WebAssemblyTableSet_t, + [SDNPHasChain, SDNPMayStore, SDNPMemOperand]>; + +def WebAssemblyTableGet_t : SDTypeProfile<1, 2, [SDTCisPtrTy<1>]>; +def WebAssemblyTableGet : SDNode<"WebAssemblyISD::TABLE_GET", WebAssemblyTableGet_t, + [SDNPHasChain, SDNPMayLoad, SDNPMemOperand]>; + + +multiclass TABLE { let mayLoad = 1 in - defm TABLE_GET_#rt : I<(outs rt:$res), (ins table32_op:$table, I32:$i), + defm TABLE_GET_#rc : I<(outs rc:$res), (ins table32_op:$table, I32:$i), (outs), (ins table32_op:$table), [], "table.get\t$res, $table, $i", @@ -21,41 +30,43 @@ 0x25>; let mayStore = 1 in - defm TABLE_SET_#rt : I<(outs), (ins table32_op:$table, I32:$i, rt:$val), + defm TABLE_SET_#rc : I<(outs), (ins table32_op:$table, I32:$i, rc:$val), (outs), (ins table32_op:$table), [], "table.set\t$table, $i, $val", "table.set\t$table", 0x26>; - defm TABLE_GROW_#rt : I<(outs I32:$sz), (ins table32_op:$table, rt:$val, I32:$n), + defm TABLE_GROW_#rc : I<(outs I32:$sz), (ins table32_op:$table, rc:$val, I32:$n), (outs), (ins table32_op:$table), [], "table.grow\t$sz, $table, $val, $n", "table.grow\t$table", 0xfc0f>; - defm TABLE_FILL_#rt : I<(outs), (ins table32_op:$table, I32:$i, rt:$val, I32:$n), + defm TABLE_FILL_#rc : I<(outs), (ins table32_op:$table, I32:$i, rc:$val, I32:$n), (outs), (ins table32_op:$table), [], "table.fill\t$table, $i, $val, $n", "table.fill\t$table", 0xfc11>; + foreach vt = rc.RegTypes in { + def : Pat<(vt (WebAssemblyTableGet (WebAssemblyWrapper tglobaladdr:$table), i32:$idx)), + (!cast("TABLE_GET_" # rc) tglobaladdr:$table, i32:$idx)>; + def : Pat<(WebAssemblyTableSet + (WebAssemblyWrapper tglobaladdr:$table), + i32:$idx, + vt:$src), + (!cast("TABLE_SET_" # rc) tglobaladdr:$table, i32:$idx, vt:$src)>; + } } defm "" : TABLE, Requires<[HasReferenceTypes]>; defm "" : TABLE, Requires<[HasReferenceTypes]>; -def wasm_table_set_t : SDTypeProfile<0, 3, []>; -def wasm_table_set : SDNode<"WebAssemblyISD::TABLE_SET", wasm_table_set_t, - [SDNPHasChain, SDNPMayStore, SDNPMemOperand]>; - -def : Pat<(wasm_table_set i32:$table, i32:$idx, funcref:$r), - (TABLE_SET_FUNCREF i32:$table, i32:$idx, funcref:$r)>, - Requires<[HasReferenceTypes]>; -def : Pat<(wasm_table_set i32:$table, i32:$idx, externref:$r), - (TABLE_SET_EXTERNREF i32:$table, i32:$idx, externref:$r)>, +def : Pat<(WebAssemblyTableSet mcsym:$table, i32:$idx, funcref:$r), + (TABLE_SET_FUNCREF mcsym:$table, i32:$idx, funcref:$r)>, Requires<[HasReferenceTypes]>; defm TABLE_SIZE : I<(outs I32:$sz), (ins table32_op:$table), 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 @@ -17,6 +17,7 @@ #include "Utils/WebAssemblyTypeUtilities.h" #include "Utils/WebAssemblyUtilities.h" #include "WebAssemblyAsmPrinter.h" +#include "WebAssemblyISelLowering.h" #include "WebAssemblyMachineFunctionInfo.h" #include "llvm/CodeGen/AsmPrinter.h" #include "llvm/CodeGen/MachineFunction.h" @@ -28,6 +29,7 @@ #include "llvm/MC/MCSymbolWasm.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" + using namespace llvm; // This disables the removal of registers when lowering into MC, as required @@ -43,6 +45,15 @@ static void removeRegisterOperands(const MachineInstr *MI, MCInst &OutMI); +// FIXME: this is sort of duplicated into WebAssemblyISelLowering... +static bool isRefType(const Type *Ty) { + return isa(Ty) && + (Ty->getPointerAddressSpace() == + WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_FUNCREF || + Ty->getPointerAddressSpace() == + WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_EXTERNREF); +} + MCSymbol * WebAssemblyMCInstLower::GetGlobalAddressSymbol(const MachineOperand &MO) const { const GlobalValue *Global = MO.getGlobal(); @@ -56,15 +67,31 @@ const MachineFunction &MF = *MO.getParent()->getParent()->getParent(); const TargetMachine &TM = MF.getTarget(); const Function &CurrentFunc = MF.getFunction(); + Type *GlobalVT = Global->getValueType(); SmallVector VTs; - computeLegalValueVTs(CurrentFunc, TM, Global->getValueType(), VTs); - if (VTs.size() != 1) - report_fatal_error("Aggregate globals not yet implemented"); + computeLegalValueVTs(CurrentFunc, TM, GlobalVT, VTs); - bool Mutable = true; - wasm::ValType Type = WebAssembly::toValType(VTs[0]); - WasmSym->setType(wasm::WASM_SYMBOL_TYPE_GLOBAL); - WasmSym->setGlobalType(wasm::WasmGlobalType{uint8_t(Type), Mutable}); + // Tables are represented as Arrays in LLVM IR therefore + // they reach this point as aggregate Array types with an element type + // that is a reference type. + // FIXME: the following code can be simplified. + if (GlobalVT->isArrayTy() && isRefType(GlobalVT->getArrayElementType())) { + bool Mutable = true; + MVT VT = + GlobalVT->getArrayElementType()->getPointerAddressSpace() == + WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_FUNCREF + ? MVT::funcref + : MVT::externref; + wasm::ValType Type = WebAssembly::toValType(VT); + WasmSym->setType(wasm::WASM_SYMBOL_TYPE_GLOBAL); + WasmSym->setGlobalType(wasm::WasmGlobalType{uint8_t(Type), Mutable}); + } else if (VTs.size() == 1) { + bool Mutable = true; + wasm::ValType Type = WebAssembly::toValType(VTs[0]); + WasmSym->setType(wasm::WASM_SYMBOL_TYPE_GLOBAL); + WasmSym->setGlobalType(wasm::WasmGlobalType{uint8_t(Type), Mutable}); + } else + report_fatal_error("Aggregate globals not yet implemented"); } return WasmSym; } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp @@ -497,6 +497,10 @@ return WebAssembly::TEE_F64; if (RC == &WebAssembly::V128RegClass) return WebAssembly::TEE_V128; + if (RC == &WebAssembly::EXTERNREFRegClass) + return WebAssembly::TEE_EXTERNREF; + if (RC == &WebAssembly::FUNCREFRegClass) + return WebAssembly::TEE_FUNCREF; llvm_unreachable("Unexpected register class"); } diff --git a/llvm/test/CodeGen/WebAssembly/externref-tableget.ll b/llvm/test/CodeGen/WebAssembly/externref-tableget.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/externref-tableget.ll @@ -0,0 +1,20 @@ +; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s + +%extern = type opaque +%externref = type %extern addrspace(10)* ;; addrspace 10 is nonintegral + +@externref_table = local_unnamed_addr addrspace(1) global [0 x %externref] undef + +define %externref @get_externref_from_table(i32 %i) { + %p = getelementptr [0 x %externref], [0 x %externref] addrspace (1)* @externref_table, i32 0, i32 %i + %ref = load %externref, %externref addrspace(1)* %p + ret %externref %ref +} + +; CHECK-LABEL: get_externref_from_table: +; CHECK-NEXT: .functype get_externref_from_table (i32) -> (externref) +; CHECK-NEXT: local.get 0 +; CHECK-NEXT: table.get externref_table +; CHECK-NEXT: end_function + +; CHECK: .globl externref_table diff --git a/llvm/test/CodeGen/WebAssembly/externref-tableset.ll b/llvm/test/CodeGen/WebAssembly/externref-tableset.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/externref-tableset.ll @@ -0,0 +1,22 @@ +; RUN: llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s | FileCheck %s + +%extern = type opaque +%externref = type %extern addrspace(10)* ;; addrspace 10 is nonintegral + +@externref_table = local_unnamed_addr addrspace(1) global [0 x %externref] undef + +define void @set_externref_table(%externref %g, i32 %i) { + ;; this generates a table.set of @externref_table + %p = getelementptr [0 x %externref], [0 x %externref] addrspace (1)* @externref_table, i32 0, i32 %i + store %externref %g, %externref addrspace(1)* %p + ret void +} + +; CHECK-LABEL: set_externref_table: +; CHECK-NEXT: .functype set_externref_table (externref, i32) -> () +; CHECK-NEXT: local.get 1 +; CHECK-NEXT: local.get 0 +; CHECK-NEXT: table.set externref_table +; CHECK-NEXT: end_function + +; CHECK: .globl externref_table diff --git a/llvm/test/CodeGen/WebAssembly/funcref-table_call.ll b/llvm/test/CodeGen/WebAssembly/funcref-table_call.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/funcref-table_call.ll @@ -0,0 +1,32 @@ +; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s + +%func = type void () +%funcref = type %func addrspace(20)* ;; addrspace 20 is nonintegral + +@funcref_table = local_unnamed_addr addrspace(1) global [0 x %funcref] undef + +define void @call_funcref_from_table(i32 %i) { + %p = getelementptr [0 x %funcref], [0 x %funcref] addrspace (1)* @funcref_table, i32 0, i32 %i + %ref = load %funcref, %funcref addrspace(1)* %p + call addrspace(20) void %ref() + ret void +} + +; CHECK: .tabletype __funcref_call_table, funcref, 1 + +; CHECK-LABEL: call_funcref_from_table: +; CHECK-NEXT: .functype call_funcref_from_table (i32) -> () +; CHECK-NEXT: i32.const 0 +; CHECK-NEXT: local.get 0 +; CHECK-NEXT: table.get funcref_table +; CHECK-NEXT: table.set __funcref_call_table +; CHECK-NEXT: i32.const 0 +; CHECK-NEXT: call_indirect __funcref_call_table, () -> () +; CHECK-NEXT: i32.const 0 +; CHECK-NEXT: ref.null func +; CHECK-NEXT: table.set __funcref_call_table +; CHECK-NEXT: end_function + + +; CHECK: .globl funcref_table +; CHECK-NEXT .globaltype funcref_table, funcref diff --git a/llvm/test/CodeGen/WebAssembly/funcref-tableget.ll b/llvm/test/CodeGen/WebAssembly/funcref-tableget.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/funcref-tableget.ll @@ -0,0 +1,20 @@ +; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s + +%func = type void () +%funcref = type %func addrspace(20)* ;; addrspace 20 is nonintegral + +@funcref_table = local_unnamed_addr addrspace(1) global [0 x %funcref] undef + +define %funcref @get_funcref_from_table(i32 %i) { + %p = getelementptr [0 x %funcref], [0 x %funcref] addrspace (1)* @funcref_table, i32 0, i32 %i + %ref = load %funcref, %funcref addrspace(1)* %p + ret %funcref %ref +} + +; CHECK-LABEL: get_funcref_from_table: +; CHECK-NEXT: .functype get_funcref_from_table (i32) -> (funcref) +; CHECK-NEXT: local.get 0 +; CHECK-NEXT: table.get funcref_table +; CHECK-NEXT: end_function + +; CHECK: .globl funcref_table