MLIR 24.0.0git
SPIRVCanonicalization.cpp
Go to the documentation of this file.
1//===- SPIRVCanonicalization.cpp - MLIR SPIR-V canonicalization patterns --===//
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//
9// This file defines the folders and canonicalization patterns for SPIR-V ops.
10//
11//===----------------------------------------------------------------------===//
12
13#include <optional>
14#include <utility>
15
17
21#include "mlir/IR/Matchers.h"
23#include "llvm/ADT/STLExtras.h"
24#include "llvm/ADT/SmallVectorExtras.h"
25
26using namespace mlir;
27
28//===----------------------------------------------------------------------===//
29// Common utility functions
30//===----------------------------------------------------------------------===//
31
32/// Returns the boolean value under the hood if the given `boolAttr` is a scalar
33/// or splat vector bool constant.
34static std::optional<bool> getScalarOrSplatBoolAttr(Attribute attr) {
35 if (!attr)
36 return std::nullopt;
37
38 if (auto boolAttr = dyn_cast<BoolAttr>(attr))
39 return boolAttr.getValue();
40 if (auto splatAttr = dyn_cast<SplatElementsAttr>(attr))
41 if (splatAttr.getElementType().isInteger(1))
42 return splatAttr.getSplatValue<bool>();
43 return std::nullopt;
44}
45
46// Extracts an element from the given `composite` by following the given
47// `indices`. Returns a null Attribute if error happens.
50 // Check that given composite is a constant.
51 if (!composite)
52 return {};
53 // Return composite itself if we reach the end of the index chain.
54 if (indices.empty())
55 return composite;
56
57 if (auto vector = dyn_cast<ElementsAttr>(composite)) {
58 assert(indices.size() == 1 && "must have exactly one index for a vector");
59 return vector.getValues<Attribute>()[indices[0]];
60 }
61
62 if (auto array = dyn_cast<ArrayAttr>(composite)) {
63 assert(!indices.empty() && "must have at least one index for an array");
64 return extractCompositeElement(array.getValue()[indices[0]],
65 indices.drop_front());
66 }
67
68 return {};
69}
70
71static bool isDivZeroOrOverflow(const APInt &a, const APInt &b) {
72 bool div0 = b.isZero();
73 bool overflow = a.isMinSignedValue() && b.isAllOnes();
74
75 return div0 || overflow;
76}
77
78//===----------------------------------------------------------------------===//
79// TableGen'erated canonicalizers
80//===----------------------------------------------------------------------===//
81
82namespace {
83#include "SPIRVCanonicalization.inc"
84} // namespace
85
86//===----------------------------------------------------------------------===//
87// spirv.AccessChainOp / spirv.InBoundsAccessChainOp
88//===----------------------------------------------------------------------===//
89
90namespace {
91
92/// Combines chained SPIR-V access chain operations of the same kind into one.
93template <typename AccessChainOp>
94struct CombineChainedAccessChain final : OpRewritePattern<AccessChainOp> {
95 using OpRewritePattern<AccessChainOp>::OpRewritePattern;
96
97 LogicalResult matchAndRewrite(AccessChainOp accessChainOp,
98 PatternRewriter &rewriter) const override {
99 auto parentAccessChainOp =
100 accessChainOp.getBasePtr().template getDefiningOp<AccessChainOp>();
101
102 if (!parentAccessChainOp) {
103 return failure();
104 }
105
106 // Combine indices.
107 SmallVector<Value, 4> indices(parentAccessChainOp.getIndices());
108 llvm::append_range(indices, accessChainOp.getIndices());
109
110 rewriter.replaceOpWithNewOp<AccessChainOp>(
111 accessChainOp, parentAccessChainOp.getBasePtr(), indices);
112
113 return success();
114 }
115};
116} // namespace
117
118void spirv::AccessChainOp::getCanonicalizationPatterns(
119 RewritePatternSet &results, MLIRContext *context) {
120 results.add<CombineChainedAccessChain<spirv::AccessChainOp>>(context);
121}
122
123void spirv::InBoundsAccessChainOp::getCanonicalizationPatterns(
124 RewritePatternSet &results, MLIRContext *context) {
125 results.add<CombineChainedAccessChain<spirv::InBoundsAccessChainOp>>(context);
126}
127
128//===----------------------------------------------------------------------===//
129// spirv.IAddCarry / spirv.ISubBorrow
130//===----------------------------------------------------------------------===//
131
132template <typename Op>
135
136 static constexpr bool IsSub = std::is_same_v<Op, spirv::ISubBorrowOp>;
137
138 LogicalResult matchAndRewrite(Op op,
139 PatternRewriter &rewriter) const override {
140 Value lhs = op.getOperand1();
141 Value rhs = op.getOperand2();
142
143 // iaddcarry (x, 0) = <0, x>
144 // isubborrow (x, 0) = <x, 0>
145 if (matchPattern(rhs, m_Zero())) {
146 std::array<Value, 2> constituents =
147 IsSub ? std::array{lhs, rhs} : std::array{rhs, lhs};
148 rewriter.replaceOpWithNewOp<spirv::CompositeConstructOp>(op, op.getType(),
149 constituents);
150 return success();
151 }
152
153 Attribute lhsAttr;
154 Attribute rhsAttr;
155 if (!matchPattern(lhs, m_Constant(&lhsAttr)) ||
156 !matchPattern(rhs, m_Constant(&rhsAttr)))
157 return failure();
158
159 auto lowBits = constFoldBinaryOp<IntegerAttr>(
160 {lhsAttr, rhsAttr},
161 [](const APInt &a, const APInt &b) { return IsSub ? a - b : a + b; });
162 if (!lowBits)
163 return failure();
164
165 auto wrapBit = constFoldBinaryOp<IntegerAttr>(
166 {lhsAttr, rhsAttr}, [](const APInt &a, const APInt &b) {
167 bool wrapped = IsSub ? a.ult(b) : (a + b).ult(a);
168 return APInt(a.getBitWidth(), wrapped ? 1 : 0);
169 });
170 if (!wrapBit)
171 return failure();
172
173 rewriter.replaceOpWithNewOp<spirv::ConstantOp>(
174 op, op.getType(), rewriter.getArrayAttr({lowBits, wrapBit}));
175 return success();
176 }
177};
178
180void spirv::IAddCarryOp::getCanonicalizationPatterns(
181 RewritePatternSet &patterns, MLIRContext *context) {
182 patterns.add<IAddCarryFold>(context);
183}
184
186void spirv::ISubBorrowOp::getCanonicalizationPatterns(
187 RewritePatternSet &patterns, MLIRContext *context) {
188 patterns.add<ISubBorrowFold>(context);
189}
190
191//===----------------------------------------------------------------------===//
192// spirv.[S|U]MulExtended
193//===----------------------------------------------------------------------===//
194
195template <typename MulOp, bool IsSigned>
196struct MulExtendedFold final : OpRewritePattern<MulOp> {
198
199 LogicalResult matchAndRewrite(MulOp op,
200 PatternRewriter &rewriter) const override {
201 Location loc = op.getLoc();
202 Value lhs = op.getOperand1();
203 Value rhs = op.getOperand2();
204 Type constituentType = lhs.getType();
205
206 // [su]mulextended (x, 0) = <0, 0>
207 if (matchPattern(rhs, m_Zero())) {
208 Value zero = spirv::ConstantOp::getZero(constituentType, loc, rewriter);
209 Value constituents[2] = {zero, zero};
210 rewriter.replaceOpWithNewOp<spirv::CompositeConstructOp>(op, op.getType(),
211 constituents);
212 return success();
213 }
214
215 // According to the SPIR-V spec:
216 //
217 // Result Type must be from OpTypeStruct. The struct must have two
218 // members...
219 //
220 // Member 0 of the result gets the low-order bits of the multiplication.
221 //
222 // Member 1 of the result gets the high-order bits of the multiplication.
223 Attribute lhsAttr;
224 Attribute rhsAttr;
225 if (!matchPattern(lhs, m_Constant(&lhsAttr)) ||
226 !matchPattern(rhs, m_Constant(&rhsAttr)))
227 return failure();
228
229 auto lowBits = constFoldBinaryOp<IntegerAttr>(
230 {lhsAttr, rhsAttr},
231 [](const APInt &a, const APInt &b) { return a * b; });
232
233 if (!lowBits)
234 return failure();
235
236 auto highBits = constFoldBinaryOp<IntegerAttr>(
237 {lhsAttr, rhsAttr}, [](const APInt &a, const APInt &b) {
238 if (IsSigned) {
239 return llvm::APIntOps::mulhs(a, b);
240 }
241 return llvm::APIntOps::mulhu(a, b);
242 });
243
244 if (!highBits)
245 return failure();
246
247 rewriter.replaceOpWithNewOp<spirv::ConstantOp>(
248 op, op.getType(), rewriter.getArrayAttr({lowBits, highBits}));
249 return success();
250 }
251};
252
254void spirv::SMulExtendedOp::getCanonicalizationPatterns(
255 RewritePatternSet &patterns, MLIRContext *context) {
256 patterns.add<SMulExtendedOpFold>(context);
257}
258
259struct UMulExtendedOpXOne final : OpRewritePattern<spirv::UMulExtendedOp> {
260 using Base::Base;
261
262 LogicalResult matchAndRewrite(spirv::UMulExtendedOp op,
263 PatternRewriter &rewriter) const override {
264 Location loc = op.getLoc();
265 Value lhs = op.getOperand1();
266 Value rhs = op.getOperand2();
267 Type constituentType = lhs.getType();
268
269 // umulextended (x, 1) = <x, 0>
270 if (matchPattern(rhs, m_One())) {
271 Value zero = spirv::ConstantOp::getZero(constituentType, loc, rewriter);
272 Value constituents[2] = {lhs, zero};
273 rewriter.replaceOpWithNewOp<spirv::CompositeConstructOp>(op, op.getType(),
274 constituents);
275 return success();
276 }
277
278 return failure();
279 }
280};
281
283void spirv::UMulExtendedOp::getCanonicalizationPatterns(
284 RewritePatternSet &patterns, MLIRContext *context) {
285 patterns.add<UMulExtendedOpFold, UMulExtendedOpXOne>(context);
286}
287
288//===----------------------------------------------------------------------===//
289// spirv.UMod
290//===----------------------------------------------------------------------===//
291
292// Input:
293// %0 = spirv.UMod %arg0, %const32 : i32
294// %1 = spirv.UMod %0, %const4 : i32
295// Output:
296// %0 = spirv.UMod %arg0, %const32 : i32
297// %1 = spirv.UMod %arg0, %const4 : i32
298
299// The transformation is only applied if one divisor is a multiple of the other.
300
301struct UModSimplification final : OpRewritePattern<spirv::UModOp> {
302 using Base::Base;
303
304 LogicalResult matchAndRewrite(spirv::UModOp umodOp,
305 PatternRewriter &rewriter) const override {
306 auto prevUMod = umodOp.getOperand(0).getDefiningOp<spirv::UModOp>();
307 if (!prevUMod)
308 return failure();
309
310 TypedAttr prevValue;
311 TypedAttr currValue;
312 if (!matchPattern(prevUMod.getOperand(1), m_Constant(&prevValue)) ||
313 !matchPattern(umodOp.getOperand(1), m_Constant(&currValue)))
314 return failure();
315
316 // Ensure that previous divisor is a multiple of the current divisor. If
317 // not, fail the transformation.
318 bool isApplicable = false;
319 if (auto prevInt = dyn_cast<IntegerAttr>(prevValue)) {
320 auto currInt = cast<IntegerAttr>(currValue);
321 if (currInt.getValue().isZero())
322 return failure();
323 isApplicable = prevInt.getValue().urem(currInt.getValue()) == 0;
324 } else if (auto prevVec = dyn_cast<DenseElementsAttr>(prevValue)) {
325 auto currVec = cast<DenseElementsAttr>(currValue);
326 if (llvm::any_of(currVec.getValues<APInt>(),
327 [](const APInt &curr) { return curr.isZero(); }))
328 return failure();
329 isApplicable = llvm::all_of(llvm::zip_equal(prevVec.getValues<APInt>(),
330 currVec.getValues<APInt>()),
331 [](const auto &pair) {
332 auto &[prev, curr] = pair;
333 return prev.urem(curr) == 0;
334 });
335 }
336
337 if (!isApplicable)
338 return failure();
339
340 // The transformation is safe. Replace the existing UMod operation with a
341 // new UMod operation, using the original dividend and the current divisor.
342 rewriter.replaceOpWithNewOp<spirv::UModOp>(
343 umodOp, umodOp.getType(), prevUMod.getOperand(0), umodOp.getOperand(1));
344
345 return success();
346 }
347};
348
349void spirv::UModOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
350 MLIRContext *context) {
351 patterns.add<UModSimplification>(context);
352}
353
354//===----------------------------------------------------------------------===//
355// spirv.BitcastOp
356//===----------------------------------------------------------------------===//
357
358OpFoldResult spirv::BitcastOp::fold(FoldAdaptor /*adaptor*/) {
359 Value curInput = getOperand();
360 if (getType() == curInput.getType())
361 return curInput;
362
363 // Look through nested bitcasts.
364 if (auto prevCast = curInput.getDefiningOp<spirv::BitcastOp>()) {
365 Value prevInput = prevCast.getOperand();
366 if (prevInput.getType() == getType())
367 return prevInput;
368
369 getOperandMutable().assign(prevInput);
370 return getResult();
371 }
372
373 // TODO(kuhar): Consider constant-folding the operand attribute.
374 return {};
375}
376
377//===----------------------------------------------------------------------===//
378// spirv.CompositeExtractOp
379//===----------------------------------------------------------------------===//
380
381OpFoldResult spirv::CompositeExtractOp::fold(FoldAdaptor adaptor) {
382 Value compositeOp = getComposite();
383
384 while (auto insertOp =
385 compositeOp.getDefiningOp<spirv::CompositeInsertOp>()) {
386 if (getIndices() == insertOp.getIndices())
387 return insertOp.getObject();
388 compositeOp = insertOp.getComposite();
389 }
390
391 if (auto constructOp =
392 compositeOp.getDefiningOp<spirv::CompositeConstructOp>()) {
393 auto type = cast<spirv::CompositeType>(constructOp.getType());
394 if (getIndices().size() == 1 &&
395 constructOp.getConstituents().size() == type.getNumElements()) {
396 auto i = cast<IntegerAttr>(*getIndices().begin());
397 if (i.getValue().getSExtValue() <
398 static_cast<int64_t>(constructOp.getConstituents().size()))
399 return constructOp.getConstituents()[i.getValue().getSExtValue()];
400 }
401 }
402
403 auto indexVector = llvm::map_to_vector(getIndices(), [](Attribute attr) {
404 return static_cast<unsigned>(cast<IntegerAttr>(attr).getInt());
405 });
406 return extractCompositeElement(adaptor.getComposite(), indexVector);
407}
408
409//===----------------------------------------------------------------------===//
410// spirv.Constant
411//===----------------------------------------------------------------------===//
412
413OpFoldResult spirv::ConstantOp::fold(FoldAdaptor /*adaptor*/) {
414 return getValue();
415}
416
417//===----------------------------------------------------------------------===//
418// spirv.IAdd
419//===----------------------------------------------------------------------===//
420
421OpFoldResult spirv::IAddOp::fold(FoldAdaptor adaptor) {
422 // x + 0 = x
423 if (matchPattern(getOperand2(), m_Zero()))
424 return getOperand1();
425
426 // According to the SPIR-V spec:
427 //
428 // The resulting value will equal the low-order N bits of the correct result
429 // R, where N is the component width and R is computed with enough precision
430 // to avoid overflow and underflow.
432 adaptor.getOperands(),
433 [](APInt a, const APInt &b) { return std::move(a) + b; });
434}
435
436//===----------------------------------------------------------------------===//
437// spirv.IMul
438//===----------------------------------------------------------------------===//
439
440OpFoldResult spirv::IMulOp::fold(FoldAdaptor adaptor) {
441 // x * 0 == 0
442 if (matchPattern(getOperand2(), m_Zero()))
443 return getOperand2();
444 // x * 1 = x
445 if (matchPattern(getOperand2(), m_One()))
446 return getOperand1();
447
448 // According to the SPIR-V spec:
449 //
450 // The resulting value will equal the low-order N bits of the correct result
451 // R, where N is the component width and R is computed with enough precision
452 // to avoid overflow and underflow.
454 adaptor.getOperands(),
455 [](const APInt &a, const APInt &b) { return a * b; });
456}
457
458//===----------------------------------------------------------------------===//
459// spirv.ISub
460//===----------------------------------------------------------------------===//
461
462OpFoldResult spirv::ISubOp::fold(FoldAdaptor adaptor) {
463 // x - x = 0
464 if (getOperand1() == getOperand2())
466
467 // According to the SPIR-V spec:
468 //
469 // The resulting value will equal the low-order N bits of the correct result
470 // R, where N is the component width and R is computed with enough precision
471 // to avoid overflow and underflow.
473 adaptor.getOperands(),
474 [](APInt a, const APInt &b) { return std::move(a) - b; });
475}
476
477//===----------------------------------------------------------------------===//
478// spirv.SDiv
479//===----------------------------------------------------------------------===//
480
481OpFoldResult spirv::SDivOp::fold(FoldAdaptor adaptor) {
482 // sdiv (x, 1) = x
483 if (matchPattern(getOperand2(), m_One()))
484 return getOperand1();
485
486 // According to the SPIR-V spec:
487 //
488 // Signed-integer division of Operand 1 divided by Operand 2.
489 // Results are computed per component. Behavior is undefined if Operand 2 is
490 // 0. Behavior is undefined if Operand 2 is -1 and Operand 1 is the minimum
491 // representable value for the operands' type, causing signed overflow.
492 //
493 // So don't fold during undefined behavior.
494 bool div0OrOverflow = false;
496 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
497 if (div0OrOverflow || isDivZeroOrOverflow(a, b)) {
498 div0OrOverflow = true;
499 return a;
500 }
501 return a.sdiv(b);
502 });
503 return div0OrOverflow ? Attribute() : res;
504}
505
506//===----------------------------------------------------------------------===//
507// spirv.SMod
508//===----------------------------------------------------------------------===//
509
510OpFoldResult spirv::SModOp::fold(FoldAdaptor adaptor) {
511 // smod (x, 1) = 0
512 if (matchPattern(getOperand2(), m_One()))
514
515 // According to SPIR-V spec:
516 //
517 // Signed remainder operation for the remainder whose sign matches the sign
518 // of Operand 2. Behavior is undefined if Operand 2 is 0. Behavior is
519 // undefined if Operand 2 is -1 and Operand 1 is the minimum representable
520 // value for the operands' type, causing signed overflow. Otherwise, the
521 // result is the remainder r of Operand 1 divided by Operand 2 where if
522 // r ≠ 0, the sign of r is the same as the sign of Operand 2.
523 //
524 // So don't fold during undefined behavior
525 bool div0OrOverflow = false;
527 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
528 if (div0OrOverflow || isDivZeroOrOverflow(a, b)) {
529 div0OrOverflow = true;
530 return a;
531 }
532 APInt c = a.abs().urem(b.abs());
533 if (c.isZero())
534 return c;
535 if (b.isNegative()) {
536 APInt zero = APInt::getZero(c.getBitWidth());
537 return a.isNegative() ? (zero - c) : (b + c);
538 }
539 return a.isNegative() ? (b - c) : c;
540 });
541 return div0OrOverflow ? Attribute() : res;
542}
543
544//===----------------------------------------------------------------------===//
545// spirv.SRem
546//===----------------------------------------------------------------------===//
547
548OpFoldResult spirv::SRemOp::fold(FoldAdaptor adaptor) {
549 // x % 1 = 0
550 if (matchPattern(getOperand2(), m_One()))
552
553 // According to SPIR-V spec:
554 //
555 // Signed remainder operation for the remainder whose sign matches the sign
556 // of Operand 1. Behavior is undefined if Operand 2 is 0. Behavior is
557 // undefined if Operand 2 is -1 and Operand 1 is the minimum representable
558 // value for the operands' type, causing signed overflow. Otherwise, the
559 // result is the remainder r of Operand 1 divided by Operand 2 where if
560 // r ≠ 0, the sign of r is the same as the sign of Operand 1.
561
562 // Don't fold if it would do undefined behavior.
563 bool div0OrOverflow = false;
565 adaptor.getOperands(), [&](APInt a, const APInt &b) {
566 if (div0OrOverflow || isDivZeroOrOverflow(a, b)) {
567 div0OrOverflow = true;
568 return a;
569 }
570 return a.srem(b);
571 });
572 return div0OrOverflow ? Attribute() : res;
573}
574
575//===----------------------------------------------------------------------===//
576// spirv.UDiv
577//===----------------------------------------------------------------------===//
578
579OpFoldResult spirv::UDivOp::fold(FoldAdaptor adaptor) {
580 // udiv (x, 1) = x
581 if (matchPattern(getOperand2(), m_One()))
582 return getOperand1();
583
584 // According to the SPIR-V spec:
585 //
586 // Unsigned-integer division of Operand 1 divided by Operand 2. Behavior is
587 // undefined if Operand 2 is 0.
588 //
589 // So don't fold during undefined behavior.
590 bool div0 = false;
592 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
593 if (div0 || b.isZero()) {
594 div0 = true;
595 return a;
596 }
597 return a.udiv(b);
598 });
599 return div0 ? Attribute() : res;
600}
601
602//===----------------------------------------------------------------------===//
603// spirv.UMod
604//===----------------------------------------------------------------------===//
605
606OpFoldResult spirv::UModOp::fold(FoldAdaptor adaptor) {
607 // umod (x, 1) = 0
608 if (matchPattern(getOperand2(), m_One()))
610
611 // According to the SPIR-V spec:
612 //
613 // Unsigned modulo operation of Operand 1 modulo Operand 2. Behavior is
614 // undefined if Operand 2 is 0.
615 //
616 // So don't fold during undefined behavior.
617 bool div0 = false;
619 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
620 if (div0 || b.isZero()) {
621 div0 = true;
622 return a;
623 }
624 return a.urem(b);
625 });
626 return div0 ? Attribute() : res;
627}
628
629//===----------------------------------------------------------------------===//
630// spirv.SNegate
631//===----------------------------------------------------------------------===//
632
633OpFoldResult spirv::SNegateOp::fold(FoldAdaptor adaptor) {
634 // -(-x) = 0 - (0 - x) = x
635 auto op = getOperand();
636 if (auto negateOp = op.getDefiningOp<spirv::SNegateOp>())
637 return negateOp->getOperand(0);
638
639 // According to the SPIR-V spec:
640 //
641 // Signed-integer subtract of Operand from zero.
643 adaptor.getOperands(), [](const APInt &a) {
644 APInt zero = APInt::getZero(a.getBitWidth());
645 return zero - a;
646 });
647}
648
649//===----------------------------------------------------------------------===//
650// spirv.NotOp
651//===----------------------------------------------------------------------===//
652
653OpFoldResult spirv::NotOp::fold(spirv::NotOp::FoldAdaptor adaptor) {
654 // !(!x) = x
655 auto op = getOperand();
656 if (auto notOp = op.getDefiningOp<spirv::NotOp>())
657 return notOp->getOperand(0);
658
659 // According to the SPIR-V spec:
660 //
661 // Complement the bits of Operand.
662 return constFoldUnaryOp<IntegerAttr>(adaptor.getOperands(), [&](APInt a) {
663 a.flipAllBits();
664 return a;
665 });
666}
667
668//===----------------------------------------------------------------------===//
669// spirv.LogicalAnd
670//===----------------------------------------------------------------------===//
671
672OpFoldResult spirv::LogicalAndOp::fold(FoldAdaptor adaptor) {
673 if (std::optional<bool> rhs =
674 getScalarOrSplatBoolAttr(adaptor.getOperand2())) {
675 // x && true = x
676 if (*rhs)
677 return getOperand1();
678
679 // x && false = false
680 if (!*rhs)
681 return adaptor.getOperand2();
682 }
683
684 return Attribute();
685}
686
687//===----------------------------------------------------------------------===//
688// spirv.LogicalEqualOp
689//===----------------------------------------------------------------------===//
690
692spirv::LogicalEqualOp::fold(spirv::LogicalEqualOp::FoldAdaptor adaptor) {
693 // x == x -> true
694 if (getOperand1() == getOperand2()) {
695 auto trueAttr = BoolAttr::get(getContext(), true);
696 if (isa<IntegerType>(getType()))
697 return trueAttr;
698 if (auto vecTy = dyn_cast<VectorType>(getType()))
699 return SplatElementsAttr::get(vecTy, trueAttr);
700 }
701
703 adaptor.getOperands(), [](const APInt &a, const APInt &b) {
704 return a == b ? APInt::getAllOnes(1) : APInt::getZero(1);
705 });
706}
707
708//===----------------------------------------------------------------------===//
709// spirv.LogicalNotEqualOp
710//===----------------------------------------------------------------------===//
711
712OpFoldResult spirv::LogicalNotEqualOp::fold(FoldAdaptor adaptor) {
713 if (std::optional<bool> rhs =
714 getScalarOrSplatBoolAttr(adaptor.getOperand2())) {
715 // x != false -> x
716 if (!rhs.value())
717 return getOperand1();
718 }
719
720 // x == x -> false
721 if (getOperand1() == getOperand2()) {
722 auto falseAttr = BoolAttr::get(getContext(), false);
723 if (isa<IntegerType>(getType()))
724 return falseAttr;
725 if (auto vecTy = dyn_cast<VectorType>(getType()))
726 return SplatElementsAttr::get(vecTy, falseAttr);
727 }
728
730 adaptor.getOperands(), [](const APInt &a, const APInt &b) {
731 return a == b ? APInt::getZero(1) : APInt::getAllOnes(1);
732 });
733}
734
735//===----------------------------------------------------------------------===//
736// spirv.LogicalNot
737//===----------------------------------------------------------------------===//
738
739OpFoldResult spirv::LogicalNotOp::fold(FoldAdaptor adaptor) {
740 // !(!x) = x
741 auto op = getOperand();
742 if (auto notOp = op.getDefiningOp<spirv::LogicalNotOp>())
743 return notOp->getOperand(0);
744
745 // According to the SPIR-V spec:
746 //
747 // Complement the bits of Operand.
748 return constFoldUnaryOp<IntegerAttr>(adaptor.getOperands(),
749 [](const APInt &a) {
750 APInt zero = APInt::getZero(1);
751 return a == 1 ? zero : (zero + 1);
752 });
753}
754
755void spirv::LogicalNotOp::getCanonicalizationPatterns(
756 RewritePatternSet &results, MLIRContext *context) {
757 results
758 .add<ConvertLogicalNotOfIEqual, ConvertLogicalNotOfINotEqual,
759 ConvertLogicalNotOfLogicalEqual, ConvertLogicalNotOfLogicalNotEqual>(
760 context);
761}
762
763//===----------------------------------------------------------------------===//
764// spirv.LogicalOr
765//===----------------------------------------------------------------------===//
766
767OpFoldResult spirv::LogicalOrOp::fold(FoldAdaptor adaptor) {
768 if (auto rhs = getScalarOrSplatBoolAttr(adaptor.getOperand2())) {
769 if (*rhs) {
770 // x || true = true
771 return adaptor.getOperand2();
772 }
773
774 if (!*rhs) {
775 // x || false = x
776 return getOperand1();
777 }
778 }
779
780 return Attribute();
781}
782
783//===----------------------------------------------------------------------===//
784// spirv.SelectOp
785//===----------------------------------------------------------------------===//
786
787OpFoldResult spirv::SelectOp::fold(FoldAdaptor adaptor) {
788 // spirv.Select _ x x -> x
789 Value trueVals = getTrueValue();
790 Value falseVals = getFalseValue();
791 if (trueVals == falseVals)
792 return trueVals;
793
794 ArrayRef<Attribute> operands = adaptor.getOperands();
795
796 // spirv.Select true x y -> x
797 // spirv.Select false x y -> y
798 if (auto boolAttr = getScalarOrSplatBoolAttr(operands[0]))
799 return *boolAttr ? trueVals : falseVals;
800
801 // Check that all the operands are constant
802 if (!operands[0] || !operands[1] || !operands[2])
803 return Attribute();
804
805 // Note: getScalarOrSplatBoolAttr will always return a boolAttr if we are in
806 // the scalar case. Hence, we are only required to consider the case of
807 // DenseElementsAttr in foldSelectOp.
808 auto condAttrs = dyn_cast<DenseElementsAttr>(operands[0]);
809 auto trueAttrs = dyn_cast<DenseElementsAttr>(operands[1]);
810 auto falseAttrs = dyn_cast<DenseElementsAttr>(operands[2]);
811 if (!condAttrs || !trueAttrs || !falseAttrs)
812 return Attribute();
813
814 auto elementResults = llvm::to_vector<4>(trueAttrs.getValues<Attribute>());
815 auto iters = llvm::zip_equal(elementResults, condAttrs.getValues<BoolAttr>(),
816 falseAttrs.getValues<Attribute>());
817 for (auto [result, cond, falseRes] : iters) {
818 if (!cond.getValue())
819 result = falseRes;
820 }
821
822 auto resultType = trueAttrs.getType();
823 return DenseElementsAttr::get(cast<ShapedType>(resultType), elementResults);
824}
825
826//===----------------------------------------------------------------------===//
827// spirv.IEqualOp
828//===----------------------------------------------------------------------===//
829
830OpFoldResult spirv::IEqualOp::fold(spirv::IEqualOp::FoldAdaptor adaptor) {
831 // x == x -> true
832 if (getOperand1() == getOperand2()) {
833 auto trueAttr = BoolAttr::get(getContext(), true);
834 if (isa<IntegerType>(getType()))
835 return trueAttr;
836 if (auto vecTy = dyn_cast<VectorType>(getType()))
837 return SplatElementsAttr::get(vecTy, trueAttr);
838 }
839
841 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
842 return a == b ? APInt::getAllOnes(1) : APInt::getZero(1);
843 });
844}
845
846//===----------------------------------------------------------------------===//
847// spirv.INotEqualOp
848//===----------------------------------------------------------------------===//
849
850OpFoldResult spirv::INotEqualOp::fold(spirv::INotEqualOp::FoldAdaptor adaptor) {
851 // x == x -> false
852 if (getOperand1() == getOperand2()) {
853 auto falseAttr = BoolAttr::get(getContext(), false);
854 if (isa<IntegerType>(getType()))
855 return falseAttr;
856 if (auto vecTy = dyn_cast<VectorType>(getType()))
857 return SplatElementsAttr::get(vecTy, falseAttr);
858 }
859
861 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
862 return a == b ? APInt::getZero(1) : APInt::getAllOnes(1);
863 });
864}
865
866//===----------------------------------------------------------------------===//
867// spirv.SGreaterThan
868//===----------------------------------------------------------------------===//
869
871spirv::SGreaterThanOp::fold(spirv::SGreaterThanOp::FoldAdaptor adaptor) {
872 // x == x -> false
873 if (getOperand1() == getOperand2()) {
874 auto falseAttr = BoolAttr::get(getContext(), false);
875 if (isa<IntegerType>(getType()))
876 return falseAttr;
877 if (auto vecTy = dyn_cast<VectorType>(getType()))
878 return SplatElementsAttr::get(vecTy, falseAttr);
879 }
880
882 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
883 return a.sgt(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
884 });
885}
886
887//===----------------------------------------------------------------------===//
888// spirv.SGreaterThanEqual
889//===----------------------------------------------------------------------===//
890
891OpFoldResult spirv::SGreaterThanEqualOp::fold(
892 spirv::SGreaterThanEqualOp::FoldAdaptor adaptor) {
893 // x == x -> true
894 if (getOperand1() == getOperand2()) {
895 auto trueAttr = BoolAttr::get(getContext(), true);
896 if (isa<IntegerType>(getType()))
897 return trueAttr;
898 if (auto vecTy = dyn_cast<VectorType>(getType()))
899 return SplatElementsAttr::get(vecTy, trueAttr);
900 }
901
903 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
904 return a.sge(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
905 });
906}
907
908//===----------------------------------------------------------------------===//
909// spirv.UGreaterThan
910//===----------------------------------------------------------------------===//
911
913spirv::UGreaterThanOp::fold(spirv::UGreaterThanOp::FoldAdaptor adaptor) {
914 // x == x -> false
915 if (getOperand1() == getOperand2()) {
916 auto falseAttr = BoolAttr::get(getContext(), false);
917 if (isa<IntegerType>(getType()))
918 return falseAttr;
919 if (auto vecTy = dyn_cast<VectorType>(getType()))
920 return SplatElementsAttr::get(vecTy, falseAttr);
921 }
922
924 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
925 return a.ugt(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
926 });
927}
928
929//===----------------------------------------------------------------------===//
930// spirv.UGreaterThanEqual
931//===----------------------------------------------------------------------===//
932
933OpFoldResult spirv::UGreaterThanEqualOp::fold(
934 spirv::UGreaterThanEqualOp::FoldAdaptor adaptor) {
935 // x == x -> true
936 if (getOperand1() == getOperand2()) {
937 auto trueAttr = BoolAttr::get(getContext(), true);
938 if (isa<IntegerType>(getType()))
939 return trueAttr;
940 if (auto vecTy = dyn_cast<VectorType>(getType()))
941 return SplatElementsAttr::get(vecTy, trueAttr);
942 }
943
945 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
946 return a.uge(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
947 });
948}
949
950//===----------------------------------------------------------------------===//
951// spirv.SLessThan
952//===----------------------------------------------------------------------===//
953
954OpFoldResult spirv::SLessThanOp::fold(spirv::SLessThanOp::FoldAdaptor adaptor) {
955 // x == x -> false
956 if (getOperand1() == getOperand2()) {
957 auto falseAttr = BoolAttr::get(getContext(), false);
958 if (isa<IntegerType>(getType()))
959 return falseAttr;
960 if (auto vecTy = dyn_cast<VectorType>(getType()))
961 return SplatElementsAttr::get(vecTy, falseAttr);
962 }
963
965 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
966 return a.slt(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
967 });
968}
969
970//===----------------------------------------------------------------------===//
971// spirv.SLessThanEqual
972//===----------------------------------------------------------------------===//
973
975spirv::SLessThanEqualOp::fold(spirv::SLessThanEqualOp::FoldAdaptor adaptor) {
976 // x == x -> true
977 if (getOperand1() == getOperand2()) {
978 auto trueAttr = BoolAttr::get(getContext(), true);
979 if (isa<IntegerType>(getType()))
980 return trueAttr;
981 if (auto vecTy = dyn_cast<VectorType>(getType()))
982 return SplatElementsAttr::get(vecTy, trueAttr);
983 }
984
986 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
987 return a.sle(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
988 });
989}
990
991//===----------------------------------------------------------------------===//
992// spirv.ULessThan
993//===----------------------------------------------------------------------===//
994
995OpFoldResult spirv::ULessThanOp::fold(spirv::ULessThanOp::FoldAdaptor adaptor) {
996 // x == x -> false
997 if (getOperand1() == getOperand2()) {
998 auto falseAttr = BoolAttr::get(getContext(), false);
999 if (isa<IntegerType>(getType()))
1000 return falseAttr;
1001 if (auto vecTy = dyn_cast<VectorType>(getType()))
1002 return SplatElementsAttr::get(vecTy, falseAttr);
1003 }
1004
1006 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
1007 return a.ult(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
1008 });
1009}
1010
1011//===----------------------------------------------------------------------===//
1012// spirv.ULessThanEqual
1013//===----------------------------------------------------------------------===//
1014
1016spirv::ULessThanEqualOp::fold(spirv::ULessThanEqualOp::FoldAdaptor adaptor) {
1017 // x == x -> true
1018 if (getOperand1() == getOperand2()) {
1019 auto trueAttr = BoolAttr::get(getContext(), true);
1020 if (isa<IntegerType>(getType()))
1021 return trueAttr;
1022 if (auto vecTy = dyn_cast<VectorType>(getType()))
1023 return SplatElementsAttr::get(vecTy, trueAttr);
1024 }
1025
1027 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
1028 return a.ule(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
1029 });
1030}
1031
1032//===----------------------------------------------------------------------===//
1033// spirv.ShiftLeftLogical
1034//===----------------------------------------------------------------------===//
1035
1036OpFoldResult spirv::ShiftLeftLogicalOp::fold(
1037 spirv::ShiftLeftLogicalOp::FoldAdaptor adaptor) {
1038 // x << 0 -> x
1039 if (matchPattern(adaptor.getOperand2(), m_Zero())) {
1040 return getOperand1();
1041 }
1042
1043 // Unfortunately due to below undefined behaviour can't fold 0 for Base.
1044
1045 // Results are computed per component, and within each component, per bit...
1046 //
1047 // The result is undefined if Shift is greater than or equal to the bit width
1048 // of the components of Base.
1049 //
1050 // So we can use the APInt << method, but don't fold if undefined behaviour.
1051 bool shiftToLarge = false;
1053 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
1054 if (shiftToLarge || b.uge(a.getBitWidth())) {
1055 shiftToLarge = true;
1056 return a;
1057 }
1058 return a << b;
1059 });
1060 return shiftToLarge ? Attribute() : res;
1061}
1062
1063//===----------------------------------------------------------------------===//
1064// spirv.ShiftRightArithmetic
1065//===----------------------------------------------------------------------===//
1066
1067OpFoldResult spirv::ShiftRightArithmeticOp::fold(
1068 spirv::ShiftRightArithmeticOp::FoldAdaptor adaptor) {
1069 // x >> 0 -> x
1070 if (matchPattern(adaptor.getOperand2(), m_Zero())) {
1071 return getOperand1();
1072 }
1073
1074 // Unfortunately due to below undefined behaviour can't fold 0, -1 for Base.
1075
1076 // Results are computed per component, and within each component, per bit...
1077 //
1078 // The result is undefined if Shift is greater than or equal to the bit width
1079 // of the components of Base.
1080 //
1081 // So we can use the APInt ashr method, but don't fold if undefined behaviour.
1082 bool shiftToLarge = false;
1084 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
1085 if (shiftToLarge || b.uge(a.getBitWidth())) {
1086 shiftToLarge = true;
1087 return a;
1088 }
1089 return a.ashr(b);
1090 });
1091 return shiftToLarge ? Attribute() : res;
1092}
1093
1094//===----------------------------------------------------------------------===//
1095// spirv.ShiftRightLogical
1096//===----------------------------------------------------------------------===//
1097
1098OpFoldResult spirv::ShiftRightLogicalOp::fold(
1099 spirv::ShiftRightLogicalOp::FoldAdaptor adaptor) {
1100 // x >> 0 -> x
1101 if (matchPattern(adaptor.getOperand2(), m_Zero())) {
1102 return getOperand1();
1103 }
1104
1105 // Unfortunately due to below undefined behaviour can't fold 0 for Base.
1106
1107 // Results are computed per component, and within each component, per bit...
1108 //
1109 // The result is undefined if Shift is greater than or equal to the bit width
1110 // of the components of Base.
1111 //
1112 // So we can use the APInt lshr method, but don't fold if undefined behaviour.
1113 bool shiftToLarge = false;
1115 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
1116 if (shiftToLarge || b.uge(a.getBitWidth())) {
1117 shiftToLarge = true;
1118 return a;
1119 }
1120 return a.lshr(b);
1121 });
1122 return shiftToLarge ? Attribute() : res;
1123}
1124
1125//===----------------------------------------------------------------------===//
1126// spirv.BitwiseAndOp
1127//===----------------------------------------------------------------------===//
1128
1130spirv::BitwiseAndOp::fold(spirv::BitwiseAndOp::FoldAdaptor adaptor) {
1131 // x & x -> x
1132 if (getOperand1() == getOperand2()) {
1133 return getOperand1();
1134 }
1135
1136 APInt rhsMask;
1137 if (matchPattern(adaptor.getOperand2(), m_ConstantInt(&rhsMask))) {
1138 // x & 0 -> 0
1139 if (rhsMask.isZero())
1140 return getOperand2();
1141
1142 // x & <all ones> -> x
1143 if (rhsMask.isAllOnes())
1144 return getOperand1();
1145
1146 // (UConvert x : iN to iK) & <mask with N low bits set> -> UConvert x
1147 if (auto zext = getOperand1().getDefiningOp<spirv::UConvertOp>()) {
1148 int valueBits =
1150 if (rhsMask.zextOrTrunc(valueBits).isAllOnes())
1151 return getOperand1();
1152 }
1153 }
1154
1155 // According to the SPIR-V spec:
1156 //
1157 // Type is a scalar or vector of integer type.
1158 // Results are computed per component, and within each component, per bit.
1159 // So we can use the APInt & method.
1161 adaptor.getOperands(),
1162 [](const APInt &a, const APInt &b) { return a & b; });
1163}
1164
1165//===----------------------------------------------------------------------===//
1166// spirv.BitwiseOrOp
1167//===----------------------------------------------------------------------===//
1168
1169OpFoldResult spirv::BitwiseOrOp::fold(spirv::BitwiseOrOp::FoldAdaptor adaptor) {
1170 // x | x -> x
1171 if (getOperand1() == getOperand2()) {
1172 return getOperand1();
1173 }
1174
1175 APInt rhsMask;
1176 if (matchPattern(adaptor.getOperand2(), m_ConstantInt(&rhsMask))) {
1177 // x | 0 -> x
1178 if (rhsMask.isZero())
1179 return getOperand1();
1180
1181 // x | <all ones> -> <all ones>
1182 if (rhsMask.isAllOnes())
1183 return getOperand2();
1184 }
1185
1186 // According to the SPIR-V spec:
1187 //
1188 // Type is a scalar or vector of integer type.
1189 // Results are computed per component, and within each component, per bit.
1190 // So we can use the APInt | method.
1192 adaptor.getOperands(),
1193 [](const APInt &a, const APInt &b) { return a | b; });
1194}
1195
1196//===----------------------------------------------------------------------===//
1197// spirv.BitwiseXorOp
1198//===----------------------------------------------------------------------===//
1199
1201spirv::BitwiseXorOp::fold(spirv::BitwiseXorOp::FoldAdaptor adaptor) {
1202 // x ^ 0 -> x
1203 if (matchPattern(adaptor.getOperand2(), m_Zero())) {
1204 return getOperand1();
1205 }
1206
1207 // x ^ x -> 0
1208 if (getOperand1() == getOperand2())
1210
1211 // According to the SPIR-V spec:
1212 //
1213 // Type is a scalar or vector of integer type.
1214 // Results are computed per component, and within each component, per bit.
1215 // So we can use the APInt ^ method.
1217 adaptor.getOperands(),
1218 [](const APInt &a, const APInt &b) { return a ^ b; });
1219}
1220
1221//===----------------------------------------------------------------------===//
1222// spirv.mlir.selection
1223//===----------------------------------------------------------------------===//
1224
1225namespace {
1226// Blocks from the given `spirv.mlir.selection` operation must satisfy the
1227// following layout:
1228//
1229// +-----------------------------------------------+
1230// | header block |
1231// | spirv.BranchConditionalOp %cond, ^case0, ^case1 |
1232// +-----------------------------------------------+
1233// / \
1234// ...
1235//
1236//
1237// +------------------------+ +------------------------+
1238// | case #0 | | case #1 |
1239// | spirv.Store %ptr %value0 | | spirv.Store %ptr %value1 |
1240// | spirv.Branch ^merge | | spirv.Branch ^merge |
1241// +------------------------+ +------------------------+
1242//
1243//
1244// ...
1245// \ /
1246// v
1247// +-------------+
1248// | merge block |
1249// +-------------+
1250//
1251struct ConvertSelectionOpToSelect final : OpRewritePattern<spirv::SelectionOp> {
1252 using Base::Base;
1253
1254 LogicalResult matchAndRewrite(spirv::SelectionOp selectionOp,
1255 PatternRewriter &rewriter) const override {
1256 Operation *op = selectionOp.getOperation();
1257 Region &body = op->getRegion(0);
1258 // Verifier allows an empty region for `spirv.mlir.selection`.
1259 if (body.empty()) {
1260 return failure();
1261 }
1262
1263 // Check that region consists of 4 blocks:
1264 // header block, `true` block, `false` block and merge block.
1265 if (llvm::range_size(body) != 4) {
1266 return failure();
1267 }
1268
1269 Block *headerBlock = selectionOp.getHeaderBlock();
1270 if (!onlyContainsBranchConditionalOp(headerBlock)) {
1271 return failure();
1272 }
1273
1274 auto brConditionalOp =
1275 cast<spirv::BranchConditionalOp>(headerBlock->front());
1276
1277 Block *trueBlock = brConditionalOp.getSuccessor(0);
1278 Block *falseBlock = brConditionalOp.getSuccessor(1);
1279 Block *mergeBlock = selectionOp.getMergeBlock();
1280
1281 if (failed(canCanonicalizeSelection(trueBlock, falseBlock, mergeBlock)))
1282 return failure();
1283
1284 Value trueValue = getSrcValue(trueBlock);
1285 Value falseValue = getSrcValue(falseBlock);
1286 Value ptrValue = getDstPtr(trueBlock);
1287 auto storeOp = cast<spirv::StoreOp>(trueBlock->front());
1288
1289 auto selectOp = spirv::SelectOp::create(
1290 rewriter, selectionOp.getLoc(), trueValue.getType(),
1291 brConditionalOp.getCondition(), trueValue, falseValue);
1292 auto newStore = spirv::StoreOp::create(
1293 rewriter, selectOp.getLoc(), ptrValue, selectOp.getResult(),
1294 storeOp.getMemoryAccessAttr(), storeOp.getAlignmentAttr());
1295 newStore->setDiscardableAttrs(storeOp->getDiscardableAttrDictionary());
1296
1297 // `spirv.mlir.selection` is not needed anymore.
1298 rewriter.eraseOp(op);
1299 return success();
1300 }
1301
1302private:
1303 // Checks that given blocks follow the following rules:
1304 // 1. Each conditional block consists of two operations, the first operation
1305 // is a `spirv.Store` and the last operation is a `spirv.Branch`.
1306 // 2. Each `spirv.Store` uses the same pointer and the same memory attributes.
1307 // 3. A control flow goes into the given merge block from the given
1308 // conditional blocks.
1309 LogicalResult canCanonicalizeSelection(Block *trueBlock, Block *falseBlock,
1310 Block *mergeBlock) const;
1311
1312 bool onlyContainsBranchConditionalOp(Block *block) const {
1313 return llvm::hasSingleElement(*block) &&
1314 isa<spirv::BranchConditionalOp>(block->front());
1315 }
1316
1317 bool isSameAttrList(spirv::StoreOp lhs, spirv::StoreOp rhs) const {
1318 return lhs->getDiscardableAttrDictionary() ==
1319 rhs->getDiscardableAttrDictionary() &&
1320 lhs.getProperties() == rhs.getProperties();
1321 }
1322
1323 // Returns a source value for the given block.
1324 Value getSrcValue(Block *block) const {
1325 auto storeOp = cast<spirv::StoreOp>(block->front());
1326 return storeOp.getValue();
1327 }
1328
1329 // Returns a destination value for the given block.
1330 Value getDstPtr(Block *block) const {
1331 auto storeOp = cast<spirv::StoreOp>(block->front());
1332 return storeOp.getPtr();
1333 }
1334};
1335
1336LogicalResult ConvertSelectionOpToSelect::canCanonicalizeSelection(
1337 Block *trueBlock, Block *falseBlock, Block *mergeBlock) const {
1338 // Each block must consists of 2 operations.
1339 if (llvm::range_size(*trueBlock) != 2 || llvm::range_size(*falseBlock) != 2) {
1340 return failure();
1341 }
1342
1343 auto trueBrStoreOp = dyn_cast<spirv::StoreOp>(trueBlock->front());
1344 auto trueBrBranchOp =
1345 dyn_cast<spirv::BranchOp>(*std::next(trueBlock->begin()));
1346 auto falseBrStoreOp = dyn_cast<spirv::StoreOp>(falseBlock->front());
1347 auto falseBrBranchOp =
1348 dyn_cast<spirv::BranchOp>(*std::next(falseBlock->begin()));
1349
1350 if (!trueBrStoreOp || !trueBrBranchOp || !falseBrStoreOp ||
1351 !falseBrBranchOp) {
1352 return failure();
1353 }
1354
1355 // Checks that given type is valid for `spirv.SelectOp`.
1356 // According to SPIR-V spec:
1357 // "Before version 1.4, Result Type must be a pointer, scalar, or vector.
1358 // Starting with version 1.4, Result Type can additionally be a composite type
1359 // other than a vector."
1360 bool isScalarOrVector =
1361 cast<spirv::SPIRVType>(trueBrStoreOp.getValue().getType())
1362 .isScalarOrVector();
1363
1364 // Check that each `spirv.Store` uses the same pointer, memory access
1365 // attributes and a valid type of the value.
1366 if ((trueBrStoreOp.getPtr() != falseBrStoreOp.getPtr()) ||
1367 !isSameAttrList(trueBrStoreOp, falseBrStoreOp) || !isScalarOrVector) {
1368 return failure();
1369 }
1370
1371 if ((trueBrBranchOp->getSuccessor(0) != mergeBlock) ||
1372 (falseBrBranchOp->getSuccessor(0) != mergeBlock)) {
1373 return failure();
1374 }
1375
1376 return success();
1377}
1378} // namespace
1379
1380void spirv::SelectionOp::getCanonicalizationPatterns(RewritePatternSet &results,
1381 MLIRContext *context) {
1382 results.add<ConvertSelectionOpToSelect>(context);
1383}
return success()
static Value getZero(OpBuilder &b, Location loc, Type elementType)
Get zero value for an element type.
static uint64_t zext(uint32_t arg)
lhs
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
if(!isCopyOut)
b getContext())
ArithmeticExtendedBinaryFold< spirv::ISubBorrowOp > ISubBorrowFold
static Attribute extractCompositeElement(Attribute composite, ArrayRef< unsigned > indices)
MulExtendedFold< spirv::UMulExtendedOp, false > UMulExtendedOpFold
MulExtendedFold< spirv::SMulExtendedOp, true > SMulExtendedOpFold
static std::optional< bool > getScalarOrSplatBoolAttr(Attribute attr)
Returns the boolean value under the hood if the given boolAttr is a scalar or splat vector bool const...
static bool isDivZeroOrOverflow(const APInt &a, const APInt &b)
ArithmeticExtendedBinaryFold< spirv::IAddCarryOp > IAddCarryFold
Attributes are known-constant values of operations.
Definition Attributes.h:25
Block represents an ordered list of Operations.
Definition Block.h:33
Operation & front()
Definition Block.h:177
iterator begin()
Definition Block.h:167
Special case of IntegerAttr to represent boolean integers, i.e., signless i1 integers.
static BoolAttr get(MLIRContext *context, bool value)
This class is a general helper class for creating context-global objects like types,...
Definition Builders.h:51
TypedAttr getZeroAttr(Type type)
Definition Builders.cpp:333
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
Definition Builders.cpp:275
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class represents a single result from folding an operation.
This provides public APIs that all operations should have.
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:731
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
bool empty()
Definition Region.h:60
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Operation::operand_range getIndices(Operation *op)
Get the indices that the given load/store operation is operating on.
Definition Utils.cpp:18
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
Definition Matchers.h:527
Attribute constFoldBinaryOp(ArrayRef< Attribute > operands, Type resultType, CalculationT &&calculate)
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:307
detail::constant_int_predicate_matcher m_Zero()
Matches a constant scalar / vector splat / tensor splat integer zero.
Definition Matchers.h:442
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
detail::constant_int_predicate_matcher m_One()
Matches a constant scalar / vector splat / tensor splat integer one.
Definition Matchers.h:478
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
Attribute constFoldUnaryOp(ArrayRef< Attribute > operands, Type resultType, CalculationT &&calculate)
LogicalResult matchAndRewrite(Op op, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(MulOp op, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(spirv::UModOp umodOp, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(spirv::UMulExtendedOp op, PatternRewriter &rewriter) const override
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpRewritePattern Base
Type alias to allow derived classes to inherit constructors with using Base::Base;.
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})