[llvm] [InstCombine] Fold trunc(lshr(add(shl(X, ShAmt), C), ShrAmt)) (PR #214562)

via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 12 05:36:48 PDT 2026


================
@@ -0,0 +1,293 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -S -passes=instcombine < %s | FileCheck %s
+
+; trunc(lshr(BinOp(shl(X, ShAmt), C), ShrAmt))
+;   --> trunc(BinOp(shl(X, ShAmt - ShrAmt), C >> ShrAmt))
+; where BinOp is add, or, xor, or and. or/xor/and distribute over lshr
+; unconditionally; add additionally needs C's low ShrAmt bits to be zero.
+;
+; %x is the result of an opaque call in most tests below purely so the
+; RUN line's output is stable and easy to read; InstCombine's generic
+; "shrink the whole expression to a smaller type" machinery still applies
+; on top of this fold once the lshr is gone, which is expected -- it just
+; means the fold enables further cleanup, not that it's being bypassed.
+declare i32 @opaque(i32)
+declare void @use32(i32)
+
+define i8 @src_from_issue(i32 %x) {
+; CHECK-LABEL: define i8 @src_from_issue(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT:    [[V17:%.*]] = add i8 [[TMP1]], -120
+; CHECK-NEXT:    ret i8 [[V17]]
+;
+  %v2 = shl i32 %x, 12
+  %v3 = add i32 %v2, 34816
+  %v16 = lshr i32 %v3, 8
+  %v17 = trunc i32 %v16 to i8
+  ret i8 %v17
+}
+
+define i8 @positive_basic(i32 %y) {
+; CHECK-LABEL: define i8 @positive_basic(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT:    [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT:    [[V17:%.*]] = add i8 [[TMP1]], -120
+; CHECK-NEXT:    ret i8 [[V17]]
+;
+  %x = call i32 @opaque(i32 %y)
+  %v2 = shl i32 %x, 12
+  %v3 = add i32 %v2, 34816
+  %v16 = lshr i32 %v3, 8
+  %v17 = trunc i32 %v16 to i8
+  ret i8 %v17
+}
+
+; ShrAmt == ShAmt: the new shift amount is 0.
+define i8 @positive_boundary_shramt_eq_shamt(i32 %y) {
+; CHECK-LABEL: define i8 @positive_boundary_shramt_eq_shamt(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT:    [[V17:%.*]] = add i8 [[TMP1]], -120
+; CHECK-NEXT:    ret i8 [[V17]]
+;
+  %x = call i32 @opaque(i32 %y)
+  %v2 = shl i32 %x, 8
+  %v3 = add i32 %v2, 34816
+  %v16 = lshr i32 %v3, 8
+  %v17 = trunc i32 %v16 to i8
+  ret i8 %v17
+}
+
+; ShrAmt == SrcWidth - DestWidth (24): still safe, right at the boundary.
+define i8 @positive_boundary_shramt_eq_srcwidth_minus_destwidth(i32 %y) {
+; CHECK-LABEL: define i8 @positive_boundary_shramt_eq_srcwidth_minus_destwidth(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT:    [[V17:%.*]] = add i8 [[TMP1]], 1
+; CHECK-NEXT:    ret i8 [[V17]]
+;
+  %x = call i32 @opaque(i32 %y)
+  %v2 = shl i32 %x, 24
+  %v3 = add i32 %v2, 16777216
+  %v16 = lshr i32 %v3, 24
+  %v17 = trunc i32 %v16 to i8
+  ret i8 %v17
+}
+
+; The fold also applies when the inner binop is or/xor/and instead of add.
+define i8 @positive_or(i32 %y) {
+; CHECK-LABEL: define i8 @positive_or(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT:    [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT:    [[V17:%.*]] = or i8 [[TMP1]], -120
+; CHECK-NEXT:    ret i8 [[V17]]
+;
+  %x = call i32 @opaque(i32 %y)
+  %v2 = shl i32 %x, 12
+  %v3 = or i32 %v2, 34816
+  %v16 = lshr i32 %v3, 8
+  %v17 = trunc i32 %v16 to i8
+  ret i8 %v17
+}
+
+define i8 @positive_xor(i32 %y) {
+; CHECK-LABEL: define i8 @positive_xor(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT:    [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT:    [[V17:%.*]] = xor i8 [[TMP1]], -120
+; CHECK-NEXT:    ret i8 [[V17]]
+;
+  %x = call i32 @opaque(i32 %y)
+  %v2 = shl i32 %x, 12
+  %v3 = xor i32 %v2, 34816
+  %v16 = lshr i32 %v3, 8
+  %v17 = trunc i32 %v16 to i8
+  ret i8 %v17
+}
+
+define i8 @positive_and(i32 %y) {
+; CHECK-LABEL: define i8 @positive_and(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT:    [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT:    [[V17:%.*]] = and i8 [[TMP1]], -128
+; CHECK-NEXT:    ret i8 [[V17]]
+;
+  %x = call i32 @opaque(i32 %y)
+  %v2 = shl i32 %x, 12
+  %v3 = and i32 %v2, 34816
+  %v16 = lshr i32 %v3, 8
+  %v17 = trunc i32 %v16 to i8
+  ret i8 %v17
+}
+
+; Unlike add, or/xor/and distribute over lshr unconditionally, so they still
+; fold even with a constant (34817, odd) whose low bits aren't aligned to
+; ShrAmt -- compare against @negative_addc_not_aligned below, which uses the
+; same constant with add and correctly does not fold.
+define i8 @positive_or_misaligned_c(i32 %y) {
+; CHECK-LABEL: define i8 @positive_or_misaligned_c(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT:    [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT:    [[V17:%.*]] = or i8 [[TMP1]], -120
+; CHECK-NEXT:    ret i8 [[V17]]
+;
+  %x = call i32 @opaque(i32 %y)
+  %v2 = shl i32 %x, 12
+  %v3 = or i32 %v2, 34817
+  %v16 = lshr i32 %v3, 8
+  %v17 = trunc i32 %v16 to i8
+  ret i8 %v17
+}
+
+define i8 @positive_xor_misaligned_c(i32 %y) {
+; CHECK-LABEL: define i8 @positive_xor_misaligned_c(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT:    [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT:    [[V17:%.*]] = xor i8 [[TMP1]], -120
+; CHECK-NEXT:    ret i8 [[V17]]
+;
+  %x = call i32 @opaque(i32 %y)
+  %v2 = shl i32 %x, 12
+  %v3 = xor i32 %v2, 34817
+  %v16 = lshr i32 %v3, 8
+  %v17 = trunc i32 %v16 to i8
+  ret i8 %v17
+}
+
+define i8 @positive_and_misaligned_c(i32 %y) {
+; CHECK-LABEL: define i8 @positive_and_misaligned_c(
+; CHECK-SAME: i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[X:%.*]] = call i32 @opaque(i32 [[Y]])
+; CHECK-NEXT:    [[X_TR:%.*]] = trunc i32 [[X]] to i8
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i8 [[X_TR]], 4
+; CHECK-NEXT:    [[V17:%.*]] = and i8 [[TMP1]], -128
+; CHECK-NEXT:    ret i8 [[V17]]
+;
+  %x = call i32 @opaque(i32 %y)
+  %v2 = shl i32 %x, 12
+  %v3 = and i32 %v2, 34817
+  %v16 = lshr i32 %v3, 8
+  %v17 = trunc i32 %v16 to i8
+  ret i8 %v17
+}
+
+; Regression test: an "and" mask (not just trunc) used to crash the fold
+; with a dominance violation.
+define i32 @regression_and_mask_dominance(i32 %x) {
+; CHECK-LABEL: define i32 @regression_and_mask_dominance(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i32 [[X]], 4
+; CHECK-NEXT:    [[TMP2:%.*]] = add i32 [[TMP1]], 136
+; CHECK-NEXT:    [[R:%.*]] = and i32 [[TMP2]], 248
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %v2 = shl i32 %x, 12
+  %v3 = add i32 %v2, 34816
+  %v16 = lshr i32 %v3, 8
+  %r = and i32 %v16, 255
----------------
Ineshmcw wrote:

Added a partial demanded-bits mask (0xF0) for all four ops, plus splat/non-splat vector tests. Should I add other mask shapes too — e.g. a high-region mask (0x00FF0000), a single high bit (0x00800000), or a non-contiguous one (0x0F0F)?

https://github.com/llvm/llvm-project/pull/214562


More information about the llvm-commits mailing list