Skip to content

Commit 31859f8

Browse files
committed
Implementation of global.get/set for reftypes in LLVM IR
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. Reviewed By: tlively Differential Revision: https://reviews.llvm.org/D95425
1 parent ad6a84f commit 31859f8

29 files changed

+384
-13
lines changed

clang/lib/Basic/Targets/WebAssembly.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ bool WebAssemblyTargetInfo::handleTargetFeatures(
214214
continue;
215215
}
216216
if (Feature == "+reference-types") {
217+
// FIXME: Ensure address spaces 10 and 20 are marked as non-integral in
218+
// the datalayout string.
217219
HasReferenceTypes = true;
218220
continue;
219221
}

llvm/include/llvm/CodeGen/TargetLowering.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ class TargetLoweringBase {
350350
/// Return the in-memory pointer type for the given address space, defaults to
351351
/// the pointer type from the data layout. FIXME: The default needs to be
352352
/// removed once all the code is updated.
353-
MVT getPointerMemTy(const DataLayout &DL, uint32_t AS = 0) const {
353+
virtual MVT getPointerMemTy(const DataLayout &DL, uint32_t AS = 0) const {
354354
return MVT::getIntegerVT(DL.getPointerSizeInBits(AS));
355355
}
356356

llvm/include/llvm/CodeGen/ValueTypes.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ namespace llvm {
120120
return changeExtendedTypeToInteger();
121121
}
122122

123+
/// Test if the given EVT has zero size
124+
bool isZeroSized() const { return getSizeInBits() == 0; }
125+
123126
/// Test if the given EVT is simple (as opposed to being extended).
124127
bool isSimple() const {
125128
return V.SimpleTy != MVT::INVALID_SIMPLE_VALUE_TYPE;
@@ -207,7 +210,9 @@ namespace llvm {
207210
}
208211

209212
/// Return true if the bit size is a multiple of 8.
210-
bool isByteSized() const { return getSizeInBits().isKnownMultipleOf(8); }
213+
bool isByteSized() const {
214+
return !isZeroSized() && getSizeInBits().isKnownMultipleOf(8);
215+
}
211216

212217
/// Return true if the size is a power-of-two number of bytes.
213218
bool isRound() const {

llvm/include/llvm/Support/MachineValueType.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,11 @@ namespace llvm {
10261026
}
10271027
}
10281028

1029+
/// Test if the given MVT has zero size
1030+
bool isZeroSized() const {
1031+
return !getSizeInBits().isScalable() && getFixedSizeInBits() == 0;
1032+
}
1033+
10291034
/// Return the size of the specified fixed width value type in bits. The
10301035
/// function will assert if the type is scalable.
10311036
uint64_t getFixedSizeInBits() const {

llvm/lib/CodeGen/CodeGenPrepare.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6451,6 +6451,10 @@ bool CodeGenPrepare::optimizeLoadExt(LoadInst *Load) {
64516451

64526452
EVT LoadResultVT = TLI->getValueType(*DL, Load->getType());
64536453
unsigned BitWidth = LoadResultVT.getSizeInBits();
6454+
// If the BitWidth is 0, do not try to optimize the type
6455+
if (BitWidth == 0)
6456+
return false;
6457+
64546458
APInt DemandBits(BitWidth, 0);
64556459
APInt WidestAndBits(BitWidth, 0);
64566460

llvm/lib/CodeGen/MachineOperand.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,8 +1163,8 @@ void MachineMemOperand::print(raw_ostream &OS, ModuleSlotTracker &MST,
11631163
<< "unknown-address";
11641164
}
11651165
MachineOperand::printOperandOffset(OS, getOffset());
1166-
if (getAlign() != getSize())
1167-
OS << ", align " << getAlign().value();
1166+
if (getSize() > 0 && getAlign() != getSize())
1167+
OS << ", align " << getAlign().value();
11681168
if (getAlign() != getBaseAlign())
11691169
OS << ", basealign " << getBaseAlign().value();
11701170
auto AAInfo = getAAInfo();

llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23090,6 +23090,10 @@ bool DAGCombiner::parallelizeChainedStores(StoreSDNode *St) {
2309023090
if (BasePtr.getBase().isUndef())
2309123091
return false;
2309223092

23093+
// Do not handle stores to opaque types
23094+
if (St->getMemoryVT().isZeroSized())
23095+
return false;
23096+
2309323097
// BaseIndexOffset assumes that offsets are fixed-size, which
2309423098
// is not valid for scalable vectors where the offsets are
2309523099
// scaled by `vscale`, so bail out early.

llvm/lib/CodeGen/TargetLoweringBase.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1706,7 +1706,7 @@ bool TargetLoweringBase::allowsMemoryAccessForAlignment(
17061706
// For example, the ABI alignment may change based on software platform while
17071707
// this function should only be affected by hardware implementation.
17081708
Type *Ty = VT.getTypeForEVT(Context);
1709-
if (Alignment >= DL.getABITypeAlign(Ty)) {
1709+
if (VT.isZeroSized() || Alignment >= DL.getABITypeAlign(Ty)) {
17101710
// Assume that an access that meets the ABI-specified alignment is fast.
17111711
if (Fast != nullptr)
17121712
*Fast = true;

llvm/lib/CodeGen/ValueTypes.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,10 @@ Type *EVT::getTypeForEVT(LLVMContext &Context) const {
198198
case MVT::ppcf128: return Type::getPPC_FP128Ty(Context);
199199
case MVT::x86mmx: return Type::getX86_MMXTy(Context);
200200
case MVT::x86amx: return Type::getX86_AMXTy(Context);
201+
case MVT::externref:
202+
return PointerType::get(StructType::create(Context), 10);
203+
case MVT::funcref:
204+
return PointerType::get(StructType::create(Context), 20);
201205
case MVT::v1i1:
202206
return FixedVectorType::get(Type::getInt1Ty(Context), 1);
203207
case MVT::v2i1:

llvm/lib/Target/WebAssembly/Utils/WebAssemblyUtilities.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,31 @@ MCSymbolWasm *WebAssembly::getOrCreateFunctionTableSymbol(
116116
return Sym;
117117
}
118118

119+
MCSymbolWasm *WebAssembly::getOrCreateFuncrefCallTableSymbol(
120+
MCContext &Ctx, const WebAssemblySubtarget *Subtarget) {
121+
StringRef Name = "__funcref_call_table";
122+
MCSymbolWasm *Sym = cast_or_null<MCSymbolWasm>(Ctx.lookupSymbol(Name));
123+
if (Sym) {
124+
if (!Sym->isFunctionTable())
125+
Ctx.reportError(SMLoc(), "symbol is not a wasm funcref table");
126+
} else {
127+
Sym = cast<MCSymbolWasm>(Ctx.getOrCreateSymbol(Name));
128+
129+
// Setting Weak ensure only one table is left after linking when multiple
130+
// modules define the table.
131+
Sym->setWeak(true);
132+
133+
wasm::WasmLimits Limits = {0, 1, 1};
134+
wasm::WasmTableType TableType = {wasm::WASM_TYPE_FUNCREF, Limits};
135+
Sym->setType(wasm::WASM_SYMBOL_TYPE_TABLE);
136+
Sym->setTableType(TableType);
137+
}
138+
// MVP object files can't have symtab entries for tables.
139+
if (!(Subtarget && Subtarget->hasReferenceTypes()))
140+
Sym->setOmitFromLinkingSection();
141+
return Sym;
142+
}
143+
119144
// Find a catch instruction from an EH pad.
120145
MachineInstr *WebAssembly::findCatch(MachineBasicBlock *EHPad) {
121146
assert(EHPad->isEHPad());

llvm/lib/Target/WebAssembly/Utils/WebAssemblyUtilities.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ MCSymbolWasm *
6868
getOrCreateFunctionTableSymbol(MCContext &Ctx,
6969
const WebAssemblySubtarget *Subtarget);
7070

71+
/// Returns the __funcref_call_table, for use in funcref calls when lowered to
72+
/// table.set + call_indirect.
73+
MCSymbolWasm *
74+
getOrCreateFuncrefCallTableSymbol(MCContext &Ctx,
75+
const WebAssemblySubtarget *Subtarget);
76+
7177
/// Find a catch instruction from an EH pad. Returns null if no catch
7278
/// instruction found or the catch is in an invalid location.
7379
MachineInstr *findCatch(MachineBasicBlock *EHPad);

llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,12 @@ class WebAssemblyFastISel final : public FastISel {
130130
case MVT::i64:
131131
case MVT::f32:
132132
case MVT::f64:
133+
return VT;
133134
case MVT::funcref:
134135
case MVT::externref:
135-
return VT;
136+
if (Subtarget->hasReferenceTypes())
137+
return VT;
138+
break;
136139
case MVT::f16:
137140
return MVT::f32;
138141
case MVT::v16i8:

llvm/lib/Target/WebAssembly/WebAssemblyISD.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ HANDLE_NODETYPE(MEMORY_FILL)
4848
HANDLE_MEM_NODETYPE(LOAD_SPLAT)
4949
HANDLE_MEM_NODETYPE(GLOBAL_GET)
5050
HANDLE_MEM_NODETYPE(GLOBAL_SET)
51+
HANDLE_MEM_NODETYPE(TABLE_SET)

llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class WebAssemblyDAGToDAGISel final : public SelectionDAGISel {
6363

6464
bool SelectInlineAsmMemoryOperand(const SDValue &Op, unsigned ConstraintID,
6565
std::vector<SDValue> &OutOps) override;
66+
bool SelectExternRefAddr(const SDValue &Addr, const SDValue &Base);
6667

6768
// Include the pieces autogenerated from the target description.
6869
#include "WebAssemblyGenDAGISel.inc"

llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include "WebAssemblyISelLowering.h"
1515
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
16+
#include "Utils/WebAssemblyTypeUtilities.h"
1617
#include "Utils/WebAssemblyUtilities.h"
1718
#include "WebAssemblyMachineFunctionInfo.h"
1819
#include "WebAssemblySubtarget.h"
@@ -66,6 +67,10 @@ WebAssemblyTargetLowering::WebAssemblyTargetLowering(
6667
addRegisterClass(MVT::v2i64, &WebAssembly::V128RegClass);
6768
addRegisterClass(MVT::v2f64, &WebAssembly::V128RegClass);
6869
}
70+
if (Subtarget->hasReferenceTypes()) {
71+
addRegisterClass(MVT::externref, &WebAssembly::EXTERNREFRegClass);
72+
addRegisterClass(MVT::funcref, &WebAssembly::FUNCREFRegClass);
73+
}
6974
// Compute derived properties from the register classes.
7075
computeRegisterProperties(Subtarget->getRegisterInfo());
7176

@@ -82,6 +87,12 @@ WebAssemblyTargetLowering::WebAssemblyTargetLowering(
8287
setOperationAction(ISD::STORE, T, Custom);
8388
}
8489
}
90+
if (Subtarget->hasReferenceTypes()) {
91+
for (auto T : {MVT::externref, MVT::funcref}) {
92+
setOperationAction(ISD::LOAD, T, Custom);
93+
setOperationAction(ISD::STORE, T, Custom);
94+
}
95+
}
8596

8697
setOperationAction(ISD::GlobalAddress, MVTPtr, Custom);
8798
setOperationAction(ISD::GlobalTLSAddress, MVTPtr, Custom);
@@ -468,6 +479,16 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
468479
bool IsIndirect = CallParams.getOperand(0).isReg();
469480
bool IsRetCall = CallResults.getOpcode() == WebAssembly::RET_CALL_RESULTS;
470481

482+
bool IsFuncrefCall = false;
483+
if (IsIndirect) {
484+
Register Reg = CallParams.getOperand(0).getReg();
485+
const MachineFunction *MF = BB->getParent();
486+
const MachineRegisterInfo &MRI = MF->getRegInfo();
487+
const TargetRegisterClass *TRC = MRI.getRegClass(Reg);
488+
IsFuncrefCall = (TRC == &WebAssembly::FUNCREFRegClass);
489+
assert(!IsFuncrefCall || Subtarget->hasReferenceTypes());
490+
}
491+
471492
unsigned CallOp;
472493
if (IsIndirect && IsRetCall) {
473494
CallOp = WebAssembly::RET_CALL_INDIRECT;
@@ -511,8 +532,11 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
511532
// Placeholder for the type index.
512533
MIB.addImm(0);
513534
// The table into which this call_indirect indexes.
514-
MCSymbolWasm *Table =
515-
WebAssembly::getOrCreateFunctionTableSymbol(MF.getContext(), Subtarget);
535+
MCSymbolWasm *Table = IsFuncrefCall
536+
? WebAssembly::getOrCreateFuncrefCallTableSymbol(
537+
MF.getContext(), Subtarget)
538+
: WebAssembly::getOrCreateFunctionTableSymbol(
539+
MF.getContext(), Subtarget);
516540
if (Subtarget->hasReferenceTypes()) {
517541
MIB.addSym(Table);
518542
} else {
@@ -531,6 +555,39 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
531555
CallParams.eraseFromParent();
532556
CallResults.eraseFromParent();
533557

558+
// If this is a funcref call, to avoid hidden GC roots, we need to clear the
559+
// table slot with ref.null upon call_indirect return.
560+
//
561+
// This generates the following code, which comes right after a call_indirect
562+
// of a funcref:
563+
//
564+
// i32.const 0
565+
// ref.null func
566+
// table.set __funcref_call_table
567+
if (IsIndirect && IsFuncrefCall) {
568+
MCSymbolWasm *Table = WebAssembly::getOrCreateFuncrefCallTableSymbol(
569+
MF.getContext(), Subtarget);
570+
Register RegZero =
571+
MF.getRegInfo().createVirtualRegister(&WebAssembly::I32RegClass);
572+
MachineInstr *Const0 =
573+
BuildMI(MF, DL, TII.get(WebAssembly::CONST_I32), RegZero).addImm(0);
574+
BB->insertAfter(MIB.getInstr()->getIterator(), Const0);
575+
576+
Register RegFuncref =
577+
MF.getRegInfo().createVirtualRegister(&WebAssembly::FUNCREFRegClass);
578+
MachineInstr *RefNull =
579+
BuildMI(MF, DL, TII.get(WebAssembly::REF_NULL_FUNCREF), RegFuncref)
580+
.addImm(static_cast<int32_t>(WebAssembly::HeapType::Funcref));
581+
BB->insertAfter(Const0->getIterator(), RefNull);
582+
583+
MachineInstr *TableSet =
584+
BuildMI(MF, DL, TII.get(WebAssembly::TABLE_SET_FUNCREF))
585+
.addSym(Table)
586+
.addReg(RegZero)
587+
.addReg(RegFuncref);
588+
BB->insertAfter(RefNull->getIterator(), TableSet);
589+
}
590+
534591
return BB;
535592
}
536593

@@ -1054,6 +1111,33 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
10541111
InTys.push_back(In.VT);
10551112
}
10561113

1114+
// Lastly, if this is a call to a funcref we need to add an instruction
1115+
// table.set to the chain and transform the call.
1116+
if (CLI.CB && isFuncref(CLI.CB->getCalledOperand())) {
1117+
// In the absence of function references proposal where a funcref call is
1118+
// lowered to call_ref, using reference types we generate a table.set to set
1119+
// the funcref to a special table used solely for this purpose, followed by
1120+
// a call_indirect. Here we just generate the table set, and return the
1121+
// SDValue of the table.set so that LowerCall can finalize the lowering by
1122+
// generating the call_indirect.
1123+
SDValue Chain = Ops[0];
1124+
1125+
MCSymbolWasm *Table = WebAssembly::getOrCreateFuncrefCallTableSymbol(
1126+
MF.getContext(), Subtarget);
1127+
SDValue Sym = DAG.getMCSymbol(Table, PtrVT);
1128+
SDValue TableSlot = DAG.getConstant(0, DL, MVT::i32);
1129+
SDValue TableSetOps[] = {Chain, Sym, TableSlot, Callee};
1130+
SDValue TableSet = DAG.getMemIntrinsicNode(
1131+
WebAssemblyISD::TABLE_SET, DL, DAG.getVTList(MVT::Other), TableSetOps,
1132+
MVT::funcref,
1133+
// Machine Mem Operand args
1134+
MachinePointerInfo(WasmAddressSpace::FUNCREF),
1135+
CLI.CB->getCalledOperand()->getPointerAlignment(DAG.getDataLayout()),
1136+
MachineMemOperand::MOStore);
1137+
1138+
Ops[0] = TableSet; // The new chain is the TableSet itself
1139+
}
1140+
10571141
if (CLI.IsTailCall) {
10581142
// ret_calls do not return values to the current frame
10591143
SDVTList NodeTys = DAG.getVTList(MVT::Other, MVT::Glue);
@@ -1285,6 +1369,13 @@ static Optional<unsigned> IsWebAssemblyLocal(SDValue Op, SelectionDAG &DAG) {
12851369
return WebAssemblyFrameLowering::getLocalForStackObject(MF, FI->getIndex());
12861370
}
12871371

1372+
bool WebAssemblyTargetLowering::isFuncref(const Value *Op) const {
1373+
const Type *Ty = Op->getType();
1374+
1375+
return isa<PointerType>(Ty) &&
1376+
Ty->getPointerAddressSpace() == WasmAddressSpace::FUNCREF;
1377+
}
1378+
12881379
SDValue WebAssemblyTargetLowering::LowerStore(SDValue Op,
12891380
SelectionDAG &DAG) const {
12901381
SDLoc DL(Op);

llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,33 @@ class WebAssemblyTargetLowering final : public TargetLowering {
4545
WebAssemblyTargetLowering(const TargetMachine &TM,
4646
const WebAssemblySubtarget &STI);
4747

48+
enum WasmAddressSpace : unsigned {
49+
// WebAssembly uses the following address spaces:
50+
// AS 0 : is the default address space for values in linear memory
51+
DEFAULT = 0,
52+
// AS 1 : is a non-integral address space for global variables
53+
GLOBAL = 1,
54+
// AS 10 : is a non-integral address space for externref values
55+
EXTERNREF = 10,
56+
// AS 20 : is a non-integral address space for funcref values
57+
FUNCREF = 20,
58+
};
59+
60+
MVT getPointerTy(const DataLayout &DL, uint32_t AS = 0) const override {
61+
if (AS == WasmAddressSpace::EXTERNREF)
62+
return MVT::externref;
63+
else if (AS == WasmAddressSpace::FUNCREF)
64+
return MVT::funcref;
65+
return TargetLowering::getPointerTy(DL, AS);
66+
}
67+
MVT getPointerMemTy(const DataLayout &DL, uint32_t AS = 0) const override {
68+
if (AS == WasmAddressSpace::EXTERNREF)
69+
return MVT::externref;
70+
else if (AS == WasmAddressSpace::FUNCREF)
71+
return MVT::funcref;
72+
return TargetLowering::getPointerMemTy(DL, AS);
73+
}
74+
4875
private:
4976
/// Keep a pointer to the WebAssemblySubtarget around so that we can make the
5077
/// right decision when generating code for different targets.
@@ -66,6 +93,7 @@ class WebAssemblyTargetLowering final : public TargetLowering {
6693
bool isLegalAddressingMode(const DataLayout &DL, const AddrMode &AM, Type *Ty,
6794
unsigned AS,
6895
Instruction *I = nullptr) const override;
96+
bool isFuncref(const Value *Op) const;
6997
bool allowsMisalignedMemoryAccesses(EVT, unsigned AddrSpace, Align Alignment,
7098
MachineMemOperand::Flags Flags,
7199
bool *Fast) const override;

0 commit comments

Comments
 (0)