Bug type: miscompilation
Component: InstCombine
Bug location: llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp: foldBitCeil
Issue title: [InstCombine] `foldBitCeil` miscompiles non-power-of-two integer widths
Labels: miscompilation
llvm:instcombine
Reported by: cardigan1008

There exists an incorrect transformation for non-power-of-two integer widths in `foldBitCeil`.

The rewrite replaces the original computation `shift = BitWidth - ctlz(x)` to `shift = (-ctlz(x)) & (BitWidth - 1)`. 

This masking trick is only valid when `BitWidth` is a power of two, because in that case `(BitWidth - 1)` is an all-ones mask and the expression effectively computes `(BitWidth - ctlz) mod BitWidth`.

For non-power-of-two widths (e.g. `i33`), `BitWidth - 1` is not an all-ones mask. As a result, `(-ctlz) & (BitWidth - 1)` does not compute `BitWidth - ctlz` and can produce an entirely different shift amount.

For example, with `i33` and `x = 7`:

- `ctlz(7) = 30`
- Source computes `shift = 33 - 30 = 3`, yielding `1 << 3 = 8`
- Transformed code computes `(-30) & 32 = 32`, yielding` 1 << 32`

This causes a value mismatch and makes the transformation incorrect for non-power-of-two bit widths. 

Alive2 proof: https://alive2.llvm.org/ce/z/Z3Gq8Q

Exposed from https://github.com/llvm/llvm-project/commit/2c094ac761912eea0d7e8ccb140bc647b5378bdf, cc @dtcxzyw 

Comments: author: cardigan1008
body: I can propose a patch for this. 

Fix patch:
commit 57927e9ffcf1ebd36b0b9d76c49e7af1dd5d31d1
Author: Yunbo Ni <87902024+cardigan1008@users.noreply.github.com>
Date:   Mon Dec 29 20:17:36 2025 +0800

    [InstCombine] Restrict `foldBitCeil` to power-of-two integer widths (#173849)
    
    The masking rewrite in `foldBitCeil` assumes a power-of-two bitwidth.
    
    For non-power-of-two integer types, `(-ctlz) & (BitWidth - 1)` is not
    equivalent to `BitWidth - ctlz` and can miscompile.
    
    This patch restricts the transform to power-of-two bitwidths.
    
    Alive2 proof: https://alive2.llvm.org/ce/z/i2E6zT
    
    Fixes #173787

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 67d184583272..480f31980139 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -3846,6 +3846,8 @@ static Instruction *foldBitCeil(SelectInst &SI, IRBuilderBase &Builder,
                                 InstCombinerImpl &IC) {
   Type *SelType = SI.getType();
   unsigned BitWidth = SelType->getScalarSizeInBits();
+  if (!isPowerOf2_32(BitWidth))
+    return nullptr;
 
   Value *FalseVal = SI.getFalseValue();
   Value *TrueVal = SI.getTrueValue();


Regression tests:
file: llvm/test/Transforms/InstCombine/bit_ceil.ll
commands: opt < %s -passes=instcombine -S
tests: test_name: bit_ceil_plus_nsw
test_body: define i32 @bit_ceil_plus_nsw(i32 %x) {
entry:
  %sub = add nsw i32 %x, 1
  %ctlz = tail call i32 @llvm.ctlz.i32(i32 %sub, i1 false)
  %sub2 = sub nuw nsw i32 32, %ctlz
  %shl = shl nuw i32 1, %sub2
  %ult = icmp ult i32 %x, 2147483647
  %sel = select i1 %ult, i32 %shl, i32 1
  ret i32 %sel
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i32 @llvm.ctlz.i32(i32, i1 immarg) #0

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: test_bit_ceil_i33_non_pow2
test_body: define i33 @test_bit_ceil_i33_non_pow2(i33 %x) {
  %ctlz = call i33 @llvm.ctlz.i33(i33 %x, i1 false)
  %sub = sub i33 33, %ctlz
  %shl = shl i33 1, %sub
  %dec = add i33 %x, -1
  %ult = icmp ult i33 %dec, -2
  %sel = select i1 %ult, i33 %shl, i33 1
  ret i33 %sel
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i33 @llvm.ctlz.i33(i33, i1 immarg) #0

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: test_drop_range_attr
test_body: define i32 @test_drop_range_attr(i32 %x) {
  %ctlz = call range(i32 1, 33) i32 @llvm.ctlz.i32(i32 %x, i1 false)
  %sub = sub i32 32, %ctlz
  %shl = shl i32 1, %sub
  %dec = add i32 %x, -1
  %ult = icmp ult i32 %dec, -2
  %sel = select i1 %ult, i32 %shl, i32 1
  ret i32 %sel
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i32 @llvm.ctlz.i32(i32, i1 immarg) #0

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }