[llvm] [GIsel] Add constant-folding for more unary integer ops (PR #194265)

Alex MacLean via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 27 13:00:24 PDT 2026


https://github.com/AlexMaclean updated https://github.com/llvm/llvm-project/pull/194265

>From 65ccaaafd5f8576b80034c9622e300b296674eb1 Mon Sep 17 00:00:00 2001
From: Alex Maclean <amaclean at nvidia.com>
Date: Sun, 26 Apr 2026 13:28:45 -0700
Subject: [PATCH 1/2] [GIsel] Add constant-folding for more unary integer ops

---
 .../llvm/CodeGen/GlobalISel/CombinerHelper.h  |   8 +-
 llvm/include/llvm/CodeGen/GlobalISel/Utils.h  |  12 +-
 .../include/llvm/Target/GlobalISel/Combine.td |  13 +-
 llvm/lib/CodeGen/GlobalISel/CSEMIRBuilder.cpp |   9 +-
 .../lib/CodeGen/GlobalISel/CombinerHelper.cpp |   8 +-
 llvm/lib/CodeGen/GlobalISel/Utils.cpp         |  18 +-
 ...ir => combine-constant-fold-unary-int.mir} | 173 ++++++++++++++++++
 7 files changed, 213 insertions(+), 28 deletions(-)
 rename llvm/test/CodeGen/AArch64/GlobalISel/{combine-constant-fold-count.mir => combine-constant-fold-unary-int.mir} (55%)

