[llvm-branch-commits] [clang] [CIR] Add cir.min op and refactor cir.max lowering (PR #185276)
Henrich Lauko via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sun Mar 8 07:03:28 PDT 2026
https://github.com/xlauko updated https://github.com/llvm/llvm-project/pull/185276
>From 70a34519a3df7ea7ce9db0911a04e44cf5d24b46 Mon Sep 17 00:00:00 2001
From: xlauko <xlauko at mail.muni.cz>
Date: Sun, 8 Mar 2026 13:40:09 +0100
Subject: [PATCH] [CIR] Add cir.min op and refactor cir.max lowering
Add cir.min operation for integer minimum computation. Refactor cir.max
lowering into a shared lowerMinMaxOp template reused by both ops. Includes
lowering tests for signed, unsigned, and vector types, plus canonicalization
tests.
---
clang/include/clang/CIR/Dialect/IR/CIROps.td | 27 ++++++-
.../Dialect/Transforms/CIRCanonicalize.cpp | 10 +--
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 25 ++++--
clang/test/CIR/Lowering/binop-int-vector.cir | 24 ++++++
clang/test/CIR/Lowering/binop-signed-int.cir | 2 +
.../test/CIR/Lowering/binop-unsigned-int.cir | 3 +
.../CIR/Transforms/max-min-idempotent.cir | 77 +++++++++++++++++++
7 files changed, 157 insertions(+), 11 deletions(-)
create mode 100644 clang/test/CIR/Lowering/binop-int-vector.cir
create mode 100644 clang/test/CIR/Transforms/max-min-idempotent.cir
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 8860d6df27a74..b9ff04f4f8101 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2390,13 +2390,38 @@ def CIR_MaxOp : CIR_BinaryOp<"max", CIR_AnyIntOrVecOfIntType, [
let summary = "Integer maximum";
let description = [{
The `cir.max` operation computes the maximum of two integer operands.
- Both operands and the result must have the same integer type.
+ Both operands and the result must have the same integer type or vector of
+ integer type.
Example:
```mlir
%0 = cir.max %a, %b : !s32i
%1 = cir.max %a, %b : !u32i
+ %2 = cir.max %a, %b : !cir.vector<4 x !s32i>
+ ```
+ }];
+}
+
+//===----------------------------------------------------------------------===//
+// MinOp
+//===----------------------------------------------------------------------===//
+
+def CIR_MinOp : CIR_BinaryOp<"min", CIR_AnyIntOrVecOfIntType, [
+ Commutative, Idempotent
+]> {
+ let summary = "Integer minimum";
+ let description = [{
+ The `cir.min` operation computes the minimum of two integer operands.
+ Both operands and the result must have the same integer type or vector of
+ integer type.
+
+ Example:
+
+ ```mlir
+ %0 = cir.min %a, %b : !s32i
+ %1 = cir.min %a, %b : !u32i
+ %2 = cir.min %a, %b : !cir.vector<4 x !s32i>
```
}];
}
diff --git a/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp b/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp
index 46a8c011b320b..c29e7585aa56d 100644
--- a/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp
@@ -71,11 +71,11 @@ void CIRCanonicalizePass::runOnOperation() {
// Many operations are here to perform a manual `fold` in
// applyOpPatternsGreedily.
if (isa<BrOp, BrCondOp, CastOp, ScopeOp, SwitchOp, SelectOp, UnaryOp, AddOp,
- MulOp, AndOp, OrOp, XorOp, MaxOp, ComplexCreateOp, ComplexImagOp,
- ComplexRealOp, VecCmpOp, VecCreateOp, VecExtractOp, VecShuffleOp,
- VecShuffleDynamicOp, VecTernaryOp, BitClrsbOp, BitClzOp, BitCtzOp,
- BitFfsOp, BitParityOp, BitPopcountOp, BitReverseOp, ByteSwapOp,
- RotateOp, ConstantOp>(op))
+ MulOp, AndOp, OrOp, XorOp, MaxOp, MinOp, ComplexCreateOp,
+ ComplexImagOp, ComplexRealOp, VecCmpOp, VecCreateOp, VecExtractOp,
+ VecShuffleOp, VecShuffleDynamicOp, VecTernaryOp, BitClrsbOp,
+ BitClzOp, BitCtzOp, BitFfsOp, BitParityOp, BitPopcountOp,
+ BitReverseOp, ByteSwapOp, RotateOp, ConstantOp>(op))
ops.push_back(op);
});
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 8bcd040382ab3..d8816b48da2de 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -2982,18 +2982,33 @@ mlir::LogicalResult CIRToLLVMXorOpLowering::matchAndRewrite(
return mlir::success();
}
-mlir::LogicalResult CIRToLLVMMaxOpLowering::matchAndRewrite(
- cir::MaxOp op, OpAdaptor adaptor,
- mlir::ConversionPatternRewriter &rewriter) const {
+template <typename CIROp, typename UIntOp, typename SIntOp>
+static mlir::LogicalResult
+lowerMinMaxOp(CIROp op, typename CIROp::Adaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) {
const mlir::Value lhs = adaptor.getLhs();
const mlir::Value rhs = adaptor.getRhs();
if (isIntTypeUnsigned(elementTypeIfVector(op.getRhs().getType())))
- rewriter.replaceOpWithNewOp<mlir::LLVM::UMaxOp>(op, lhs, rhs);
+ rewriter.replaceOpWithNewOp<UIntOp>(op, lhs, rhs);
else
- rewriter.replaceOpWithNewOp<mlir::LLVM::SMaxOp>(op, lhs, rhs);
+ rewriter.replaceOpWithNewOp<SIntOp>(op, lhs, rhs);
return mlir::success();
}
+mlir::LogicalResult CIRToLLVMMaxOpLowering::matchAndRewrite(
+ cir::MaxOp op, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const {
+ return lowerMinMaxOp<cir::MaxOp, mlir::LLVM::UMaxOp, mlir::LLVM::SMaxOp>(
+ op, adaptor, rewriter);
+}
+
+mlir::LogicalResult CIRToLLVMMinOpLowering::matchAndRewrite(
+ cir::MinOp op, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const {
+ return lowerMinMaxOp<cir::MinOp, mlir::LLVM::UMinOp, mlir::LLVM::SMinOp>(
+ op, adaptor, rewriter);
+}
+
/// Convert from a CIR comparison kind to an LLVM IR integral comparison kind.
static mlir::LLVM::ICmpPredicate
convertCmpKindToICmpPredicate(cir::CmpOpKind kind, bool isSigned) {
diff --git a/clang/test/CIR/Lowering/binop-int-vector.cir b/clang/test/CIR/Lowering/binop-int-vector.cir
new file mode 100644
index 0000000000000..d8814669b1fb2
--- /dev/null
+++ b/clang/test/CIR/Lowering/binop-int-vector.cir
@@ -0,0 +1,24 @@
+// RUN: cir-opt %s -cir-to-llvm -o - | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+!u32i = !cir.int<u, 32>
+
+module {
+ cir.func @signed_vec(%arg0 : !cir.vector<4 x !s32i>,
+ %arg1 : !cir.vector<4 x !s32i>) {
+ %0 = cir.max %arg0, %arg1 : !cir.vector<4 x !s32i>
+ // CHECK: = llvm.intr.smax
+ %1 = cir.min %arg0, %arg1 : !cir.vector<4 x !s32i>
+ // CHECK: = llvm.intr.smin
+ cir.return
+ }
+
+ cir.func @unsigned_vec(%arg0 : !cir.vector<4 x !u32i>,
+ %arg1 : !cir.vector<4 x !u32i>) {
+ %0 = cir.max %arg0, %arg1 : !cir.vector<4 x !u32i>
+ // CHECK: = llvm.intr.umax
+ %1 = cir.min %arg0, %arg1 : !cir.vector<4 x !u32i>
+ // CHECK: = llvm.intr.umin
+ cir.return
+ }
+}
diff --git a/clang/test/CIR/Lowering/binop-signed-int.cir b/clang/test/CIR/Lowering/binop-signed-int.cir
index 091becbd78c2c..15bee750e2d7c 100644
--- a/clang/test/CIR/Lowering/binop-signed-int.cir
+++ b/clang/test/CIR/Lowering/binop-signed-int.cir
@@ -55,6 +55,8 @@ module {
cir.store %34, %2 : !s32i, !cir.ptr<!s32i>
%37 = cir.max %32, %33 : !s32i
// CHECK: = llvm.intr.smax
+ %38 = cir.min %32, %33 : !s32i
+ // CHECK: = llvm.intr.smin
cir.return
}
}
diff --git a/clang/test/CIR/Lowering/binop-unsigned-int.cir b/clang/test/CIR/Lowering/binop-unsigned-int.cir
index 79d8f6bdcecd3..d5545cb7769d4 100644
--- a/clang/test/CIR/Lowering/binop-unsigned-int.cir
+++ b/clang/test/CIR/Lowering/binop-unsigned-int.cir
@@ -44,6 +44,7 @@ module {
%35 = cir.add sat %32, %33: !u32i
%36 = cir.sub sat %32, %33: !u32i
%37 = cir.max %32, %33 : !u32i
+ %38 = cir.min %32, %33 : !u32i
cir.return
}
}
@@ -59,6 +60,7 @@ module {
// MLIR: = llvm.intr.uadd.sat{{.*}}(i32, i32) -> i32
// MLIR: = llvm.intr.usub.sat{{.*}}(i32, i32) -> i32
// MLIR: = llvm.intr.umax
+// MLIR: = llvm.intr.umin
// LLVM: = mul i32
// LLVM: = udiv i32
@@ -71,3 +73,4 @@ module {
// LLVM: = call i32 @llvm.uadd.sat.i32
// LLVM: = call i32 @llvm.usub.sat.i32
// LLVM: = call i32 @llvm.umax.i32
+// LLVM: = call i32 @llvm.umin.i32
diff --git a/clang/test/CIR/Transforms/max-min-idempotent.cir b/clang/test/CIR/Transforms/max-min-idempotent.cir
new file mode 100644
index 0000000000000..1ce4ced3f745d
--- /dev/null
+++ b/clang/test/CIR/Transforms/max-min-idempotent.cir
@@ -0,0 +1,77 @@
+// RUN: cir-opt %s -cir-canonicalize -o - | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+!u32i = !cir.int<u, 32>
+
+// Idempotent: max(x, x) -> x
+// CHECK-LABEL: cir.func @max_idempotent
+// CHECK-NEXT: cir.return %arg0
+cir.func @max_idempotent(%arg0 : !s32i) -> !s32i {
+ %0 = cir.max %arg0, %arg0 : !s32i
+ cir.return %0 : !s32i
+}
+
+// Idempotent: min(x, x) -> x
+// CHECK-LABEL: cir.func @min_idempotent
+// CHECK-NEXT: cir.return %arg0
+cir.func @min_idempotent(%arg0 : !s32i) -> !s32i {
+ %0 = cir.min %arg0, %arg0 : !s32i
+ cir.return %0 : !s32i
+}
+
+// Idempotent: max(x, x) -> x (unsigned)
+// CHECK-LABEL: cir.func @max_idempotent_unsigned
+// CHECK-NEXT: cir.return %arg0
+cir.func @max_idempotent_unsigned(%arg0 : !u32i) -> !u32i {
+ %0 = cir.max %arg0, %arg0 : !u32i
+ cir.return %0 : !u32i
+}
+
+// Idempotent: min(x, x) -> x (unsigned)
+// CHECK-LABEL: cir.func @min_idempotent_unsigned
+// CHECK-NEXT: cir.return %arg0
+cir.func @min_idempotent_unsigned(%arg0 : !u32i) -> !u32i {
+ %0 = cir.min %arg0, %arg0 : !u32i
+ cir.return %0 : !u32i
+}
+
+// Commutative: max(const, x) -> max(x, const)
+// CHECK-LABEL: cir.func @max_commutative
+// CHECK: %[[C:.*]] = cir.const #cir.int<42> : !s32i
+// CHECK-NEXT: %[[R:.*]] = cir.max %arg0, %[[C]] : !s32i
+// CHECK-NEXT: cir.return %[[R]]
+cir.func @max_commutative(%arg0 : !s32i) -> !s32i {
+ %0 = cir.const #cir.int<42> : !s32i
+ %1 = cir.max %0, %arg0 : !s32i
+ cir.return %1 : !s32i
+}
+
+// Commutative: min(const, x) -> min(x, const)
+// CHECK-LABEL: cir.func @min_commutative
+// CHECK: %[[C:.*]] = cir.const #cir.int<42> : !s32i
+// CHECK-NEXT: %[[R:.*]] = cir.min %arg0, %[[C]] : !s32i
+// CHECK-NEXT: cir.return %[[R]]
+cir.func @min_commutative(%arg0 : !s32i) -> !s32i {
+ %0 = cir.const #cir.int<42> : !s32i
+ %1 = cir.min %0, %arg0 : !s32i
+ cir.return %1 : !s32i
+}
+
+// Idempotent chained: max(max(x, y), max(x, y)) -> max(x, y)
+// CHECK-LABEL: cir.func @max_idempotent_chained
+// CHECK-NEXT: %[[M:.*]] = cir.max %arg0, %arg1 : !s32i
+// CHECK-NEXT: cir.return %[[M]]
+cir.func @max_idempotent_chained(%arg0 : !s32i, %arg1 : !s32i) -> !s32i {
+ %0 = cir.max %arg0, %arg1 : !s32i
+ %1 = cir.max %0, %0 : !s32i
+ cir.return %1 : !s32i
+}
+
+// No fold: distinct operands should remain unchanged
+// CHECK-LABEL: cir.func @max_no_fold
+// CHECK-NEXT: %[[R:.*]] = cir.max %arg0, %arg1 : !s32i
+// CHECK-NEXT: cir.return %[[R]]
+cir.func @max_no_fold(%arg0 : !s32i, %arg1 : !s32i) -> !s32i {
+ %0 = cir.max %arg0, %arg1 : !s32i
+ cir.return %0 : !s32i
+}
More information about the llvm-branch-commits
mailing list