Skip to content

Commit 46667a1

Browse files
committed
[WebAssembly] Implementation of global.get/set for reftypes in LLVM IR
Reland of 31859f8. 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/D104797
1 parent df0066a commit 46667a1

29 files changed

+403
-18
lines changed

clang/lib/Basic/Targets/WebAssembly.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,9 @@ class LLVM_LIBRARY_VISIBILITY WebAssembly32TargetInfo
150150
const TargetOptions &Opts)
151151
: WebAssemblyTargetInfo(T, Opts) {
152152
if (T.isOSEmscripten())
153-
resetDataLayout("e-m:e-p:32:32-i64:64-f128:64-n32:64-S128-ni:1");
153+
resetDataLayout("e-m:e-p:32:32-i64:64-f128:64-n32:64-S128-ni:1:10:20");
154154
else
155-
resetDataLayout("e-m:e-p:32:32-i64:64-n32:64-S128-ni:1");
155+
resetDataLayout("e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20");
156156
}
157157

158158
protected:
@@ -172,9 +172,9 @@ class LLVM_LIBRARY_VISIBILITY WebAssembly64TargetInfo
172172
PtrDiffType = SignedLong;
173173
IntPtrType = SignedLong;
174174
if (T.isOSEmscripten())
175-
resetDataLayout("e-m:e-p:64:64-i64:64-f128:64-n32:64-S128-ni:1");
175+
resetDataLayout("e-m:e-p:64:64-i64:64-f128:64-n32:64-S128-ni:1:10:20");
176176
else
177-
resetDataLayout("e-m:e-p:64:64-i64:64-n32:64-S128-ni:1");
177+
resetDataLayout("e-m:e-p:64:64-i64:64-n32:64-S128-ni:1:10:20");
178178
}
179179

180180
protected:

clang/test/CodeGen/target-data.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,11 @@
108108

109109
// RUN: %clang_cc1 -triple wasm32-unknown-unknown -o - -emit-llvm %s | \
110110
// RUN: FileCheck %s -check-prefix=WEBASSEMBLY32
111-
// WEBASSEMBLY32: target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1"
111+
// WEBASSEMBLY32: target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20"
112112

113113
// RUN: %clang_cc1 -triple wasm64-unknown-unknown -o - -emit-llvm %s | \
114114
// RUN: FileCheck %s -check-prefix=WEBASSEMBLY64
115-
// WEBASSEMBLY64: target datalayout = "e-m:e-p:64:64-i64:64-n32:64-S128-ni:1"
115+
// WEBASSEMBLY64: target datalayout = "e-m:e-p:64:64-i64:64-n32:64-S128-ni:1:10:20"
116116

117117
// RUN: %clang_cc1 -triple lanai-unknown-unknown -o - -emit-llvm %s | \
118118
// RUN: FileCheck %s -check-prefix=LANAI

llvm/include/llvm/CodeGen/TargetLowering.h

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

llvm/include/llvm/CodeGen/ValueTypes.h

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