diff --git a/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h b/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
index b278a4eeda90f..97f29015c6911 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
@@ -409,9 +409,11 @@ class CombinerHelper {
   void applyCombineConstantFoldFpUnary(MachineInstr &MI,
                                        const ConstantFP *Cst) const;
 
-  /// Constant fold G_CTLZ, G_CTTZ, G_CTPOP (and their _ZERO_UNDEF variants)
-  /// when the operand is a scalar constant or a G_BUILD_VECTOR of constants.
-  bool matchConstantFoldCountOp(MachineInstr &MI, BuildFnTy &MatchInfo) const;
+  /// Constant fold a unary integer op (G_CTLZ, G_CTTZ, G_CTPOP and their
+  /// _ZERO_UNDEF variants, G_ABS, G_BSWAP, G_BITREVERSE) when the operand is
+  /// a scalar constant or a G_BUILD_VECTOR of constants.
+  bool matchConstantFoldUnaryIntOp(MachineInstr &MI,
+                                   BuildFnTy &MatchInfo) const;
 
   /// Transform IntToPtr(PtrToInt(x)) to x if cast is in the same address space.
   bool matchCombineI2PToP2I(MachineInstr &MI, Register &Reg) const;
diff --git a/llvm/include/llvm/CodeGen/GlobalISel/Utils.h b/llvm/include/llvm/CodeGen/GlobalISel/Utils.h
index 72e60ed8d5189..a861a60ecfcd3 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/Utils.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/Utils.h
@@ -319,12 +319,12 @@ LLVM_ABI std::optional<APFloat>
 ConstantFoldIntToFloat(unsigned Opcode, LLT DstTy, Register Src,
                        const MachineRegisterInfo &MRI);
 
-/// Tries to constant fold a bit-counting operation (G_CTLZ, G_CTTZ, G_CTPOP
-/// and their _ZERO_UNDEF variants) on \p Src. If \p Src is a vector then it
-/// tries to do an element-wise constant fold.
-LLVM_ABI SmallVector<APInt> ConstantFoldCountOp(unsigned Opcode, LLT DstTy,
-                                                Register Src,
-                                                const MachineRegisterInfo &MRI);
+/// Tries to constant fold a unary integer operation (G_CTLZ, G_CTTZ, G_CTPOP
+/// and their _ZERO_UNDEF variants, G_ABS, G_BSWAP, G_BITREVERSE) on \p Src.
+/// If \p Src is a vector then it tries to do an element-wise constant fold.
+LLVM_ABI SmallVector<APInt>
+ConstantFoldUnaryIntOp(unsigned Opcode, LLT DstTy, Register Src,
+                       const MachineRegisterInfo &MRI);
 
 LLVM_ABI std::optional<SmallVector<APInt>>
 ConstantFoldICmp(unsigned Pred, const Register Op1, const Register Op2,
diff --git a/llvm/include/llvm/Target/GlobalISel/Combine.td b/llvm/include/llvm/Target/GlobalISel/Combine.td
index 504ad0c9d817f..d4ef5722f44c5 100644
--- a/llvm/include/llvm/Target/GlobalISel/Combine.td
+++ b/llvm/include/llvm/Target/GlobalISel/Combine.td
@@ -1390,16 +1390,17 @@ def constant_fold_cast_op : GICombineRule<
    [{ return Helper.matchConstantFoldCastOp(*${d}, ${matchinfo}); }]),
   (apply [{ Helper.replaceInstWithConstant(*${d}, ${matchinfo}); }])>;
 
-def count_op_frags : GICombinePatFrag<
+def unary_int_op_frags : GICombinePatFrag<
   (outs root:$dst), (ins),
   !foreach(op,
-           [G_CTLZ, G_CTLZ_ZERO_UNDEF, G_CTTZ, G_CTTZ_ZERO_UNDEF, G_CTPOP],
+           [G_CTLZ, G_CTLZ_ZERO_UNDEF, G_CTTZ, G_CTTZ_ZERO_UNDEF, G_CTPOP,
+            G_ABS, G_BSWAP, G_BITREVERSE],
            (pattern (op $dst, $src)))>;
 
-def constant_fold_count_op : GICombineRule<
+def constant_fold_unary_int_op : GICombineRule<
   (defs root:$dst, build_fn_matchinfo:$matchinfo),
-  (match (count_op_frags $dst):$mi,
-   [{ return Helper.matchConstantFoldCountOp(*${mi}, ${matchinfo}); }]),
+  (match (unary_int_op_frags $dst):$mi,
+   [{ return Helper.matchConstantFoldUnaryIntOp(*${mi}, ${matchinfo}); }]),
   (apply [{ Helper.applyBuildFn(*${mi}, ${matchinfo}); }])>;
 
 def mulo_by_2: GICombineRule<
@@ -2378,7 +2379,7 @@ def all_combines : GICombineGroup<[integer_reassoc_combines, trivial_combines,
     shift_immed_chain, shift_of_shifted_logic_chain, load_or_combine,
     div_rem_to_divrem, funnel_shift_combines, bitreverse_shift, commute_shift,
     form_bitfield_extract, constant_fold_binops, constant_fold_fma,
-    constant_fold_cast_op, constant_fold_count_op, fabs_fneg_fold,
+    constant_fold_cast_op, constant_fold_unary_int_op, fabs_fneg_fold,
     mulh_combines, redundant_neg_operands,
     and_or_disjoint_mask, fma_combines, fold_binop_into_select,
     intrem_combines, intdiv_combines, fdiv_repeated_divison,
diff --git a/llvm/lib/CodeGen/GlobalISel/CSEMIRBuilder.cpp b/llvm/lib/CodeGen/GlobalISel/CSEMIRBuilder.cpp
index 28bc880af962e..ff96d7a3cbd9a 100644
--- a/llvm/lib/CodeGen/GlobalISel/CSEMIRBuilder.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/CSEMIRBuilder.cpp
@@ -285,11 +285,14 @@ MachineInstrBuilder CSEMIRBuilder::buildInstr(unsigned Opc,
   case TargetOpcode::G_CTLZ_ZERO_UNDEF:
   case TargetOpcode::G_CTTZ:
   case TargetOpcode::G_CTTZ_ZERO_UNDEF:
-  case TargetOpcode::G_CTPOP: {
+  case TargetOpcode::G_CTPOP:
+  case TargetOpcode::G_ABS:
+  case TargetOpcode::G_BSWAP:
+  case TargetOpcode::G_BITREVERSE: {
     assert(SrcOps.size() == 1 && "Expected one source");
     assert(DstOps.size() == 1 && "Expected one dest");
-    auto Csts = ConstantFoldCountOp(Opc, DstOps[0].getLLTTy(*getMRI()),
-                                    SrcOps[0].getReg(), *getMRI());
+    auto Csts = ConstantFoldUnaryIntOp(Opc, DstOps[0].getLLTTy(*getMRI()),
+                                       SrcOps[0].getReg(), *getMRI());
     if (Csts.empty())
       break;
     if (Csts.size() == 1)
diff --git a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
index 07696a0b6f3b3..95b7f864c16fd 100644
--- a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
@@ -5320,11 +5320,11 @@ bool CombinerHelper::matchConstantFoldCastOp(MachineInstr &MI,
   return false;
 }
 
-bool CombinerHelper::matchConstantFoldCountOp(MachineInstr &MI,
-                                              BuildFnTy &MatchInfo) const {
+bool CombinerHelper::matchConstantFoldUnaryIntOp(MachineInstr &MI,
+                                                 BuildFnTy &MatchInfo) const {
   Register Dst = MI.getOperand(0).getReg();
-  auto Csts = ConstantFoldCountOp(MI.getOpcode(), MRI.getType(Dst),
-                                  MI.getOperand(1).getReg(), MRI);
+  auto Csts = ConstantFoldUnaryIntOp(MI.getOpcode(), MRI.getType(Dst),
+                                     MI.getOperand(1).getReg(), MRI);
   if (Csts.empty())
     return false;
 
diff --git a/llvm/lib/CodeGen/GlobalISel/Utils.cpp b/llvm/lib/CodeGen/GlobalISel/Utils.cpp
index 891d55a85db7d..b3aac99cfa427 100644
--- a/llvm/lib/CodeGen/GlobalISel/Utils.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/Utils.cpp
@@ -940,11 +940,11 @@ llvm::ConstantFoldIntToFloat(unsigned Opcode, LLT DstTy, Register Src,
   return std::nullopt;
 }
 
-SmallVector<APInt> llvm::ConstantFoldCountOp(unsigned Opcode, LLT DstTy,
-                                             Register Src,
-                                             const MachineRegisterInfo &MRI) {
+SmallVector<APInt>
+llvm::ConstantFoldUnaryIntOp(unsigned Opcode, LLT DstTy, Register Src,
+                             const MachineRegisterInfo &MRI) {
   unsigned EltBits = DstTy.getScalarSizeInBits();
-  auto Count = [Opcode, EltBits](const APInt &V) -> APInt {
+  auto Fold = [Opcode, EltBits](const APInt &V) -> APInt {
     switch (Opcode) {
     case TargetOpcode::G_CTLZ:
     case TargetOpcode::G_CTLZ_ZERO_UNDEF:
@@ -954,13 +954,19 @@ SmallVector<APInt> llvm::ConstantFoldCountOp(unsigned Opcode, LLT DstTy,
       return APInt(EltBits, V.countr_zero());
     case TargetOpcode::G_CTPOP:
       return APInt(EltBits, V.popcount());
+    case TargetOpcode::G_ABS:
+      return V.abs();
+    case TargetOpcode::G_BSWAP:
+      return V.byteSwap();
+    case TargetOpcode::G_BITREVERSE:
+      return V.reverseBits();
     }
-    llvm_unreachable("unexpected opcode in ConstantFoldCountOp");
+    llvm_unreachable("unexpected opcode in ConstantFoldUnaryIntOp");
   };
 
   auto tryFoldScalar = [&](Register R) -> std::optional<APInt> {
     if (auto MaybeCst = getIConstantVRegVal(R, MRI))
-      return Count(*MaybeCst);
+      return Fold(*MaybeCst);
     return std::nullopt;
   };
   if (MRI.getType(Src).isVector()) {
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/combine-constant-fold-count.mir b/llvm/test/CodeGen/AArch64/GlobalISel/combine-constant-fold-unary-int.mir
similarity index 55%
rename from llvm/test/CodeGen/AArch64/GlobalISel/combine-constant-fold-count.mir
rename to llvm/test/CodeGen/AArch64/GlobalISel/combine-constant-fold-unary-int.mir
index 2a2a84c368784..cba19b49d7687 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/combine-constant-fold-count.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/combine-constant-fold-unary-int.mir
@@ -225,3 +225,176 @@ body:             |
     $w0 = COPY %res(s32)
     RET_ReallyLR implicit $w0
 ...
+---
+name:            abs_s32_pos
+body:             |
+  bb.0:
+    ; CHECK-LABEL: name: abs_s32_pos
+    ; CHECK: %a:_(s32) = G_CONSTANT i32 42
+    ; CHECK-NEXT: $w0 = COPY %a(s32)
+    ; CHECK-NEXT: RET_ReallyLR implicit $w0
+    %a:_(s32) = G_CONSTANT i32 42
+    %res:_(s32) = G_ABS %a
+    $w0 = COPY %res(s32)
+    RET_ReallyLR implicit $w0
+...
+---
+name:            abs_s32_neg
+body:             |
+  bb.0:
+    ; CHECK-LABEL: name: abs_s32_neg
+    ; CHECK: %res:_(s32) = G_CONSTANT i32 42
+    ; CHECK-NEXT: $w0 = COPY %res(s32)
+    ; CHECK-NEXT: RET_ReallyLR implicit $w0
+    %a:_(s32) = G_CONSTANT i32 -42
+    %res:_(s32) = G_ABS %a
+    $w0 = COPY %res(s32)
+    RET_ReallyLR implicit $w0
+...
+---
+# abs of INT_MIN stays INT_MIN.
+name:            abs_s32_int_min
+body:             |
+  bb.0:
+    ; CHECK-LABEL: name: abs_s32_int_min
+    ; CHECK: %a:_(s32) = G_CONSTANT i32 -2147483648
+    ; CHECK-NEXT: $w0 = COPY %a(s32)
+    ; CHECK-NEXT: RET_ReallyLR implicit $w0
+    %a:_(s32) = G_CONSTANT i32 -2147483648
+    %res:_(s32) = G_ABS %a
+    $w0 = COPY %res(s32)
+    RET_ReallyLR implicit $w0
+...
+---
+name:            abs_v4s32
+body:             |
+  bb.0:
+    ; CHECK-LABEL: name: abs_v4s32
+    ; CHECK: %c1:_(s32) = G_CONSTANT i32 2
+    ; CHECK-NEXT: %c3:_(s32) = G_CONSTANT i32 4
+    ; CHECK-NEXT: [[C:%[0-9]+]]:_(s32) = G_CONSTANT i32 1
+    ; CHECK-NEXT: [[C1:%[0-9]+]]:_(s32) = G_CONSTANT i32 3
+    ; CHECK-NEXT: %res:_(<4 x s32>) = G_BUILD_VECTOR [[C]](s32), %c1(s32), [[C1]](s32), %c3(s32)
+    ; CHECK-NEXT: $q0 = COPY %res(<4 x s32>)
+    ; CHECK-NEXT: RET_ReallyLR implicit $q0
+    %c0:_(s32) = G_CONSTANT i32 -1
+    %c1:_(s32) = G_CONSTANT i32 2
+    %c2:_(s32) = G_CONSTANT i32 -3
+    %c3:_(s32) = G_CONSTANT i32 4
+    %v:_(<4 x s32>) = G_BUILD_VECTOR %c0, %c1, %c2, %c3
+    %res:_(<4 x s32>) = G_ABS %v
+    $q0 = COPY %res(<4 x s32>)
+    RET_ReallyLR implicit $q0
+...
+---
+name:            bswap_s32
+body:             |
+  bb.0:
+    ; CHECK-LABEL: name: bswap_s32
+    ; CHECK: %res:_(s32) = G_CONSTANT i32 2018915346
+    ; CHECK-NEXT: $w0 = COPY %res(s32)
+    ; CHECK-NEXT: RET_ReallyLR implicit $w0
+    %a:_(s32) = G_CONSTANT i32 305419896
+    %res:_(s32) = G_BSWAP %a
+    $w0 = COPY %res(s32)
+    RET_ReallyLR implicit $w0
+...
+---
+name:            bswap_s64
+body:             |
+  bb.0:
+    ; CHECK-LABEL: name: bswap_s64
+    ; CHECK: %a:_(s64) = G_CONSTANT i64 -1
+    ; CHECK-NEXT: $x0 = COPY %a(s64)
+    ; CHECK-NEXT: RET_ReallyLR implicit $x0
+    %a:_(s64) = G_CONSTANT i64 -1
+    %res:_(s64) = G_BSWAP %a
+    $x0 = COPY %res(s64)
+    RET_ReallyLR implicit $x0
+...
+---
+name:            bswap_v4s32
+body:             |
+  bb.0:
+    ; CHECK-LABEL: name: bswap_v4s32
+    ; CHECK: [[C:%[0-9]+]]:_(s32) = G_CONSTANT i32 16777216
+    ; CHECK-NEXT: [[C1:%[0-9]+]]:_(s32) = G_CONSTANT i32 33554432
+    ; CHECK-NEXT: [[C2:%[0-9]+]]:_(s32) = G_CONSTANT i32 67108864
+    ; CHECK-NEXT: [[C3:%[0-9]+]]:_(s32) = G_CONSTANT i32 134217728
+    ; CHECK-NEXT: %res:_(<4 x s32>) = G_BUILD_VECTOR [[C]](s32), [[C1]](s32), [[C2]](s32), [[C3]](s32)
+    ; CHECK-NEXT: $q0 = COPY %res(<4 x s32>)
+    ; CHECK-NEXT: RET_ReallyLR implicit $q0
+    %c0:_(s32) = G_CONSTANT i32 1
+    %c1:_(s32) = G_CONSTANT i32 2
+    %c2:_(s32) = G_CONSTANT i32 4
+    %c3:_(s32) = G_CONSTANT i32 8
+    %v:_(<4 x s32>) = G_BUILD_VECTOR %c0, %c1, %c2, %c3
+    %res:_(<4 x s32>) = G_BSWAP %v
+    $q0 = COPY %res(<4 x s32>)
+    RET_ReallyLR implicit $q0
+...
+---
+name:            bitreverse_s32
+body:             |
+  bb.0:
+    ; CHECK-LABEL: name: bitreverse_s32
+    ; CHECK: %res:_(s32) = G_CONSTANT i32 -2147483648
+    ; CHECK-NEXT: $w0 = COPY %res(s32)
+    ; CHECK-NEXT: RET_ReallyLR implicit $w0
+    %a:_(s32) = G_CONSTANT i32 1
+    %res:_(s32) = G_BITREVERSE %a
+    $w0 = COPY %res(s32)
+    RET_ReallyLR implicit $w0
+...
+---
+name:            bitreverse_s32_all_ones
+body:             |
+  bb.0:
+    ; CHECK-LABEL: name: bitreverse_s32_all_ones
+    ; CHECK: %a:_(s32) = G_CONSTANT i32 -1
+    ; CHECK-NEXT: $w0 = COPY %a(s32)
+    ; CHECK-NEXT: RET_ReallyLR implicit $w0
+    %a:_(s32) = G_CONSTANT i32 -1
+    %res:_(s32) = G_BITREVERSE %a
+    $w0 = COPY %res(s32)
+    RET_ReallyLR implicit $w0
+...
+---
+name:            bitreverse_v4s32
+body:             |
+  bb.0:
+    ; CHECK-LABEL: name: bitreverse_v4s32
+    ; CHECK: [[C:%[0-9]+]]:_(s32) = G_CONSTANT i32 -2147483648
+    ; CHECK-NEXT: [[C1:%[0-9]+]]:_(s32) = G_CONSTANT i32 1073741824
+    ; CHECK-NEXT: [[C2:%[0-9]+]]:_(s32) = G_CONSTANT i32 536870912
+    ; CHECK-NEXT: [[C3:%[0-9]+]]:_(s32) = G_CONSTANT i32 268435456
+    ; CHECK-NEXT: %res:_(<4 x s32>) = G_BUILD_VECTOR [[C]](s32), [[C1]](s32), [[C2]](s32), [[C3]](s32)
+    ; CHECK-NEXT: $q0 = COPY %res(<4 x s32>)
+    ; CHECK-NEXT: RET_ReallyLR implicit $q0
+    %c0:_(s32) = G_CONSTANT i32 1
+    %c1:_(s32) = G_CONSTANT i32 2
+    %c2:_(s32) = G_CONSTANT i32 4
+    %c3:_(s32) = G_CONSTANT i32 8
+    %v:_(<4 x s32>) = G_BUILD_VECTOR %c0, %c1, %c2, %c3
+    %res:_(<4 x s32>) = G_BITREVERSE %v
+    $q0 = COPY %res(<4 x s32>)
+    RET_ReallyLR implicit $q0
+...
+---
+# Negative test: non-constant source should not be folded.
+name:            abs_nonconstant
+body:             |
+  bb.0:
+    liveins: $w0
+    ; CHECK-LABEL: name: abs_nonconstant
+    ; CHECK: liveins: $w0
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: %a:_(s32) = COPY $w0
+    ; CHECK-NEXT: %res:_(s32) = G_ABS %a
+    ; CHECK-NEXT: $w0 = COPY %res(s32)
+    ; CHECK-NEXT: RET_ReallyLR implicit $w0
+    %a:_(s32) = COPY $w0
+    %res:_(s32) = G_ABS %a
+    $w0 = COPY %res(s32)
+    RET_ReallyLR implicit $w0
+...

>From 834a8167e5f76fc16e6904900569a30881c7be93 Mon Sep 17 00:00:00 2001
From: Alex Maclean <amaclean at nvidia.com>
Date: Mon, 27 Apr 2026 13:00:09 -0700
Subject: [PATCH 2/2] fixup

---
 .../CodeGen/SPIRV/extensions/enable-all-extensions-but-one.ll | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/test/CodeGen/SPIRV/extensions/enable-all-extensions-but-one.ll b/llvm/test/CodeGen/SPIRV/extensions/enable-all-extensions-but-one.ll
index aeef8392468b6..b1381679470e1 100644
--- a/llvm/test/CodeGen/SPIRV/extensions/enable-all-extensions-but-one.ll
+++ b/llvm/test/CodeGen/SPIRV/extensions/enable-all-extensions-but-one.ll
@@ -6,8 +6,8 @@
 
 @G = global i32 0
 
-define i6 @foo() {
-  %call = tail call i32 @llvm.bitreverse.i32(i32 42)
+define i6 @foo(i32 %x) {
+  %call = tail call i32 @llvm.bitreverse.i32(i32 %x)
   store i32 %call, ptr @G
   ret i6 2
 }



More information about the llvm-commits mailing list