[WebAssembly] Implementation of global.get/set for reftypes in LLVM IR

Reland of 31859f896.

This change implements new DAG notes GLOBAL_GET/GLOBAL_SET, and
lowering methods for load and stores of reference types from IR
globals. Once the lowering creates the new nodes, tablegen pattern
matches those and converts them to Wasm global.get/set.

Differential Revision: https://reviews.llvm.org/D104797
This commit is contained in:
Paulo Matos 2021-06-10 10:02:10 +02:00
parent 6944f7da25
commit 4facbf213c
29 changed files with 400 additions and 16 deletions

View File

@ -147,7 +147,7 @@ public:
explicit WebAssembly32TargetInfo(const llvm::Triple &T, explicit WebAssembly32TargetInfo(const llvm::Triple &T,
const TargetOptions &Opts) const TargetOptions &Opts)
: WebAssemblyTargetInfo(T, Opts) { : WebAssemblyTargetInfo(T, Opts) {
resetDataLayout("e-m:e-p:32:32-i64:64-n32:64-S128-ni:1"); resetDataLayout("e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20");
} }
protected: protected:
@ -166,7 +166,7 @@ public:
SizeType = UnsignedLong; SizeType = UnsignedLong;
PtrDiffType = SignedLong; PtrDiffType = SignedLong;
IntPtrType = SignedLong; IntPtrType = SignedLong;
resetDataLayout("e-m:e-p:64:64-i64:64-n32:64-S128-ni:1"); resetDataLayout("e-m:e-p:64:64-i64:64-n32:64-S128-ni:1:10:20");
} }
protected: protected:

View File

@ -108,11 +108,11 @@
// RUN: %clang_cc1 -triple wasm32-unknown-unknown -o - -emit-llvm %s | \ // RUN: %clang_cc1 -triple wasm32-unknown-unknown -o - -emit-llvm %s | \
// RUN: FileCheck %s -check-prefix=WEBASSEMBLY32 // RUN: FileCheck %s -check-prefix=WEBASSEMBLY32
// WEBASSEMBLY32: target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1" // WEBASSEMBLY32: target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20"
// RUN: %clang_cc1 -triple wasm64-unknown-unknown -o - -emit-llvm %s | \ // RUN: %clang_cc1 -triple wasm64-unknown-unknown -o - -emit-llvm %s | \
// RUN: FileCheck %s -check-prefix=WEBASSEMBLY64 // RUN: FileCheck %s -check-prefix=WEBASSEMBLY64
// WEBASSEMBLY64: target datalayout = "e-m:e-p:64:64-i64:64-n32:64-S128-ni:1" // WEBASSEMBLY64: target datalayout = "e-m:e-p:64:64-i64:64-n32:64-S128-ni:1:10:20"
// RUN: %clang_cc1 -triple lanai-unknown-unknown -o - -emit-llvm %s | \ // RUN: %clang_cc1 -triple lanai-unknown-unknown -o - -emit-llvm %s | \
// RUN: FileCheck %s -check-prefix=LANAI // RUN: FileCheck %s -check-prefix=LANAI

View File

@ -350,7 +350,7 @@ public:
/// Return the in-memory pointer type for the given address space, defaults to /// Return the in-memory pointer type for the given address space, defaults to
/// the pointer type from the data layout. FIXME: The default needs to be /// the pointer type from the data layout. FIXME: The default needs to be
/// removed once all the code is updated. /// removed once all the code is updated.
MVT getPointerMemTy(const DataLayout &DL, uint32_t AS = 0) const { virtual MVT getPointerMemTy(const DataLayout &DL, uint32_t AS = 0) const {
return MVT::getIntegerVT(DL.getPointerSizeInBits(AS)); return MVT::getIntegerVT(DL.getPointerSizeInBits(AS));
} }

View File

