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() ? (std::move(zero) - c) : (b + std::move(c));
538 }
539 if (a.isNegative())
540 return b - std::move(c);
541 return c;
542 });
543 return div0OrOverflow ? Attribute() : res;
544}
545
546//===----------------------------------------------------------------------===//
547// spirv.SRem
548//===----------------------------------------------------------------------===//
549
550OpFoldResult spirv::SRemOp::fold(FoldAdaptor adaptor) {
551 // x % 1 = 0
552 if (matchPattern(getOperand2(), m_One()))
554
555 // According to SPIR-V spec:
556 //
557 // Signed remainder operation for the remainder whose sign matches the sign
558 // of Operand 1. Behavior is undefined if Operand 2 is 0. Behavior is
559 // undefined if Operand 2 is -1 and Operand 1 is the minimum representable
560 // value for the operands' type, causing signed overflow. Otherwise, the
561 // result is the remainder r of Operand 1 divided by Operand 2 where if
562 // r ≠ 0, the sign of r is the same as the sign of Operand 1.
563
564 // Don't fold if it would do undefined behavior.
565 bool div0OrOverflow = false;
567 adaptor.getOperands(), [&](APInt a, const APInt &b) {
568 if (div0OrOverflow || isDivZeroOrOverflow(a, b)) {
569 div0OrOverflow = true;
570 return a;
571 }
572 return a.srem(b);
573 });
574 return div0OrOverflow ? Attribute() : res;
575}
576
577//===----------------------------------------------------------------------===//
578// spirv.UDiv
579//===----------------------------------------------------------------------===//
580
581OpFoldResult spirv::UDivOp::fold(FoldAdaptor adaptor) {
582 // udiv (x, 1) = x
583 if (matchPattern(getOperand2(), m_One()))
584 return getOperand1();
585
586 // According to the SPIR-V spec:
587 //
588 // Unsigned-integer division of Operand 1 divided by Operand 2. Behavior is
589 // undefined if Operand 2 is 0.
590 //
591 // So don't fold during undefined behavior.
592 bool div0 = false;
594 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
595 if (div0 || b.isZero()) {
596 div0 = true;
597 return a;
598 }
599 return a.udiv(b);
600 });
601 return div0 ? Attribute() : res;
602}
603
604//===----------------------------------------------------------------------===//
605// spirv.UMod
606//===----------------------------------------------------------------------===//
607
608OpFoldResult spirv::UModOp::fold(FoldAdaptor adaptor) {
609 // umod (x, 1) = 0
610 if (matchPattern(getOperand2(), m_One()))
612
613 // According to the SPIR-V spec:
614 //
615 // Unsigned modulo operation of Operand 1 modulo Operand 2. Behavior is
616 // undefined if Operand 2 is 0.
617 //
618 // So don't fold during undefined behavior.
619 bool div0 = false;
621 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
622 if (div0 || b.isZero()) {
623 div0 = true;
624 return a;
625 }
626 return a.urem(b);
627 });
628 return div0 ? Attribute() : res;
629}
630
631//===----------------------------------------------------------------------===//
632// spirv.SNegate
633//===----------------------------------------------------------------------===//
634
635OpFoldResult spirv::SNegateOp::fold(FoldAdaptor adaptor) {
636 // -(-x) = 0 - (0 - x) = x
637 auto op = getOperand();
638 if (auto negateOp = op.getDefiningOp<spirv::SNegateOp>())
639 return negateOp->getOperand(0);
640
641 // According to the SPIR-V spec:
642 //
643 // Signed-integer subtract of Operand from zero.
645 adaptor.getOperands(), [](const APInt &a) {
646 APInt zero = APInt::getZero(a.getBitWidth());
647 return std::move(zero) - a;
648 });
649}
650
651//===----------------------------------------------------------------------===//
652// spirv.NotOp
653//===----------------------------------------------------------------------===//
654
655OpFoldResult spirv::NotOp::fold(spirv::NotOp::FoldAdaptor adaptor) {
656 // !(!x) = x
657 auto op = getOperand();
658 if (auto notOp = op.getDefiningOp<spirv::NotOp>())
659 return notOp->getOperand(0);
660
661 // According to the SPIR-V spec:
662 //
663 // Complement the bits of Operand.
664 return constFoldUnaryOp<IntegerAttr>(adaptor.getOperands(), [&](APInt a) {
665 a.flipAllBits();
666 return a;
667 });
668}
669
670//===----------------------------------------------------------------------===//
671// spirv.LogicalAnd
672//===----------------------------------------------------------------------===//
673
674OpFoldResult spirv::LogicalAndOp::fold(FoldAdaptor adaptor) {
675 if (std::optional<bool> rhs =
676 getScalarOrSplatBoolAttr(adaptor.getOperand2())) {
677 // x && true = x
678 if (*rhs)
679 return getOperand1();
680
681 // x && false = false
682 if (!*rhs)
683 return adaptor.getOperand2();
684 }
685
686 return Attribute();
687}
688
689//===----------------------------------------------------------------------===//
690// spirv.LogicalEqualOp
691//===----------------------------------------------------------------------===//
692
694spirv::LogicalEqualOp::fold(spirv::LogicalEqualOp::FoldAdaptor adaptor) {
695 // x == x -> true
696 if (getOperand1() == getOperand2()) {
697 auto trueAttr = BoolAttr::get(getContext(), true);
698 if (isa<IntegerType>(getType()))
699 return trueAttr;
700 if (auto vecTy = dyn_cast<VectorType>(getType()))
701 return SplatElementsAttr::get(vecTy, trueAttr);
702 }
703
705 adaptor.getOperands(), [](const APInt &a, const APInt &b) {
706 return a == b ? APInt::getAllOnes(1) : APInt::getZero(1);
707 });
708}
709
710//===----------------------------------------------------------------------===//
711// spirv.LogicalNotEqualOp
712//===----------------------------------------------------------------------===//
713
714OpFoldResult spirv::LogicalNotEqualOp::fold(FoldAdaptor adaptor) {
715 if (std::optional<bool> rhs =
716 getScalarOrSplatBoolAttr(adaptor.getOperand2())) {
717 // x != false -> x
718 if (!rhs.value())
719 return getOperand1();
720 }
721
722 // x == x -> false
723 if (getOperand1() == getOperand2()) {
724 auto falseAttr = BoolAttr::get(getContext(), false);
725 if (isa<IntegerType>(getType()))
726 return falseAttr;
727 if (auto vecTy = dyn_cast<VectorType>(getType()))
728 return SplatElementsAttr::get(vecTy, falseAttr);
729 }
730
732 adaptor.getOperands(), [](const APInt &a, const APInt &b) {
733 return a == b ? APInt::getZero(1) : APInt::getAllOnes(1);
734 });
735}
736
737//===----------------------------------------------------------------------===//
738// spirv.LogicalNot
739//===----------------------------------------------------------------------===//
740
741OpFoldResult spirv::LogicalNotOp::fold(FoldAdaptor adaptor) {
742 // !(!x) = x
743 auto op = getOperand();
744 if (auto notOp = op.getDefiningOp<spirv::LogicalNotOp>())
745 return notOp->getOperand(0);
746
747 // According to the SPIR-V spec:
748 //
749 // Complement the bits of Operand.
751 adaptor.getOperands(), [](const APInt &a) {
752 return a == 1 ? APInt::getZero(1) : APInt::getAllOnes(1);
753 });
754}
755
756void spirv::LogicalNotOp::getCanonicalizationPatterns(
757 RewritePatternSet &results, MLIRContext *context) {
758 results
759 .add<ConvertLogicalNotOfIEqual, ConvertLogicalNotOfINotEqual,
760 ConvertLogicalNotOfLogicalEqual, ConvertLogicalNotOfLogicalNotEqual>(
761 context);
762}
763
764//===----------------------------------------------------------------------===//
765// spirv.LogicalOr
766//===----------------------------------------------------------------------===//
767
768OpFoldResult spirv::LogicalOrOp::fold(FoldAdaptor adaptor) {
769 if (auto rhs = getScalarOrSplatBoolAttr(adaptor.getOperand2())) {
770 if (*rhs) {
771 // x || true = true
772 return adaptor.getOperand2();
773 }
774
775 if (!*rhs) {
776 // x || false = x
777 return getOperand1();
778 }
779 }
780
781 return Attribute();
782}
783
784//===----------------------------------------------------------------------===//
785// spirv.SelectOp
786//===----------------------------------------------------------------------===//
787
788OpFoldResult spirv::SelectOp::fold(FoldAdaptor adaptor) {
789 // spirv.Select _ x x -> x
790 Value trueVals = getTrueValue();
791 Value falseVals = getFalseValue();
792 if (trueVals == falseVals)
793 return trueVals;
794
795 ArrayRef<Attribute> operands = adaptor.getOperands();
796
797 // spirv.Select true x y -> x
798 // spirv.Select false x y -> y
799 if (auto boolAttr = getScalarOrSplatBoolAttr(operands[0]))
800 return *boolAttr ? trueVals : falseVals;
801
802 // Check that all the operands are constant
803 if (!operands[0] || !operands[1] || !operands[2])
804 return Attribute();
805
806 // Note: getScalarOrSplatBoolAttr will always return a boolAttr if we are in
807 // the scalar case. Hence, we are only required to consider the case of
808 // DenseElementsAttr in foldSelectOp.
809 auto condAttrs = dyn_cast<DenseElementsAttr>(operands[0]);
810 auto trueAttrs = dyn_cast<DenseElementsAttr>(operands[1]);
811 auto falseAttrs = dyn_cast<DenseElementsAttr>(operands[2]);
812 if (!condAttrs || !trueAttrs || !falseAttrs)
813 return Attribute();
814
815 auto elementResults = llvm::to_vector<4>(trueAttrs.getValues<Attribute>());
816 auto iters = llvm::zip_equal(elementResults, condAttrs.getValues<BoolAttr>(),
817 falseAttrs.getValues<Attribute>());
818 for (auto [result, cond, falseRes] : iters) {
819 if (!cond.getValue())
820 result = falseRes;
821 }
822
823 auto resultType = trueAttrs.getType();
824 return DenseElementsAttr::get(cast<ShapedType>(resultType), elementResults);
825}
826
827//===----------------------------------------------------------------------===//
828// spirv.IEqualOp
829//===----------------------------------------------------------------------===//
830
831OpFoldResult spirv::IEqualOp::fold(spirv::IEqualOp::FoldAdaptor adaptor) {
832 // x == x -> true
833 if (getOperand1() == getOperand2()) {
834 auto trueAttr = BoolAttr::get(getContext(), true);
835 if (isa<IntegerType>(getType()))
836 return trueAttr;
837 if (auto vecTy = dyn_cast<VectorType>(getType()))
838 return SplatElementsAttr::get(vecTy, trueAttr);
839 }
840
842 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
843 return a == b ? APInt::getAllOnes(1) : APInt::getZero(1);
844 });
845}
846
847//===----------------------------------------------------------------------===//
848// spirv.INotEqualOp
849//===----------------------------------------------------------------------===//
850
851OpFoldResult spirv::INotEqualOp::fold(spirv::INotEqualOp::FoldAdaptor adaptor) {
852 // x == x -> false
853 if (getOperand1() == getOperand2()) {
854 auto falseAttr = BoolAttr::get(getContext(), false);
855 if (isa<IntegerType>(getType()))
856 return falseAttr;
857 if (auto vecTy = dyn_cast<VectorType>(getType()))
858 return SplatElementsAttr::get(vecTy, falseAttr);
859 }
860
862 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
863 return a == b ? APInt::getZero(1) : APInt::getAllOnes(1);
864 });
865}
866
867//===----------------------------------------------------------------------===//
868// spirv.SGreaterThan
869//===----------------------------------------------------------------------===//
870
872spirv::SGreaterThanOp::fold(spirv::SGreaterThanOp::FoldAdaptor adaptor) {
873 // x == x -> false
874 if (getOperand1() == getOperand2()) {
875 auto falseAttr = BoolAttr::get(getContext(), false);
876 if (isa<IntegerType>(getType()))
877 return falseAttr;
878 if (auto vecTy = dyn_cast<VectorType>(getType()))
879 return SplatElementsAttr::get(vecTy, falseAttr);
880 }
881
883 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
884 return a.sgt(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
885 });
886}
887
888//===----------------------------------------------------------------------===//
889// spirv.SGreaterThanEqual
890//===----------------------------------------------------------------------===//
891
892OpFoldResult spirv::SGreaterThanEqualOp::fold(
893 spirv::SGreaterThanEqualOp::FoldAdaptor adaptor) {
894 // x == x -> true
895 if (getOperand1() == getOperand2()) {
896 auto trueAttr = BoolAttr::get(getContext(), true);
897 if (isa<IntegerType>(getType()))
898 return trueAttr;
899 if (auto vecTy = dyn_cast<VectorType>(getType()))
900 return SplatElementsAttr::get(vecTy, trueAttr);
901 }
902
904 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
905 return a.sge(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
906 });
907}
908
909//===----------------------------------------------------------------------===//
910// spirv.UGreaterThan
911//===----------------------------------------------------------------------===//
912
914spirv::UGreaterThanOp::fold(spirv::UGreaterThanOp::FoldAdaptor adaptor) {
915 // x == x -> false
916 if (getOperand1() == getOperand2()) {
917 auto falseAttr = BoolAttr::get(getContext(), false);
918 if (isa<IntegerType>(getType()))
919 return falseAttr;
920 if (auto vecTy = dyn_cast<VectorType>(getType()))
921 return SplatElementsAttr::get(vecTy, falseAttr);
922 }
923
925 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
926 return a.ugt(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
927 });
928}
929
930//===----------------------------------------------------------------------===//
931// spirv.UGreaterThanEqual
932//===----------------------------------------------------------------------===//
933
934OpFoldResult spirv::UGreaterThanEqualOp::fold(
935 spirv::UGreaterThanEqualOp::FoldAdaptor adaptor) {
936 // x == x -> true
937 if (getOperand1() == getOperand2()) {
938 auto trueAttr = BoolAttr::get(getContext(), true);
939 if (isa<IntegerType>(getType()))
940 return trueAttr;
941 if (auto vecTy = dyn_cast<VectorType>(getType()))
942 return SplatElementsAttr::get(vecTy, trueAttr);
943 }
944
946 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
947 return a.uge(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
948 });
949}
950
951//===----------------------------------------------------------------------===//
952// spirv.SLessThan
953//===----------------------------------------------------------------------===//
954
955OpFoldResult spirv::SLessThanOp::fold(spirv::SLessThanOp::FoldAdaptor adaptor) {
956 // x == x -> false
957 if (getOperand1() == getOperand2()) {
958 auto falseAttr = BoolAttr::get(getContext(), false);
959 if (isa<IntegerType>(getType()))
960 return falseAttr;
961 if (auto vecTy = dyn_cast<VectorType>(getType()))
962 return SplatElementsAttr::get(vecTy, falseAttr);
963 }
964
966 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
967 return a.slt(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
968 });
969}
970
971//===----------------------------------------------------------------------===//
972// spirv.SLessThanEqual
973//===----------------------------------------------------------------------===//
974
976spirv::SLessThanEqualOp::fold(spirv::SLessThanEqualOp::FoldAdaptor adaptor) {
977 // x == x -> true
978 if (getOperand1() == getOperand2()) {
979 auto trueAttr = BoolAttr::get(getContext(), true);
980 if (isa<IntegerType>(getType()))
981 return trueAttr;
982 if (auto vecTy = dyn_cast<VectorType>(getType()))
983 return SplatElementsAttr::get(vecTy, trueAttr);
984 }
985
987 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
988 return a.sle(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
989 });
990}
991
992//===----------------------------------------------------------------------===//
993// spirv.ULessThan
994//===----------------------------------------------------------------------===//
995
996OpFoldResult spirv::ULessThanOp::fold(spirv::ULessThanOp::FoldAdaptor adaptor) {
997 // x == x -> false
998 if (getOperand1() == getOperand2()) {
999 auto falseAttr = BoolAttr::get(getContext(), false);
1000 if (isa<IntegerType>(getType()))
1001 return falseAttr;
1002 if (auto vecTy = dyn_cast<VectorType>(getType()))
1003 return SplatElementsAttr::get(vecTy, falseAttr);
1004 }
1005
1007 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
1008 return a.ult(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
1009 });
1010}
1011
1012//===----------------------------------------------------------------------===//
1013// spirv.ULessThanEqual
1014//===----------------------------------------------------------------------===//
1015
1017spirv::ULessThanEqualOp::fold(spirv::ULessThanEqualOp::FoldAdaptor adaptor) {
1018 // x == x -> true
1019 if (getOperand1() == getOperand2()) {
1020 auto trueAttr = BoolAttr::get(getContext(), true);
1021 if (isa<IntegerType>(getType()))
1022 return trueAttr;
1023 if (auto vecTy = dyn_cast<VectorType>(getType()))
1024 return SplatElementsAttr::get(vecTy, trueAttr);
1025 }
1026
1028 adaptor.getOperands(), getType(), [](const APInt &a, const APInt &b) {
1029 return a.ule(b) ? APInt::getAllOnes(1) : APInt::getZero(1);
1030 });
1031}
1032
1033//===----------------------------------------------------------------------===//
1034// spirv.ShiftLeftLogical
1035//===----------------------------------------------------------------------===//
1036
1037OpFoldResult spirv::ShiftLeftLogicalOp::fold(
1038 spirv::ShiftLeftLogicalOp::FoldAdaptor adaptor) {
1039 // x << 0 -> x
1040 if (matchPattern(adaptor.getOperand2(), m_Zero())) {
1041 return getOperand1();
1042 }
1043
1044 // Unfortunately due to below undefined behaviour can't fold 0 for Base.
1045
1046 // Results are computed per component, and within each component, per bit...
1047 //
1048 // The result is undefined if Shift is greater than or equal to the bit width
1049 // of the components of Base.
1050 //
1051 // So we can use the APInt << method, but don't fold if undefined behaviour.
1052 bool shiftToLarge = false;
1054 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
1055 if (shiftToLarge || b.uge(a.getBitWidth())) {
1056 shiftToLarge = true;
1057 return a;
1058 }
1059 return a << b;
1060 });
1061 return shiftToLarge ? Attribute() : res;
1062}
1063
1064//===----------------------------------------------------------------------===//
1065// spirv.ShiftRightArithmetic
1066//===----------------------------------------------------------------------===//
1067
1068OpFoldResult spirv::ShiftRightArithmeticOp::fold(
1069 spirv::ShiftRightArithmeticOp::FoldAdaptor adaptor) {
1070 // x >> 0 -> x
1071 if (matchPattern(adaptor.getOperand2(), m_Zero())) {
1072 return getOperand1();
1073 }
1074
1075 // Unfortunately due to below undefined behaviour can't fold 0, -1 for Base.
1076
1077 // Results are computed per component, and within each component, per bit...
1078 //
1079 // The result is undefined if Shift is greater than or equal to the bit width
1080 // of the components of Base.
1081 //
1082 // So we can use the APInt ashr method, but don't fold if undefined behaviour.
1083 bool shiftToLarge = false;
1085 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
1086 if (shiftToLarge || b.uge(a.getBitWidth())) {
1087 shiftToLarge = true;
1088 return a;
1089 }
1090 return a.ashr(b);
1091 });
1092 return shiftToLarge ? Attribute() : res;
1093}
1094
1095//===----------------------------------------------------------------------===//
1096// spirv.ShiftRightLogical
1097//===----------------------------------------------------------------------===//
1098
1099OpFoldResult spirv::ShiftRightLogicalOp::fold(
1100 spirv::ShiftRightLogicalOp::FoldAdaptor adaptor) {
1101 // x >> 0 -> x
1102 if (matchPattern(adaptor.getOperand2(), m_Zero())) {
1103 return getOperand1();
1104 }
1105
1106 // Unfortunately due to below undefined behaviour can't fold 0 for Base.
1107
1108 // Results are computed per component, and within each component, per bit...
1109 //
1110 // The result is undefined if Shift is greater than or equal to the bit width
1111 // of the components of Base.
1112 //
1113 // So we can use the APInt lshr method, but don't fold if undefined behaviour.
1114 bool shiftToLarge = false;
1116 adaptor.getOperands(), [&](const APInt &a, const APInt &b) {
1117 if (shiftToLarge || b.uge(a.getBitWidth())) {
1118 shiftToLarge = true;
1119 return a;
1120 }
1121 return a.lshr(b);
1122 });
1123 return shiftToLarge ? Attribute() : res;
1124}
1125
1126//===----------------------------------------------------------------------===//
1127// spirv.BitwiseAndOp
1128//===----------------------------------------------------------------------===//
1129
1131spirv::BitwiseAndOp::fold(spirv::BitwiseAndOp::FoldAdaptor adaptor) {
1132 // x & x -> x
1133 if (getOperand1() == getOperand2()) {
1134 return getOperand1();
1135 }
1136
1137 APInt rhsMask;
1138 if (matchPattern(adaptor.getOperand2(), m_ConstantInt(&rhsMask))) {
1139 // x & 0 -> 0
1140 if (rhsMask.isZero())
1141 return getOperand2();
1142
1143 // x & <all ones> -> x
1144 if (rhsMask.isAllOnes())
1145 return getOperand1();
1146
1147 // (UConvert x : iN to iK) & <mask with N low bits set> -> UConvert x
1148 if (auto zext = getOperand1().getDefiningOp<spirv::UConvertOp>()) {
1149 int valueBits =
1151 if (rhsMask.zextOrTrunc(valueBits).isAllOnes())
1152 return getOperand1();
1153 }
1154 }
1155
1156 // According to the SPIR-V spec:
1157 //
1158 // Type is a scalar or vector of integer type.
1159 // Results are computed per component, and within each component, per bit.
1160 // So we can use the APInt & method.
1162 adaptor.getOperands(),
1163 [](const APInt &a, const APInt &b) { return a & b; });
1164}
1165
1166//===----------------------------------------------------------------------===//
1167// spirv.BitwiseOrOp
1168//===----------------------------------------------------------------------===//
1169
1170OpFoldResult spirv::BitwiseOrOp::fold(spirv::BitwiseOrOp::FoldAdaptor adaptor) {
1171 // x | x -> x
1172 if (getOperand1() == getOperand2()) {
1173 return getOperand1();
1174 }
1175
1176 APInt rhsMask;
1177 if (matchPattern(adaptor.getOperand2(), m_ConstantInt(&rhsMask))) {
1178 // x | 0 -> x
1179 if (rhsMask.isZero())
1180 return getOperand1();
1181
1182 // x | <all ones> -> <all ones>
1183 if (rhsMask.isAllOnes())
1184 return getOperand2();
1185 }
1186
1187 // According to the SPIR-V spec:
1188 //
1189 // Type is a scalar or vector of integer type.
1190 // Results are computed per component, and within each component, per bit.
1191 // So we can use the APInt | method.
1193 adaptor.getOperands(),
1194 [](const APInt &a, const APInt &b) { return a | b; });
1195}
1196
1197//===----------------------------------------------------------------------===//
1198// spirv.BitwiseXorOp
1199//===----------------------------------------------------------------------===//
1200
1202spirv::BitwiseXorOp::fold(spirv::BitwiseXorOp::FoldAdaptor adaptor) {
1203 // x ^ 0 -> x
1204 if (matchPattern(adaptor.getOperand2(), m_Zero())) {
1205 return getOperand1();
1206 }
1207
1208 // x ^ x -> 0
1209 if (getOperand1() == getOperand2())
1211
1212 // According to the SPIR-V spec:
1213 //
1214 // Type is a scalar or vector of integer type.
1215 // Results are computed per component, and within each component, per bit.
1216 // So we can use the APInt ^ method.
1218 adaptor.getOperands(),
1219 [](const APInt &a, const APInt &b) { return a ^ b; });
1220}
1221
1222//===----------------------------------------------------------------------===//
1223// spirv.mlir.selection
1224//===----------------------------------------------------------------------===//
1225
1226namespace {
1227// Blocks from the given `spirv.mlir.selection` operation must satisfy the
1228// following layout:
1229//
1230// +-----------------------------------------------+
1231// | header block |
1232// | spirv.BranchConditionalOp %cond, ^case0, ^case1 |
1233// +-----------------------------------------------+
1234// / \
1235// ...
1236//
1237//
1238// +------------------------+ +------------------------+
1239// | case #0 | | case #1 |
1240// | spirv.Store %ptr %value0 | | spirv.Store %ptr %value1 |
1241// | spirv.Branch ^merge | | spirv.Branch ^merge |
1242// +------------------------+ +------------------------+
1243//
1244//
1245// ...
1246// \ /
1247// v
1248// +-------------+
1249// | merge block |
1250// +-------------+
1251//
1252struct ConvertSelectionOpToSelect final : OpRewritePattern<spirv::SelectionOp> {
1253 using Base::Base;
1254
1255 LogicalResult matchAndRewrite(spirv::SelectionOp selectionOp,
1256 PatternRewriter &rewriter) const override {
1257 Operation *op = selectionOp.getOperation();
1258 Region &body = op->getRegion(0);
1259 // Verifier allows an empty region for `spirv.mlir.selection`.
1260 if (body.empty()) {
1261 return failure();
1262 }
1263
1264 // Check that region consists of 4 blocks:
1265 // header block, `true` block, `false` block and merge block.
1266 if (llvm::range_size(body) != 4) {
1267 return failure();
1268 }
1269
1270 Block *headerBlock = selectionOp.getHeaderBlock();
1271 if (!onlyContainsBranchConditionalOp(headerBlock)) {
1272 return failure();
1273 }
1274
1275 auto brConditionalOp =
1276 cast<spirv::BranchConditionalOp>(headerBlock->front());
1277
1278 Block *trueBlock = brConditionalOp.getSuccessor(0);
1279 Block *falseBlock = brConditionalOp.getSuccessor(1);
1280 Block *mergeBlock = selectionOp.getMergeBlock();
1281
1282 if (failed(canCanonicalizeSelection(trueBlock, falseBlock, mergeBlock)))
1283 return failure();
1284
1285 Value trueValue = getSrcValue(trueBlock);
1286 Value falseValue = getSrcValue(falseBlock);
1287 Value ptrValue = getDstPtr(trueBlock);
1288 auto storeOp = cast<spirv::StoreOp>(trueBlock->front());
1289
1290 auto selectOp = spirv::SelectOp::create(
1291 rewriter, selectionOp.getLoc(), trueValue.getType(),
1292 brConditionalOp.getCondition(), trueValue, falseValue);
1293 auto newStore = spirv::StoreOp::create(
1294 rewriter, selectOp.getLoc(), ptrValue, selectOp.getResult(),
1295 storeOp.getMemoryAccessAttr(), storeOp.getAlignmentAttr());
1296 newStore->setDiscardableAttrs(storeOp->getDiscardableAttrDictionary());
1297
1298 // `spirv.mlir.selection` is not needed anymore.
1299 rewriter.eraseOp(op);
1300 return success();
1301 }
1302
1303private:
1304 // Checks that given blocks follow the following rules:
1305 // 1. Each conditional block consists of two operations, the first operation
1306 // is a `spirv.Store` and the last operation is a `spirv.Branch`.
1307 // 2. Each `spirv.Store` uses the same pointer and the same memory attributes.
1308 // 3. A control flow goes into the given merge block from the given
1309 // conditional blocks.
1310 LogicalResult canCanonicalizeSelection(Block *trueBlock, Block *falseBlock,
1311 Block *mergeBlock) const;
1312
1313 bool onlyContainsBranchConditionalOp(Block *block) const {
1314 return llvm::hasSingleElement(*block) &&
1315 isa<spirv::BranchConditionalOp>(block->front());
1316 }
1317
1318 bool isSameAttrList(spirv::StoreOp lhs, spirv::StoreOp rhs) const {
1319 return lhs->getDiscardableAttrDictionary() ==
1320 rhs->getDiscardableAttrDictionary() &&
1321 lhs.getProperties() == rhs.getProperties();
1322 }
1323
1324 // Returns a source value for the given block.
1325 Value getSrcValue(Block *block) const {
1326 auto storeOp = cast<spirv::StoreOp>(block->front());
1327 return storeOp.getValue();
1328 }
1329
1330 // Returns a destination value for the given block.
1331 Value getDstPtr(Block *block) const {
1332 auto storeOp = cast<spirv::StoreOp>(block->front());
1333 return storeOp.getPtr();
1334 }
1335};
1336
1337LogicalResult ConvertSelectionOpToSelect::canCanonicalizeSelection(
1338 Block *trueBlock, Block *falseBlock, Block *mergeBlock) const {
1339 // Each block must consists of 2 operations.
1340 if (llvm::range_size(*trueBlock) != 2 || llvm::range_size(*falseBlock) != 2) {
1341 return failure();
1342 }
1343
1344 auto trueBrStoreOp = dyn_cast<spirv::StoreOp>(trueBlock->front());
1345 auto trueBrBranchOp =
1346 dyn_cast<spirv::BranchOp>(*std::next(trueBlock->begin()));
1347 auto falseBrStoreOp = dyn_cast<spirv::StoreOp>(falseBlock->front());
1348 auto falseBrBranchOp =
1349 dyn_cast<spirv::BranchOp>(*std::next(falseBlock->begin()));
1350
1351 if (!trueBrStoreOp || !trueBrBranchOp || !falseBrStoreOp ||
1352 !falseBrBranchOp) {
1353 return failure();
1354 }
1355
1356 // Checks that given type is valid for `spirv.SelectOp`.
1357 // According to SPIR-V spec:
1358 // "Before version 1.4, Result Type must be a pointer, scalar, or vector.
1359 // Starting with version 1.4, Result Type can additionally be a composite type
1360 // other than a vector."
1361 bool isScalarOrVector =
1362 cast<spirv::SPIRVType>(trueBrStoreOp.getValue().getType())
1363 .isScalarOrVector();
1364
1365 // Check that each `spirv.Store` uses the same pointer, memory access
1366 // attributes and a valid type of the value.
1367 if ((trueBrStoreOp.getPtr() != falseBrStoreOp.getPtr()) ||
1368 !isSameAttrList(trueBrStoreOp, falseBrStoreOp) || !isScalarOrVector) {
1369 return failure();
1370 }
1371
1372 if ((trueBrBranchOp->getSuccessor(0) != mergeBlock) ||
1373 (falseBrBranchOp->getSuccessor(0) != mergeBlock)) {
1374 return failure();
1375 }
1376
1377 return success();
1378}
1379} // namespace
1380
1381void spirv::SelectionOp::getCanonicalizationPatterns(RewritePatternSet &results,
1382 MLIRContext *context) {
1383 results.add<ConvertSelectionOpToSelect>(context);
1384}
return success()
if(failed(verifyVectorMemoryOp(getOperation(), memrefType, getVectorType()))) return failure()
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...
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:738
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:732
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:310
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={})