123+
/// Test if the given EVT has zero size, this will fail if called on a
124+
/// scalable type
125+
bool isZeroSized() const {
126+
return !isScalableVector() && getSizeInBits() == 0;
127+
}
128+
123129
/// Test if the given EVT is simple (as opposed to being extended).
124130
bool isSimple() const {
125131
return V.SimpleTy != MVT::INVALID_SIMPLE_VALUE_TYPE;
@@ -207,7 +213,9 @@ namespace llvm {
207213
}
208214

209215
/// Return true if the bit size is a multiple of 8.
210-
bool isByteSized() const { return getSizeInBits().isKnownMultipleOf(8); }
216+
bool isByteSized() const {
217+
return !isZeroSized() && getSizeInBits().isKnownMultipleOf(8);
218+
}
211219

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

llvm/lib/CodeGen/CodeGenPrepare.cpp

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

64666466
EVT LoadResultVT = TLI->getValueType(*DL, Load->getType());
64676467
unsigned BitWidth = LoadResultVT.getSizeInBits();
6468+
// If the BitWidth is 0, do not try to optimize the type
6469+
if (BitWidth == 0)
6470+
return false;
6471+
64686472
APInt DemandBits(BitWidth, 0);
64696473
APInt WidestAndBits(BitWidth, 0);
64706474

llvm/lib/CodeGen/MachineOperand.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1180,7 +1180,7 @@ void MachineMemOperand::print(raw_ostream &OS, ModuleSlotTracker &MST,
11801180
<< "unknown-address";
11811181
}
11821182
MachineOperand::printOperandOffset(OS, getOffset());
1183-
if (getAlign() != getSize())
1183+
if (getSize() > 0 && getAlign() != getSize())
11841184
OS << ", align " << getAlign().value();
11851185
if (getAlign() != getBaseAlign())
11861186
OS << ", basealign " << getBaseAlign().value();

llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp

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

23246+
// Do not handle stores to opaque types
23247+
if (St->getMemoryVT().isZeroSized())
23248+
return false;
23249+
2324623250
// BaseIndexOffset assumes that offsets are fixed-size, which
2324723251
// is not valid for scalable vectors where the offsets are
2324823252
// 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
@@ -1710,7 +1710,7 @@ bool TargetLoweringBase::allowsMemoryAccessForAlignment(
17101710
// For example, the ABI alignment may change based on software platform while
17111711
// this function should only be affected by hardware implementation.
17121712
Type *Ty = VT.getTypeForEVT(Context);
1713-
if (Alignment >= DL.getABITypeAlign(Ty)) {
1713+
if (VT.isZeroSized() || Alignment >= DL.getABITypeAlign(Ty)) {
17141714
// Assume that an access that meets the ABI-specified alignment is fast.
17151715
if (Fast != nullptr)
17161716
*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
@@ -50,3 +50,4 @@ HANDLE_NODETYPE(MEMORY_FILL)
5050
HANDLE_MEM_NODETYPE(LOAD_SPLAT)
5151
HANDLE_MEM_NODETYPE(GLOBAL_GET)
5252
HANDLE_MEM_NODETYPE(GLOBAL_SET)
53+
HANDLE_MEM_NODETYPE(TABLE_SET)

llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp

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

1414
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
1515
#include "WebAssembly.h"
16+
#include "WebAssemblyISelLowering.h"
1617
#include "WebAssemblyTargetMachine.h"
1718
#include "llvm/CodeGen/MachineFrameInfo.h"
1819
#include "llvm/CodeGen/SelectionDAGISel.h"
@@ -47,11 +48,32 @@ class WebAssemblyDAGToDAGISel final : public SelectionDAGISel {
4748
return "WebAssembly Instruction Selection";
4849
}
4950

51+
void checkForInvalidNodes(const Function &F) {
52+
// This function will check for uses of ptrtoint on reference types and
53+
// report a fatal error if these are found.
54+
for (const BasicBlock &BB : F) {
55+
for (const Instruction &I : BB) {
56+
if (const PtrToIntInst *PTI = dyn_cast<const PtrToIntInst>(&I)) {
57+
const Value *V = PTI->getPointerOperand();
58+
if (WebAssemblyTargetLowering::isFuncrefType(V->getType()) ||
59+
WebAssemblyTargetLowering::isExternrefType(V->getType()))
60+
report_fatal_error("ptrtoint not allowed on reference types");
61+
} else if (const IntToPtrInst *ITP = dyn_cast<const IntToPtrInst>(&I)) {
62+
if (WebAssemblyTargetLowering::isFuncrefType(ITP->getDestTy()) ||
63+
WebAssemblyTargetLowering::isExternrefType(ITP->getDestTy()))
64+
report_fatal_error("inttoptr not allowed on reference types");
65+
}
66+
}
67+
}
68+
}
69+
5070
bool runOnMachineFunction(MachineFunction &MF) override {
5171
LLVM_DEBUG(dbgs() << "********** ISelDAGToDAG **********\n"
5272
"********** Function: "
5373
<< MF.getName() << '\n');
5474

75+
checkForInvalidNodes(MF.getFunction());
76+
5577
Subtarget = &MF.getSubtarget<WebAssemblySubtarget>();
5678

5779
return SelectionDAGISel::runOnMachineFunction(MF);
@@ -63,6 +85,7 @@ class WebAssemblyDAGToDAGISel final : public SelectionDAGISel {
6385

6486
bool SelectInlineAsmMemoryOperand(const SDValue &Op, unsigned ConstraintID,
6587
std::vector<SDValue> &OutOps) override;
88+
bool SelectExternRefAddr(const SDValue &Addr, const SDValue &Base);
6689

6790
// Include the pieces autogenerated from the target description.
6891
#include "WebAssemblyGenDAGISel.inc"

llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp

Lines changed: 96 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);
@@ -497,6 +508,16 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
497508
bool IsIndirect = CallParams.getOperand(0).isReg();
498509
bool IsRetCall = CallResults.getOpcode() == WebAssembly::RET_CALL_RESULTS;
499510

511+
bool IsFuncrefCall = false;
512+
if (IsIndirect) {
513+
Register Reg = CallParams.getOperand(0).getReg();
514+
const MachineFunction *MF = BB->getParent();
515+
const MachineRegisterInfo &MRI = MF->getRegInfo();
516+
const TargetRegisterClass *TRC = MRI.getRegClass(Reg);
517+
IsFuncrefCall = (TRC == &WebAssembly::FUNCREFRegClass);
518+
assert(!IsFuncrefCall || Subtarget->hasReferenceTypes());
519+
}
520+
500521
unsigned CallOp;
501522
if (IsIndirect && IsRetCall) {
502523
CallOp = WebAssembly::RET_CALL_INDIRECT;
@@ -540,8 +561,11 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
540561
// Placeholder for the type index.
541562
MIB.addImm(0);
542563
// The table into which this call_indirect indexes.
543-
MCSymbolWasm *Table =
544-
WebAssembly::getOrCreateFunctionTableSymbol(MF.getContext(), Subtarget);
564+
MCSymbolWasm *Table = IsFuncrefCall
565+
? WebAssembly::getOrCreateFuncrefCallTableSymbol(
566+
MF.getContext(), Subtarget)
567+
: WebAssembly::getOrCreateFunctionTableSymbol(
568+
MF.getContext(), Subtarget);
545569
if (Subtarget->hasReferenceTypes()) {
546570
MIB.addSym(Table);
547571
} else {
@@ -560,6 +584,39 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
560584
CallParams.eraseFromParent();
561585
CallResults.eraseFromParent();
562586

587+
// If this is a funcref call, to avoid hidden GC roots, we need to clear the
588+
// table slot with ref.null upon call_indirect return.
589+
//
590+
// This generates the following code, which comes right after a call_indirect
591+
// of a funcref:
592+
//
593+
// i32.const 0
594+
// ref.null func
595+
// table.set __funcref_call_table
596+
if (IsIndirect && IsFuncrefCall) {
597+
MCSymbolWasm *Table = WebAssembly::getOrCreateFuncrefCallTableSymbol(
598+
MF.getContext(), Subtarget);
599+
Register RegZero =
600+
MF.getRegInfo().createVirtualRegister(&WebAssembly::I32RegClass);
601+
MachineInstr *Const0 =
602+
BuildMI(MF, DL, TII.get(WebAssembly::CONST_I32), RegZero).addImm(0);
603+
BB->insertAfter(MIB.getInstr()->getIterator(), Const0);
604+
605+
Register RegFuncref =
606+
MF.getRegInfo().createVirtualRegister(&WebAssembly::FUNCREFRegClass);
607+
MachineInstr *RefNull =
608+
BuildMI(MF, DL, TII.get(WebAssembly::REF_NULL_FUNCREF), RegFuncref)
609+
.addImm(static_cast<int32_t>(WebAssembly::HeapType::Funcref));
610+
BB->insertAfter(Const0->getIterator(), RefNull);
611+
612+
MachineInstr *TableSet =
613+
BuildMI(MF, DL, TII.get(WebAssembly::TABLE_SET_FUNCREF))
614+
.addSym(Table)
615+
.addReg(RegZero)
616+
.addReg(RegFuncref);
617+
BB->insertAfter(RefNull->getIterator(), TableSet);
618+
}
619+
563620
return BB;
564621
}
565622

@@ -1029,6 +1086,33 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
10291086
InTys.push_back(In.VT);
10301087
}
10311088

1089+
// Lastly, if this is a call to a funcref we need to add an instruction
1090+
// table.set to the chain and transform the call.
1091+
if (CLI.CB && isFuncrefType(CLI.CB->getCalledOperand()->getType())) {
1092+
// In the absence of function references proposal where a funcref call is
1093+
// lowered to call_ref, using reference types we generate a table.set to set
1094+
// the funcref to a special table used solely for this purpose, followed by
1095+
// a call_indirect. Here we just generate the table set, and return the
1096+
// SDValue of the table.set so that LowerCall can finalize the lowering by
1097+
// generating the call_indirect.
1098+
SDValue Chain = Ops[0];
1099+
1100+
MCSymbolWasm *Table = WebAssembly::getOrCreateFuncrefCallTableSymbol(
1101+
MF.getContext(), Subtarget);
1102+
SDValue Sym = DAG.getMCSymbol(Table, PtrVT);
1103+
SDValue TableSlot = DAG.getConstant(0, DL, MVT::i32);
1104+
SDValue TableSetOps[] = {Chain, Sym, TableSlot, Callee};
1105+
SDValue TableSet = DAG.getMemIntrinsicNode(
1106+
WebAssemblyISD::TABLE_SET, DL, DAG.getVTList(MVT::Other), TableSetOps,
1107+
MVT::funcref,
1108+
// Machine Mem Operand args
1109+
MachinePointerInfo(WasmAddressSpace::FUNCREF),
1110+
CLI.CB->getCalledOperand()->getPointerAlignment(DAG.getDataLayout()),
1111+
MachineMemOperand::MOStore);
1112+
1113+
Ops[0] = TableSet; // The new chain is the TableSet itself
1114+
}
1115+
10321116
if (CLI.IsTailCall) {
10331117
// ret_calls do not return values to the current frame
10341118
SDVTList NodeTys = DAG.getVTList(MVT::Other, MVT::Glue);
@@ -1260,6 +1344,16 @@ static Optional<unsigned> IsWebAssemblyLocal(SDValue Op, SelectionDAG &DAG) {
12601344
return WebAssemblyFrameLowering::getLocalForStackObject(MF, FI->getIndex());
12611345
}
12621346

1347+
bool WebAssemblyTargetLowering::isFuncrefType(const Type *Ty) {
1348+
return isa<PointerType>(Ty) &&
1349+
Ty->getPointerAddressSpace() == WasmAddressSpace::FUNCREF;
1350+
}
1351+
1352+
bool WebAssemblyTargetLowering::isExternrefType(const Type *Ty) {
1353+
return isa<PointerType>(Ty) &&
1354+
Ty->getPointerAddressSpace() == WasmAddressSpace::EXTERNREF;
1355+
}
1356+
12631357
SDValue WebAssemblyTargetLowering::LowerStore(SDValue Op,
12641358
SelectionDAG &DAG) const {
12651359
SDLoc DL(Op);

0 commit comments

Comments
 (0)