LLVM 24.0.0git
SPIRVCombinerHelper.cpp
Go to the documentation of this file.
1//===-- SPIRVCombinerHelper.cpp -------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "SPIRVGlobalRegistry.h"
11#include "SPIRVUtils.h"
15#include "llvm/IR/IntrinsicsSPIRV.h"
16#include "llvm/IR/LLVMContext.h" // Explicitly include for LLVMContext
18
19using namespace llvm;
20using namespace MIPatternMatch;
21
27
28/// This match is part of a combine that
29/// rewrites length(X - Y) to distance(X, Y)
30/// (f32 (g_intrinsic length
31/// (g_fsub (vXf32 X) (vXf32 Y))))
32/// ->
33/// (f32 (g_intrinsic distance
34/// (vXf32 X) (vXf32 Y)))
35///
38 return false;
39
40 // First operand of MI is `G_INTRINSIC` so start at operand 2.
41 Register SubReg = MI.getOperand(2).getReg();
42 return mi_match(SubReg, MRI, m_GFSub(m_Reg(), m_Reg()));
43}
44
46 // Extract the operands for X and Y from the match criteria.
47 Register SubDestReg = MI.getOperand(2).getReg();
48 MachineInstr *SubInstr = MRI.getVRegDef(SubDestReg);
49 Register SubOperand1 = SubInstr->getOperand(1).getReg();
50 Register SubOperand2 = SubInstr->getOperand(2).getReg();
51 Register ResultReg = MI.getOperand(0).getReg();
52
53 Builder.setInstrAndDebugLoc(MI);
54 Builder.buildIntrinsic(Intrinsic::spv_distance, ResultReg)
55 .addUse(SubOperand1)
56 .addUse(SubOperand2);
57
58 MI.eraseFromParent();
59}
60
61/// This match is part of a combine that
62/// rewrites select(fcmp(dot(I, Ng), 0), N, -N) to faceforward(N, I, Ng)
63/// (vXf32 (g_select
64/// (g_fcmp
65/// (g_intrinsic dot(vXf32 I) (vXf32 Ng)
66/// 0)
67/// (vXf32 N)
68/// (vXf32 g_fneg (vXf32 N))))
69/// ->
70/// (vXf32 (g_intrinsic faceforward
71/// (vXf32 N) (vXf32 I) (vXf32 Ng)))
72///
73/// This only works for Vulkan shader targets.
74///
76 if (!STI.isShader())
77 return false;
78
79 // Match overall select pattern.
80 Register CondReg, TrueReg, FalseReg;
81 if (!mi_match(MI.getOperand(0).getReg(), MRI,
82 m_GISelect(m_Reg(CondReg), m_Reg(TrueReg), m_Reg(FalseReg))))
83 return false;
84
85 // Match the FCMP condition.
86 Register DotReg, CondZeroReg;
88 if (!mi_match(CondReg, MRI,
89 m_GFCmp(m_Pred(Pred), m_Reg(DotReg), m_Reg(CondZeroReg))))
90 return false;
91 if (Pred == CmpInst::FCMP_OGT || Pred == CmpInst::FCMP_UGT)
92 std::swap(DotReg, CondZeroReg);
93 else if (!(Pred == CmpInst::FCMP_OLT || Pred == CmpInst::FCMP_ULT))
94 return false;
95
96 // Check if FCMP is a comparison between a dot product and 0.
98 Register DotOperand1, DotOperand2;
99 // Check for scalar dot product.
100 if (!mi_match(DotReg, MRI,
101 m_GFMul(m_Reg(DotOperand1), m_Reg(DotOperand2))) ||
102 !MRI.getType(DotOperand1).isScalar() ||
103 !MRI.getType(DotOperand2).isScalar())
104 return false;
105 }
106
107 const ConstantFP *ZeroVal;
108 if (!mi_match(CondZeroReg, MRI, m_GFCst(ZeroVal)) || !ZeroVal->isZero())
109 return false;
110
111 // Check if select's false operand is the negation of the true operand.
112 auto AreNegatedConstantsOrSplats = [&](Register TrueReg, Register FalseReg) {
113 std::optional<FPValueAndVReg> TrueVal, FalseVal;
114 if (!mi_match(TrueReg, MRI, m_GFCstOrSplat(TrueVal)) ||
115 !mi_match(FalseReg, MRI, m_GFCstOrSplat(FalseVal)))
116 return false;
117 APFloat TrueValNegated = TrueVal->Value;
118 TrueValNegated.changeSign();
119 return FalseVal->Value.compare(TrueValNegated) == APFloat::cmpEqual;
120 };
121
122 if (!mi_match(TrueReg, MRI, m_GFNeg(m_SpecificReg(FalseReg))) &&
123 !mi_match(FalseReg, MRI, m_GFNeg(m_SpecificReg(TrueReg)))) {
124 std::optional<FPValueAndVReg> MulConstant;
125 GBuildVector *TrueInstr, *FalseInstr;
126 if (mi_match(TrueReg, MRI, m_GBuildVector(TrueInstr)) &&
127 mi_match(FalseReg, MRI, m_GBuildVector(FalseInstr)) &&
128 TrueInstr->getNumOperands() == FalseInstr->getNumOperands()) {
129 for (unsigned I = 1; I < TrueInstr->getNumOperands(); ++I)
130 if (!AreNegatedConstantsOrSplats(TrueInstr->getOperand(I).getReg(),
131 FalseInstr->getOperand(I).getReg()))
132 return false;
133 } else if (mi_match(TrueReg, MRI,
134 m_GFMul(m_SpecificReg(FalseReg),
135 m_GFCstOrSplat(MulConstant))) ||
136 mi_match(FalseReg, MRI,
137 m_GFMul(m_SpecificReg(TrueReg),
138 m_GFCstOrSplat(MulConstant))) ||
139 mi_match(TrueReg, MRI,
140 m_GFMul(m_GFCstOrSplat(MulConstant),
141 m_SpecificReg(FalseReg))) ||
142 mi_match(FalseReg, MRI,
143 m_GFMul(m_GFCstOrSplat(MulConstant),
144 m_SpecificReg(TrueReg)))) {
145 if (!MulConstant || !MulConstant->Value.isMinusOne())
146 return false;
147 } else if (!AreNegatedConstantsOrSplats(TrueReg, FalseReg))
148 return false;
149 }
150
151 return true;
152}
153
155 // Extract the operands for N, I, and Ng from the match criteria.
156 Register CondReg = MI.getOperand(1).getReg();
157 MachineInstr *CondInstr = MRI.getVRegDef(CondReg);
158 Register DotReg = CondInstr->getOperand(2).getReg();
159 CmpInst::Predicate Pred = cast<GFCmp>(CondInstr)->getCond();
160 if (Pred == CmpInst::FCMP_OGT || Pred == CmpInst::FCMP_UGT)
161 DotReg = CondInstr->getOperand(3).getReg();
162 MachineInstr *DotInstr = MRI.getVRegDef(DotReg);
163 Register DotOperand1, DotOperand2;
164 if (DotInstr->getOpcode() == TargetOpcode::G_FMUL) {
165 DotOperand1 = DotInstr->getOperand(1).getReg();
166 DotOperand2 = DotInstr->getOperand(2).getReg();
167 } else {
168 DotOperand1 = DotInstr->getOperand(2).getReg();
169 DotOperand2 = DotInstr->getOperand(3).getReg();
170 }
171 Register TrueReg = MI.getOperand(2).getReg();
172 Register FalseReg = MI.getOperand(3).getReg();
173 MachineInstr *TrueInstr = MRI.getVRegDef(TrueReg);
174 if (TrueInstr->getOpcode() == TargetOpcode::G_FNEG ||
175 TrueInstr->getOpcode() == TargetOpcode::G_FMUL)
176 std::swap(TrueReg, FalseReg);
177
178 Register ResultReg = MI.getOperand(0).getReg();
179 Builder.setInstrAndDebugLoc(MI);
180 Builder.buildIntrinsic(Intrinsic::spv_faceforward, ResultReg)
181 .addUse(TrueReg) // N
182 .addUse(DotOperand1) // I
183 .addUse(DotOperand2); // Ng
184
185 MI.eraseFromParent();
186}
187
189 Register ResReg = MI.getOperand(0).getReg();
190 Register InReg = MI.getOperand(2).getReg();
191 uint32_t Rows = MI.getOperand(3).getImm();
192 uint32_t Cols = MI.getOperand(4).getImm();
193
194 Builder.setInstrAndDebugLoc(MI);
195
196 // A 1xN or Nx1 transpose is a pure reshape.
197 if (Rows == 1 || Cols == 1) {
198 Builder.buildCopy(ResReg, InReg);
199 MI.eraseFromParent();
200 return;
201 }
202
204 for (uint32_t K = 0; K < Rows * Cols; ++K) {
205 uint32_t R = K / Cols;
206 uint32_t C = K % Cols;
207 Mask.push_back(C * Rows + R);
208 }
209
210 Builder.buildShuffleVector(ResReg, InReg, InReg, Mask);
211 MI.eraseFromParent();
212}
213
215SPIRVCombinerHelper::extractColumns(Register MatrixReg, uint32_t NumberOfCols,
216 SPIRVTypeInst SpvColType,
217 SPIRVGlobalRegistry *GR) const {
218 // If the matrix is a single colunm, return that single column.
219 if (NumberOfCols == 1)
220 return {MatrixReg};
221
223 LLT ColTy = GR->getRegType(SpvColType);
224 for (uint32_t J = 0; J < NumberOfCols; ++J)
226 Builder.buildUnmerge(Cols, MatrixReg);
227 for (Register R : Cols) {
228 setRegClassType(R, SpvColType, GR, &MRI, Builder.getMF());
229 }
230 return Cols;
231}
232
234SPIRVCombinerHelper::extractRows(Register MatrixReg, uint32_t NumRows,
235 uint32_t NumCols, SPIRVTypeInst SpvRowType,
236 SPIRVGlobalRegistry *GR) const {
238 LLT VecTy = GR->getRegType(SpvRowType);
239
240 // If there is only one column, then each row is a scalar that needs
241 // to be extracted.
242 if (NumCols == 1) {
243 assert(SpvRowType->getOpcode() != SPIRV::OpTypeVector);
244 for (uint32_t I = 0; I < NumRows; ++I)
245 Rows.push_back(MRI.createGenericVirtualRegister(VecTy));
246 Builder.buildUnmerge(Rows, MatrixReg);
247 for (Register R : Rows) {
248 setRegClassType(R, SpvRowType, GR, &MRI, Builder.getMF());
249 }
250 return Rows;
251 }
252
253 // If the matrix is a single row return that row.
254 if (NumRows == 1) {
255 return {MatrixReg};
256 }
257
258 for (uint32_t I = 0; I < NumRows; ++I) {
259 SmallVector<int, 4> Mask;
260 for (uint32_t k = 0; k < NumCols; ++k)
261 Mask.push_back(k * NumRows + I);
262 Rows.push_back(Builder.buildShuffleVector(VecTy, MatrixReg, MatrixReg, Mask)
263 .getReg(0));
264 }
265 for (Register R : Rows) {
266 setRegClassType(R, SpvRowType, GR, &MRI, Builder.getMF());
267 }
268 return Rows;
269}
270
271Register SPIRVCombinerHelper::computeDotProduct(Register RowA, Register ColB,
272 SPIRVTypeInst SpvVecType,
273 SPIRVGlobalRegistry *GR) const {
274 bool IsVectorOp = SpvVecType->getOpcode() == SPIRV::OpTypeVector;
275 SPIRVTypeInst SpvScalarType = GR->getScalarOrVectorComponentType(SpvVecType);
276 bool IsFloatOp = SpvScalarType->getOpcode() == SPIRV::OpTypeFloat;
277 LLT VecTy = GR->getRegType(SpvVecType);
278
279 Register DotRes;
280 if (IsVectorOp) {
281 LLT ScalarTy = VecTy.getElementType();
282 Intrinsic::SPVIntrinsics DotIntrinsic =
283 (IsFloatOp ? Intrinsic::spv_fdot : Intrinsic::spv_udot);
284 DotRes = Builder.buildIntrinsic(DotIntrinsic, {ScalarTy})
285 .addUse(RowA)
286 .addUse(ColB)
287 .getReg(0);
288 } else {
289 if (IsFloatOp)
290 DotRes = Builder.buildFMul(VecTy, RowA, ColB).getReg(0);
291 else
292 DotRes = Builder.buildMul(VecTy, RowA, ColB).getReg(0);
293 }
294 setRegClassType(DotRes, SpvScalarType, GR, &MRI, Builder.getMF());
295 return DotRes;
296}
297
298SmallVector<Register, 16> SPIRVCombinerHelper::computeDotProducts(
300 SPIRVTypeInst SpvVecType, SPIRVGlobalRegistry *GR) const {
301 SmallVector<Register, 16> ResultScalars;
302 for (uint32_t J = 0; J < ColsB.size(); ++J) {
303 for (uint32_t I = 0; I < RowsA.size(); ++I) {
304 ResultScalars.push_back(
305 computeDotProduct(RowsA[I], ColsB[J], SpvVecType, GR));
306 }
307 }
308 return ResultScalars;
309}
310
312SPIRVCombinerHelper::getDotProductVectorType(Register ResReg, uint32_t K,
313 SPIRVGlobalRegistry *GR) const {
314 // Loop over all non debug uses of ResReg
315 Type *ScalarResType = nullptr;
316 for (auto &UseMI : MRI.use_instructions(ResReg)) {
317 if (UseMI.getOpcode() != TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS)
318 continue;
319
320 if (!isSpvIntrinsic(UseMI, Intrinsic::spv_assign_type))
321 continue;
322
323 Type *Ty = getMDOperandAsType(UseMI.getOperand(2).getMetadata(), 0);
324 if (Ty->isVectorTy())
325 ScalarResType = cast<VectorType>(Ty)->getElementType();
326 else
327 ScalarResType = Ty;
328 assert(ScalarResType->isIntegerTy() || ScalarResType->isFloatingPointTy());
329 break;
330 }
331 if (!ScalarResType)
332 llvm_unreachable("Could not determine scalar result type");
333 Type *VecType =
334 (K > 1 ? FixedVectorType::get(ScalarResType, K) : ScalarResType);
335 return GR->getOrCreateSPIRVType(VecType, Builder,
336 SPIRV::AccessQualifier::None, false);
337}
338
340 Register ResReg = MI.getOperand(0).getReg();
341 Register AReg = MI.getOperand(2).getReg();
342 Register BReg = MI.getOperand(3).getReg();
343 uint32_t NumRowsA = MI.getOperand(4).getImm();
344 uint32_t NumColsA = MI.getOperand(5).getImm();
345 uint32_t NumColsB = MI.getOperand(6).getImm();
346
347 Builder.setInstrAndDebugLoc(MI);
348
350 MI.getMF()->getSubtarget<SPIRVSubtarget>().getSPIRVGlobalRegistry();
351
352 SPIRVTypeInst SpvVecType = getDotProductVectorType(ResReg, NumColsA, GR);
354 extractColumns(BReg, NumColsB, SpvVecType, GR);
356 extractRows(AReg, NumRowsA, NumColsA, SpvVecType, GR);
357 SmallVector<Register, 16> ResultScalars =
358 computeDotProducts(RowsA, ColsB, SpvVecType, GR);
359
360 if (ResultScalars.size() == 1)
361 Builder.buildCopy(ResReg, ResultScalars[0]);
362 else
363 Builder.buildBuildVector(ResReg, ResultScalars);
364 MI.eraseFromParent();
365}
MachineInstrBuilder & UseMI
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
static GCRegistry::Add< ShadowStackGC > C("shadow-stack", "Very portable GC for uncooperative code generators")
static GCRegistry::Add< OcamlGC > B("ocaml", "ocaml 3.10-compatible GC")
Declares convenience wrapper classes for interpreting MachineInstr instances as specific generic oper...
IRTranslator LLVM IR MI
#define I(x, y, z)
Definition MD5.cpp:57
Contains matchers for matching SSA Machine Instructions.
Promote Memory to Register
Definition Mem2Reg.cpp:110
void changeSign()
Definition APFloat.h:1393
Represent a constant reference to an array (0 or more elements consecutively in memory),...
Definition ArrayRef.h:40
size_t size() const
Get the array size.
Definition ArrayRef.h:141
Predicate
This enumeration lists the possible predicates for CmpInst subclasses.
Definition InstrTypes.h:740
@ FCMP_OLT
0 1 0 0 True if ordered and less than
Definition InstrTypes.h:746
@ FCMP_OGT
0 0 1 0 True if ordered and greater than
Definition InstrTypes.h:744
@ FCMP_ULT
1 1 0 0 True if unordered or less than
Definition InstrTypes.h:754
@ FCMP_UGT
1 0 1 0 True if unordered or greater than
Definition InstrTypes.h:752
MachineRegisterInfo & MRI
const LegalizerInfo * LI
MachineDominatorTree * MDT
GISelValueTracking * VT
GISelChangeObserver & Observer
MachineIRBuilder & Builder
ConstantFP - Floating Point Values [float, double].
Definition Constants.h:420
bool isZero() const
Return true if the value is positive or negative zero.
Definition Constants.h:467
static LLVM_ABI FixedVectorType * get(Type *ElementType, unsigned NumElts)
Definition Type.cpp:867
Represents a G_BUILD_VECTOR.
Abstract class that contains various methods for clients to notify about changes.
LLT getElementType() const
Returns the vector's element type. Only valid for vector types.
DominatorTree Class - Concrete subclass of DominatorTreeBase that is used to compute a normal dominat...
Helper class to build MachineInstr.
Representation of each machine instruction.
unsigned getOpcode() const
Returns the opcode of this MachineInstr.
unsigned getNumOperands() const
Retuns the total number of operands.
const MachineOperand & getOperand(unsigned i) const
Register getReg() const
getReg - Returns the register number.
const MachineFunction & getMF() const
LLVM_ABI Register createGenericVirtualRegister(LLT Ty, StringRef Name="")
Create and return a new generic virtual register with low-level type Ty.
Wrapper class representing virtual and physical registers.
Definition Register.h:20
void applyMatrixMultiply(MachineInstr &MI) const
bool matchSelectToFaceForward(MachineInstr &MI) const
This match is part of a combine that rewrites select(fcmp(dot(I, Ng), 0), N, -N) to faceforward(N,...
void applyMatrixTranspose(MachineInstr &MI) const
LLVM_ABI CombinerHelper(GISelChangeObserver &Observer, MachineIRBuilder &B, bool IsPreLegalize, GISelValueTracking *VT=nullptr, MachineDominatorTree *MDT=nullptr, const LegalizerInfo *LI=nullptr)
void applySPIRVFaceForward(MachineInstr &MI) const
SPIRVCombinerHelper(GISelChangeObserver &Observer, MachineIRBuilder &B, bool IsPreLegalize, GISelValueTracking *VT, MachineDominatorTree *MDT, const LegalizerInfo *LI, const SPIRVSubtarget &STI)
const SPIRVSubtarget & STI
void applySPIRVDistance(MachineInstr &MI) const
bool matchLengthToDistance(MachineInstr &MI) const
This match is part of a combine that rewrites length(X - Y) to distance(X, Y) (f32 (g_intrinsic lengt...
LLT getRegType(SPIRVTypeInst SpvType) const
SPIRVTypeInst getScalarOrVectorComponentType(SPIRVTypeInst Type) const
SPIRVTypeInst getOrCreateSPIRVType(const Type *Type, MachineInstr &I, SPIRV::AccessQualifier::AccessQualifier AQ, bool EmitIR)
void push_back(const T &Elt)
This is a 'vector' (really, a variable-sized array), optimized for the case when the array is small.
bool isVectorTy() const
True if this is an instance of VectorType.
Definition Type.h:288
bool isFloatingPointTy() const
Return true if this is one of the floating-point types.
Definition Type.h:186
bool isIntegerTy() const
True if this is an instance of IntegerType.
Definition Type.h:257
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
constexpr std::underlying_type_t< E > Mask()
Get a bitmask with 1s in all places up to the high-order bit of E's largest value.
operand_type_match m_Reg()
GInstrBind< GBuildVector > m_GBuildVector(GBuildVector *&Inst)
operand_type_match m_Pred()
BinaryOp_match< LHS, RHS, TargetOpcode::G_FSUB, false > m_GFSub(const LHS &L, const RHS &R)
TernaryOp_match< Src0Ty, Src1Ty, Src2Ty, TargetOpcode::G_SELECT > m_GISelect(const Src0Ty &Src0, const Src1Ty &Src1, const Src2Ty &Src2)
bool mi_match(Reg R, const MachineRegisterInfo &MRI, Pattern &&P)
SpecificRegisterMatch m_SpecificReg(Register RequestedReg)
Matches a register only if it is equal to RequestedReg.
UnaryOp_match< SrcTy, TargetOpcode::G_FNEG > m_GFNeg(const SrcTy &Src)
GFCstAndRegMatch m_GFCst(std::optional< FPValueAndVReg > &FPValReg)
GFCstOrSplatGFCstMatch m_GFCstOrSplat(std::optional< FPValueAndVReg > &FPValReg)
BinaryOp_match< LHS, RHS, TargetOpcode::G_FMUL, true > m_GFMul(const LHS &L, const RHS &R)
GInstrBind< GIntrinsic > m_GIntrinsic(GIntrinsic *&Inst)
Binds the defining instruction of Reg if it is a GIntrinsic (any of the four G_INTRINSIC* opcodes).
CompareOp_match< Pred, LHS, RHS, TargetOpcode::G_FCMP > m_GFCmp(const Pred &P, const LHS &L, const RHS &R)
This is an optimization pass for GlobalISel generic memory operations.
void setRegClassType(Register Reg, SPIRVTypeInst SpvType, SPIRVGlobalRegistry *GR, MachineRegisterInfo *MRI, const MachineFunction &MF, bool Force)
class LLVM_GSL_OWNER SmallVector
Forward declaration of SmallVector so that calculateSmallVectorDefaultInlinedElements can reference s...
decltype(auto) cast(const From &Val)
cast<X> - Return the argument parameter cast to the specified type.
Definition Casting.h:559
Type * getMDOperandAsType(const MDNode *N, unsigned I)
bool isSpvIntrinsic(const MachineInstr &MI, Intrinsic::ID IntrinsicID)
void swap(llvm::BitVector &LHS, llvm::BitVector &RHS)
Implement std::swap in terms of BitVector swap.
Definition BitVector.h:880