@ -120,6 +120,12 @@ namespace llvm {
return changeExtendedTypeToInteger(); return changeExtendedTypeToInteger();
} }
/// Test if the given EVT has zero size, this will fail if called on a
/// scalable type
bool isZeroSized() const {
return !isScalableVector() && getSizeInBits() == 0;
}
/// Test if the given EVT is simple (as opposed to being extended). /// Test if the given EVT is simple (as opposed to being extended).
bool isSimple() const { bool isSimple() const {
return V.SimpleTy != MVT::INVALID_SIMPLE_VALUE_TYPE; return V.SimpleTy != MVT::INVALID_SIMPLE_VALUE_TYPE;
@ -207,7 +213,9 @@ namespace llvm {
} }
/// Return true if the bit size is a multiple of 8. /// Return true if the bit size is a multiple of 8.
bool isByteSized() const { return getSizeInBits().isKnownMultipleOf(8); } bool isByteSized() const {
return !isZeroSized() && getSizeInBits().isKnownMultipleOf(8);
}
/// Return true if the size is a power-of-two number of bytes. /// Return true if the size is a power-of-two number of bytes.
bool isRound() const { bool isRound() const {

View File

@ -6465,6 +6465,10 @@ bool CodeGenPrepare::optimizeLoadExt(LoadInst *Load) {
EVT LoadResultVT = TLI->getValueType(*DL, Load->getType()); EVT LoadResultVT = TLI->getValueType(*DL, Load->getType());
unsigned BitWidth = LoadResultVT.getSizeInBits(); unsigned BitWidth = LoadResultVT.getSizeInBits();
// If the BitWidth is 0, do not try to optimize the type
if (BitWidth == 0)
return false;
APInt DemandBits(BitWidth, 0); APInt DemandBits(BitWidth, 0);
APInt WidestAndBits(BitWidth, 0); APInt WidestAndBits(BitWidth, 0);

View File

@ -1180,7 +1180,7 @@ void MachineMemOperand::print(raw_ostream &OS, ModuleSlotTracker &MST,
<< "unknown-address"; << "unknown-address";
} }
MachineOperand::printOperandOffset(OS, getOffset()); MachineOperand::printOperandOffset(OS, getOffset());
if (getAlign() != getSize()) if (getSize() > 0 && getAlign() != getSize())
OS << ", align " << getAlign().value(); OS << ", align " << getAlign().value();
if (getAlign() != getBaseAlign()) if (getAlign() != getBaseAlign())
OS << ", basealign " << getBaseAlign().value(); OS << ", basealign " << getBaseAlign().value();

View File

@ -23174,6 +23174,10 @@ bool DAGCombiner::parallelizeChainedStores(StoreSDNode *St) {
if (BasePtr.getBase().isUndef()) if (BasePtr.getBase().isUndef())
return false; return false;
// Do not handle stores to opaque types
if (St->getMemoryVT().isZeroSized())
return false;
// BaseIndexOffset assumes that offsets are fixed-size, which // BaseIndexOffset assumes that offsets are fixed-size, which
// is not valid for scalable vectors where the offsets are // is not valid for scalable vectors where the offsets are
// scaled by `vscale`, so bail out early. // scaled by `vscale`, so bail out early.

View File

@ -1710,7 +1710,7 @@ bool TargetLoweringBase::allowsMemoryAccessForAlignment(
// For example, the ABI alignment may change based on software platform while // For example, the ABI alignment may change based on software platform while
// this function should only be affected by hardware implementation. // this function should only be affected by hardware implementation.
Type *Ty = VT.getTypeForEVT(Context); Type *Ty = VT.getTypeForEVT(Context);
if (Alignment >= DL.getABITypeAlign(Ty)) { if (VT.isZeroSized() || Alignment >= DL.getABITypeAlign(Ty)) {
// Assume that an access that meets the ABI-specified alignment is fast. // Assume that an access that meets the ABI-specified alignment is fast.
if (Fast != nullptr) if (Fast != nullptr)
*Fast = true; *Fast = true;

View File

@ -198,6 +198,10 @@ Type *EVT::getTypeForEVT(LLVMContext &Context) const {
case MVT::ppcf128: return Type::getPPC_FP128Ty(Context); case MVT::ppcf128: return Type::getPPC_FP128Ty(Context);
case MVT::x86mmx: return Type::getX86_MMXTy(Context); case MVT::x86mmx: return Type::getX86_MMXTy(Context);
case MVT::x86amx: return Type::getX86_AMXTy(Context); case MVT::x86amx: return Type::getX86_AMXTy(Context);
case MVT::externref:
return PointerType::get(StructType::create(Context), 10);
case MVT::funcref:
return PointerType::get(StructType::create(Context), 20);
case MVT::v1i1: case MVT::v1i1:
return FixedVectorType::get(Type::getInt1Ty(Context), 1); return FixedVectorType::get(Type::getInt1Ty(Context), 1);
case MVT::v2i1: case MVT::v2i1:

View File

@ -116,6 +116,31 @@ MCSymbolWasm *WebAssembly::getOrCreateFunctionTableSymbol(
return Sym; return Sym;
} }
MCSymbolWasm *WebAssembly::getOrCreateFuncrefCallTableSymbol(
MCContext &Ctx, const WebAssemblySubtarget *Subtarget) {
StringRef Name = "__funcref_call_table";
MCSymbolWasm *Sym = cast_or_null<MCSymbolWasm>(Ctx.lookupSymbol(Name));
if (Sym) {
if (!Sym->isFunctionTable())
Ctx.reportError(SMLoc(), "symbol is not a wasm funcref table");
} else {
Sym = cast<MCSymbolWasm>(Ctx.getOrCreateSymbol(Name));
// Setting Weak ensure only one table is left after linking when multiple
// modules define the table.
Sym->setWeak(true);
wasm::WasmLimits Limits = {0, 1, 1};
wasm::WasmTableType TableType = {wasm::WASM_TYPE_FUNCREF, Limits};
Sym->setType(wasm::WASM_SYMBOL_TYPE_TABLE);
Sym->setTableType(TableType);
}
// MVP object files can't have symtab entries for tables.
if (!(Subtarget && Subtarget->hasReferenceTypes()))
Sym->setOmitFromLinkingSection();
return Sym;
}
// Find a catch instruction from an EH pad. // Find a catch instruction from an EH pad.
MachineInstr *WebAssembly::findCatch(MachineBasicBlock *EHPad) { MachineInstr *WebAssembly::findCatch(MachineBasicBlock *EHPad) {
assert(EHPad->isEHPad()); assert(EHPad->isEHPad());

View File

@ -68,6 +68,12 @@ MCSymbolWasm *
getOrCreateFunctionTableSymbol(MCContext &Ctx, getOrCreateFunctionTableSymbol(MCContext &Ctx,
const WebAssemblySubtarget *Subtarget); const WebAssemblySubtarget *Subtarget);
/// Returns the __funcref_call_table, for use in funcref calls when lowered to
/// table.set + call_indirect.
MCSymbolWasm *
getOrCreateFuncrefCallTableSymbol(MCContext &Ctx,
const WebAssemblySubtarget *Subtarget);
/// Find a catch instruction from an EH pad. Returns null if no catch /// Find a catch instruction from an EH pad. Returns null if no catch
/// instruction found or the catch is in an invalid location. /// instruction found or the catch is in an invalid location.
MachineInstr *findCatch(MachineBasicBlock *EHPad); MachineInstr *findCatch(MachineBasicBlock *EHPad);

View File

@ -130,9 +130,12 @@ private:
case MVT::i64: case MVT::i64:
case MVT::f32: case MVT::f32:
case MVT::f64: case MVT::f64:
return VT;
case MVT::funcref: case MVT::funcref:
case MVT::externref: case MVT::externref:
return VT; if (Subtarget->hasReferenceTypes())
return VT;
break;
case MVT::f16: case MVT::f16:
return MVT::f32; return MVT::f32;
case MVT::v16i8: case MVT::v16i8:

View File

@ -48,3 +48,4 @@ HANDLE_NODETYPE(MEMORY_FILL)
HANDLE_MEM_NODETYPE(LOAD_SPLAT) HANDLE_MEM_NODETYPE(LOAD_SPLAT)
HANDLE_MEM_NODETYPE(GLOBAL_GET) HANDLE_MEM_NODETYPE(GLOBAL_GET)
HANDLE_MEM_NODETYPE(GLOBAL_SET) HANDLE_MEM_NODETYPE(GLOBAL_SET)
HANDLE_MEM_NODETYPE(TABLE_SET)

View File

@ -13,6 +13,7 @@
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h" #include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
#include "WebAssembly.h" #include "WebAssembly.h"
#include "WebAssemblyISelLowering.h"
#include "WebAssemblyTargetMachine.h" #include "WebAssemblyTargetMachine.h"
#include "llvm/CodeGen/MachineFrameInfo.h" #include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/SelectionDAGISel.h" #include "llvm/CodeGen/SelectionDAGISel.h"
@ -47,11 +48,32 @@ public:
return "WebAssembly Instruction Selection"; return "WebAssembly Instruction Selection";
} }
void checkForInvalidNodes(const Function &F) {
// This function will check for uses of ptrtoint on reference types and
// report a fatal error if these are found.
for (const BasicBlock &BB : F) {
for (const Instruction &I : BB) {
if (const PtrToIntInst *PTI = dyn_cast<const PtrToIntInst>(&I)) {
const Value *V = PTI->getPointerOperand();
if (WebAssemblyTargetLowering::isFuncrefType(V->getType()) ||
WebAssemblyTargetLowering::isExternrefType(V->getType()))
report_fatal_error("ptrtoint not allowed on reference types");
} else if (const IntToPtrInst *ITP = dyn_cast<const IntToPtrInst>(&I)) {
if (WebAssemblyTargetLowering::isFuncrefType(ITP->getDestTy()) ||
WebAssemblyTargetLowering::isExternrefType(ITP->getDestTy()))
report_fatal_error("inttoptr not allowed on reference types");
}
}
}
}
bool runOnMachineFunction(MachineFunction &MF) override { bool runOnMachineFunction(MachineFunction &MF) override {
LLVM_DEBUG(dbgs() << "********** ISelDAGToDAG **********\n" LLVM_DEBUG(dbgs() << "********** ISelDAGToDAG **********\n"
"********** Function: " "********** Function: "
<< MF.getName() << '\n'); << MF.getName() << '\n');
checkForInvalidNodes(MF.getFunction());
Subtarget = &MF.getSubtarget<WebAssemblySubtarget>(); Subtarget = &MF.getSubtarget<WebAssemblySubtarget>();
return SelectionDAGISel::runOnMachineFunction(MF); return SelectionDAGISel::runOnMachineFunction(MF);
@ -63,6 +85,7 @@ public:
bool SelectInlineAsmMemoryOperand(const SDValue &Op, unsigned ConstraintID, bool SelectInlineAsmMemoryOperand(const SDValue &Op, unsigned ConstraintID,
std::vector<SDValue> &OutOps) override; std::vector<SDValue> &OutOps) override;
bool SelectExternRefAddr(const SDValue &Addr, const SDValue &Base);
// Include the pieces autogenerated from the target description. // Include the pieces autogenerated from the target description.
#include "WebAssemblyGenDAGISel.inc" #include "WebAssemblyGenDAGISel.inc"

View File

@ -13,6 +13,7 @@
#include "WebAssemblyISelLowering.h" #include "WebAssemblyISelLowering.h"
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h" #include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
#include "Utils/WebAssemblyTypeUtilities.h"
#include "Utils/WebAssemblyUtilities.h" #include "Utils/WebAssemblyUtilities.h"
#include "WebAssemblyMachineFunctionInfo.h" #include "WebAssemblyMachineFunctionInfo.h"
#include "WebAssemblySubtarget.h" #include "WebAssemblySubtarget.h"
@ -66,6 +67,10 @@ WebAssemblyTargetLowering::WebAssemblyTargetLowering(
addRegisterClass(MVT::v2i64, &WebAssembly::V128RegClass); addRegisterClass(MVT::v2i64, &WebAssembly::V128RegClass);
addRegisterClass(MVT::v2f64, &WebAssembly::V128RegClass); addRegisterClass(MVT::v2f64, &WebAssembly::V128RegClass);
} }
if (Subtarget->hasReferenceTypes()) {
addRegisterClass(MVT::externref, &WebAssembly::EXTERNREFRegClass);
addRegisterClass(MVT::funcref, &WebAssembly::FUNCREFRegClass);
}
// Compute derived properties from the register classes. // Compute derived properties from the register classes.
computeRegisterProperties(Subtarget->getRegisterInfo()); computeRegisterProperties(Subtarget->getRegisterInfo());
@ -82,6 +87,12 @@ WebAssemblyTargetLowering::WebAssemblyTargetLowering(
setOperationAction(ISD::STORE, T, Custom); setOperationAction(ISD::STORE, T, Custom);
} }
} }
if (Subtarget->hasReferenceTypes()) {
for (auto T : {MVT::externref, MVT::funcref}) {
setOperationAction(ISD::LOAD, T, Custom);
setOperationAction(ISD::STORE, T, Custom);
}
}
setOperationAction(ISD::GlobalAddress, MVTPtr, Custom); setOperationAction(ISD::GlobalAddress, MVTPtr, Custom);
setOperationAction(ISD::GlobalTLSAddress, MVTPtr, Custom); setOperationAction(ISD::GlobalTLSAddress, MVTPtr, Custom);
@ -468,6 +479,16 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
bool IsIndirect = CallParams.getOperand(0).isReg(); bool IsIndirect = CallParams.getOperand(0).isReg();
bool IsRetCall = CallResults.getOpcode() == WebAssembly::RET_CALL_RESULTS; bool IsRetCall = CallResults.getOpcode() == WebAssembly::RET_CALL_RESULTS;
bool IsFuncrefCall = false;
if (IsIndirect) {
Register Reg = CallParams.getOperand(0).getReg();
const MachineFunction *MF = BB->getParent();
const MachineRegisterInfo &MRI = MF->getRegInfo();
const TargetRegisterClass *TRC = MRI.getRegClass(Reg);
IsFuncrefCall = (TRC == &WebAssembly::FUNCREFRegClass);
assert(!IsFuncrefCall || Subtarget->hasReferenceTypes());
}
unsigned CallOp; unsigned CallOp;
if (IsIndirect && IsRetCall) { if (IsIndirect && IsRetCall) {
CallOp = WebAssembly::RET_CALL_INDIRECT; CallOp = WebAssembly::RET_CALL_INDIRECT;
@ -511,8 +532,11 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
// Placeholder for the type index. // Placeholder for the type index.
MIB.addImm(0); MIB.addImm(0);
// The table into which this call_indirect indexes. // The table into which this call_indirect indexes.
MCSymbolWasm *Table = MCSymbolWasm *Table = IsFuncrefCall
WebAssembly::getOrCreateFunctionTableSymbol(MF.getContext(), Subtarget); ? WebAssembly::getOrCreateFuncrefCallTableSymbol(
MF.getContext(), Subtarget)
: WebAssembly::getOrCreateFunctionTableSymbol(
MF.getContext(), Subtarget);
if (Subtarget->hasReferenceTypes()) { if (Subtarget->hasReferenceTypes()) {
MIB.addSym(Table); MIB.addSym(Table);
} else { } else {
@ -531,6 +555,39 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
CallParams.eraseFromParent(); CallParams.eraseFromParent();
CallResults.eraseFromParent(); CallResults.eraseFromParent();
// If this is a funcref call, to avoid hidden GC roots, we need to clear the
// table slot with ref.null upon call_indirect return.
//
// This generates the following code, which comes right after a call_indirect
// of a funcref:
//
// i32.const 0
// ref.null func
// table.set __funcref_call_table
if (IsIndirect && IsFuncrefCall) {
MCSymbolWasm *Table = WebAssembly::getOrCreateFuncrefCallTableSymbol(
MF.getContext(), Subtarget);
Register RegZero =
MF.getRegInfo().createVirtualRegister(&WebAssembly::I32RegClass);
MachineInstr *Const0 =
BuildMI(MF, DL, TII.get(WebAssembly::CONST_I32), RegZero).addImm(0);
BB->insertAfter(MIB.getInstr()->getIterator(), Const0);
Register RegFuncref =
MF.getRegInfo().createVirtualRegister(&WebAssembly::FUNCREFRegClass);
MachineInstr *RefNull =
BuildMI(MF, DL, TII.get(WebAssembly::REF_NULL_FUNCREF), RegFuncref)
.addImm(static_cast<int32_t>(WebAssembly::HeapType::Funcref));
BB->insertAfter(Const0->getIterator(), RefNull);
MachineInstr *TableSet =
BuildMI(MF, DL, TII.get(WebAssembly::TABLE_SET_FUNCREF))
.addSym(Table)
.addReg(RegZero)
.addReg(RegFuncref);
BB->insertAfter(RefNull->getIterator(), TableSet);
}
return BB; return BB;
} }
@ -1054,6 +1111,33 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
InTys.push_back(In.VT); InTys.push_back(In.VT);
} }
// Lastly, if this is a call to a funcref we need to add an instruction
// table.set to the chain and transform the call.
if (CLI.CB && isFuncrefType(CLI.CB->getCalledOperand()->getType())) {
// In the absence of function references proposal where a funcref call is
// lowered to call_ref, using reference types we generate a table.set to set
// the funcref to a special table used solely for this purpose, followed by
// a call_indirect. Here we just generate the table set, and return the
// SDValue of the table.set so that LowerCall can finalize the lowering by
// generating the call_indirect.
SDValue Chain = Ops[0];
MCSymbolWasm *Table = WebAssembly::getOrCreateFuncrefCallTableSymbol(
MF.getContext(), Subtarget);
SDValue Sym = DAG.getMCSymbol(Table, PtrVT);
SDValue TableSlot = DAG.getConstant(0, DL, MVT::i32);
SDValue TableSetOps[] = {Chain, Sym, TableSlot, Callee};
SDValue TableSet = DAG.getMemIntrinsicNode(
WebAssemblyISD::TABLE_SET, DL, DAG.getVTList(MVT::Other), TableSetOps,
MVT::funcref,
// Machine Mem Operand args
MachinePointerInfo(WasmAddressSpace::FUNCREF),
CLI.CB->getCalledOperand()->getPointerAlignment(DAG.getDataLayout()),
MachineMemOperand::MOStore);
Ops[0] = TableSet; // The new chain is the TableSet itself
}
if (CLI.IsTailCall) { if (CLI.IsTailCall) {
// ret_calls do not return values to the current frame // ret_calls do not return values to the current frame
SDVTList NodeTys = DAG.getVTList(MVT::Other, MVT::Glue); SDVTList NodeTys = DAG.getVTList(MVT::Other, MVT::Glue);
@ -1285,6 +1369,16 @@ static Optional<unsigned> IsWebAssemblyLocal(SDValue Op, SelectionDAG &DAG) {
return WebAssemblyFrameLowering::getLocalForStackObject(MF, FI->getIndex()); return WebAssemblyFrameLowering::getLocalForStackObject(MF, FI->getIndex());
} }
bool WebAssemblyTargetLowering::isFuncrefType(const Type *Ty) {
return isa<PointerType>(Ty) &&
Ty->getPointerAddressSpace() == WasmAddressSpace::FUNCREF;
}
bool WebAssemblyTargetLowering::isExternrefType(const Type *Ty) {
return isa<PointerType>(Ty) &&
Ty->getPointerAddressSpace() == WasmAddressSpace::EXTERNREF;
}
SDValue WebAssemblyTargetLowering::LowerStore(SDValue Op, SDValue WebAssemblyTargetLowering::LowerStore(SDValue Op,
SelectionDAG &DAG) const { SelectionDAG &DAG) const {
SDLoc DL(Op); SDLoc DL(Op);

View File

@ -45,6 +45,36 @@ public:
WebAssemblyTargetLowering(const TargetMachine &TM, WebAssemblyTargetLowering(const TargetMachine &TM,
const WebAssemblySubtarget &STI); const WebAssemblySubtarget &STI);
enum WasmAddressSpace : unsigned {
// WebAssembly uses the following address spaces:
// AS 0 : is the default address space for values in linear memory
DEFAULT = 0,
// AS 1 : is a non-integral address space for global variables
GLOBAL = 1,
// AS 10 : is a non-integral address space for externref values
EXTERNREF = 10,
// AS 20 : is a non-integral address space for funcref values
FUNCREF = 20,
};
MVT getPointerTy(const DataLayout &DL, uint32_t AS = 0) const override {
if (AS == WasmAddressSpace::EXTERNREF)
return MVT::externref;
if (AS == WasmAddressSpace::FUNCREF)
return MVT::funcref;
return TargetLowering::getPointerTy(DL, AS);
}
MVT getPointerMemTy(const DataLayout &DL, uint32_t AS = 0) const override {
if (AS == WasmAddressSpace::EXTERNREF)
return MVT::externref;
if (AS == WasmAddressSpace::FUNCREF)
return MVT::funcref;
return TargetLowering::getPointerMemTy(DL, AS);
}
static bool isFuncrefType(const Type *Ty);
static bool isExternrefType(const Type *Ty);
private: private:
/// Keep a pointer to the WebAssemblySubtarget around so that we can make the /// Keep a pointer to the WebAssemblySubtarget around so that we can make the
/// right decision when generating code for different targets. /// right decision when generating code for different targets.

View File

@ -11,15 +11,16 @@
/// Instructions that handle tables /// Instructions that handle tables
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
multiclass TABLE<WebAssemblyRegClass rt> { multiclass TABLE<WebAssemblyRegClass rt> {
defm TABLE_GET_#rt : I<(outs rt:$res), (ins table32_op:$table), let mayLoad = 1 in
defm TABLE_GET_#rt : I<(outs rt:$res), (ins table32_op:$table, I32:$i),
(outs), (ins table32_op:$table), (outs), (ins table32_op:$table),
[], [],
"table.get\t$res, $table", "table.get\t$res, $table, $i",
"table.get\t$table", "table.get\t$table",
0x25>; 0x25>;
let mayStore = 1 in
defm TABLE_SET_#rt : I<(outs), (ins table32_op:$table, I32:$i, rt:$val), defm TABLE_SET_#rt : I<(outs), (ins table32_op:$table, I32:$i, rt:$val),
(outs), (ins table32_op:$table), (outs), (ins table32_op:$table),
[], [],
@ -46,6 +47,17 @@ multiclass TABLE<WebAssemblyRegClass rt> {
defm "" : TABLE<FUNCREF>, Requires<[HasReferenceTypes]>; defm "" : TABLE<FUNCREF>, Requires<[HasReferenceTypes]>;
defm "" : TABLE<EXTERNREF>, Requires<[HasReferenceTypes]>; defm "" : TABLE<EXTERNREF>, 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)>,
Requires<[HasReferenceTypes]>;
defm TABLE_SIZE : I<(outs I32:$sz), (ins table32_op:$table), defm TABLE_SIZE : I<(outs I32:$sz), (ins table32_op:$table),
(outs), (ins table32_op:$table), (outs), (ins table32_op:$table),
[], [],

View File

@ -217,6 +217,10 @@ static wasm::ValType getType(const TargetRegisterClass *RC) {
return wasm::ValType::F64; return wasm::ValType::F64;
if (RC == &WebAssembly::V128RegClass) if (RC == &WebAssembly::V128RegClass)
return wasm::ValType::V128; return wasm::ValType::V128;
if (RC == &WebAssembly::EXTERNREFRegClass)
return wasm::ValType::EXTERNREF;
if (RC == &WebAssembly::FUNCREFRegClass)
return wasm::ValType::FUNCREF;
llvm_unreachable("Unexpected register class"); llvm_unreachable("Unexpected register class");
} }

View File

@ -121,8 +121,8 @@ WebAssemblyTargetMachine::WebAssemblyTargetMachine(
Optional<CodeModel::Model> CM, CodeGenOpt::Level OL, bool JIT) Optional<CodeModel::Model> CM, CodeGenOpt::Level OL, bool JIT)
: LLVMTargetMachine(T, : LLVMTargetMachine(T,
TT.isArch64Bit() TT.isArch64Bit()
? "e-m:e-p:64:64-i64:64-n32:64-S128-ni:1" ? "e-m:e-p:64:64-i64:64-n32:64-S128-ni:1:10:20"
: "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1", : "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20",
TT, CPU, FS, Options, getEffectiveRelocModel(RM, TT), TT, CPU, FS, Options, getEffectiveRelocModel(RM, TT),
getEffectiveCodeModel(CM, CodeModel::Large), OL), getEffectiveCodeModel(CM, CodeModel::Large), OL),
TLOF(new WebAssemblyTargetObjectFile()) { TLOF(new WebAssemblyTargetObjectFile()) {

View File

@ -0,0 +1,19 @@
; 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_global = local_unnamed_addr addrspace(1) global %externref undef
define %externref @return_externref_global() {
;; this generates a global.get of @externref_global
%ref = load %externref, %externref addrspace(1)* @externref_global
ret %externref %ref
}
; CHECK-LABEL: return_externref_global:
; CHECK-NEXT: functype return_externref_global () -> (externref)
; CHECK-NEXT: global.get externref_global
; CHECK-NEXT: end_function
; CHECK: .globl externref_global

View File

@ -0,0 +1,20 @@
; 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_global = local_unnamed_addr addrspace(1) global %externref undef
define void @set_externref_global(%externref %g) {
;; this generates a global.set of @externref.global
store %externref %g, %externref addrspace(1)* @externref_global
ret void
}
; CHECK-LABEL: set_externref_global:
; CHECK-NEXT: functype set_externref_global (externref) -> ()
; CHECK-NEXT: local.get 0
; CHECK-NEXT: global.set externref_global
; CHECK-NEXT: end_function
; CHECK: .globl externref_global

View File

@ -0,0 +1,11 @@
; RUN: not --crash llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR
%extern = type opaque
%externref = type %extern addrspace(10)*
define %externref @int_to_externref(i32 %i) {
%ref = inttoptr i32 %i to %externref
ret %externref %ref
}
; CHECK-ERROR: LLVM ERROR: inttoptr not allowed on reference types

View File

@ -0,0 +1,11 @@
; RUN: not --crash llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR
%extern = type opaque
%externref = type %extern addrspace(10)*
define i32 @externref_to_int(%externref %ref) {
%i = ptrtoint %externref %ref to i32
ret i32 %i
}
; CHECK-ERROR: LLVM ERROR: ptrtoint not allowed on reference types

View File

@ -0,0 +1,21 @@
; 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_global = local_unnamed_addr addrspace(1) global %externref undef
define %extern @return_extern_undef() {
; Returning a ref.null or an uninitialized externref would make
; more sense if the return type would be %externref. However, in
; this case this is an %extern value, which really is an opaque
; type and should never really happen.
ret %extern undef
}
; CHECK-LABEL: return_extern_undef:
; CHECK-NEXT: functype return_extern_undef () -> ()
; CHECK-NEXT: end_function
; CHECK: .globl externref_global

View File

@ -0,0 +1,11 @@
; RUN: not llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR
%extern = type opaque
%externref = type %extern addrspace(10)*
define void @load_extern(%externref %ref) {
%e = load %extern, %externref %ref
ret void
}
; CHECK-ERROR: error: loading unsized types is not allowed

View File

@ -0,0 +1,11 @@
; RUN: not llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR
%extern = type opaque
%externref = type %extern addrspace(10)*
define void @store_extern(%externref %ref) {
store %extern undef, %externref %ref
ret void
}
; CHECK-ERROR: error: storing unsized types is not allowed

View File

@ -0,0 +1,23 @@
; 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
define void @call_funcref(%funcref %ref) {
call addrspace(20) void %ref()
ret void
}
; CHECK-LABEL: call_funcref:
; CHECK-NEXT: functype call_funcref (funcref) -> ()
; CHECK-NEXT: i32.const 0
; CHECK-NEXT: local.get 0
; CHECK-NEXT: table.set __funcref_call_table
; CHECK-NEXT: local.get 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: .tabletype __funcref_call_table, funcref

View File

@ -0,0 +1,19 @@
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s
%func = type opaque
%funcref = type %func addrspace(20)* ;; addrspace 20 is nonintegral
@funcref_global = local_unnamed_addr addrspace(1) global %funcref undef
define %funcref @return_funcref_global() {
;; this generates a global.get of @funcref_global
%ref = load %funcref, %funcref addrspace(1)* @funcref_global
ret %funcref %ref
}
; CHECK-LABEL: return_funcref_global:
; CHECK-NEXT: .functype return_funcref_global () -> (funcref)
; CHECK-NEXT: global.get funcref_global
; CHECK-NEXT: end_function
; CHECK: .globl funcref_global

View File

@ -0,0 +1,20 @@
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s
%func = type opaque
%funcref = type %func addrspace(20)* ;; addrspace 20 is nonintegral
@funcref_global = local_unnamed_addr addrspace(1) global %funcref undef
define void @set_funcref_global(%funcref %g) {
;; this generates a global.set of @funcref_global
store %funcref %g, %funcref addrspace(1)* @funcref_global
ret void
}
; CHECK-LABEL: set_funcref_global:
; CHECK-NEXT: functype set_funcref_global (funcref) -> ()
; CHECK-NEXT: local.get 0
; CHECK-NEXT: global.set funcref_global
; CHECK-NEXT: end_function
; CHECK: .globl funcref_global