[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