[llvm] 4eb56cf - [InstSimplify] Fold value-preserving zext/sext of trunc (#204089)

via llvm-commits llvm-commits at lists.llvm.org
Sun Jul 5 20:44:45 PDT 2026


Author: Pengcheng Wang
Date: 2026-07-06T03:44:39Z
New Revision: 4eb56cf497aaee55dc1346a7e1319dff33864601

URL: https://github.com/llvm/llvm-project/commit/4eb56cf497aaee55dc1346a7e1319dff33864601
DIFF: https://github.com/llvm/llvm-project/commit/4eb56cf497aaee55dc1346a7e1319dff33864601.diff

LOG: [InstSimplify] Fold value-preserving zext/sext of trunc (#204089)

Fold a value-preserving `zext (trunc nuw X)` / `sext (trunc nsw X)` back
to
`X` in `simplifyCastInst` (when the trunc source type equals the extend
result type and the no-wrap flag guarantees the round-trip is identity).

After indvars canonicalizes a min-index loop, the running-index select
has a
`trunc nuw nsw i64 %iv to i32` on its true side while `arr[i]` is
addressed
directly with the i64 `%iv`. When PHI-translating the select-dependent
address along the backedge (see #203863), the true side becomes
`gep base, zext nneg (trunc nuw %iv)`, which has no matching instruction
in
the loop, so the select dependency is never formed and the redundant
load
survives.

Teaching `simplifyCastInst` to collapse the value-preserving
extend-of-trunc
makes the translated address fold back to `gep base, %iv`, matching the
existing `arr[i]` address, so the load through the select index is
eliminated. This fixes the integer min-index variant of

https://github.com/llvm/llvm-project/issues/58569#issuecomment-2788365047
(e.g. returning `minloc` instead of the value).

Alive2: https://alive2.llvm.org/ce/z/T5kMG8

Assisted-by: TraeCli (AI assistant)

Added: 
    

Modified: 
    llvm/lib/Analysis/InstructionSimplify.cpp
    llvm/test/Transforms/GVN/PRE/pre-load-through-select.ll
    llvm/test/Transforms/InstSimplify/cast.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index fcd7f68260856..1efc01a726c37 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -5668,6 +5668,17 @@ static Value *simplifyCastInst(unsigned CastOpc, Value *Op, Type *Ty,
       X->getType() == Ty && Ty == Q.DL.getIndexType(Ptr->getType()))
     return X;
 
+  // Fold a value-preserving zext/sext of a trunc back to the original value.
+  if (CastOpc == Instruction::ZExt || CastOpc == Instruction::SExt) {
+    if (auto *Trunc = dyn_cast<TruncInst>(Op)) {
+      Value *Src = Trunc->getOperand(0);
+      bool NoWrap = CastOpc == Instruction::ZExt ? Trunc->hasNoUnsignedWrap()
+                                                 : Trunc->hasNoSignedWrap();
+      if (Src->getType() == Ty && NoWrap)
+        return Src;
+    }
+  }
+
   return nullptr;
 }
 

diff  --git a/llvm/test/Transforms/GVN/PRE/pre-load-through-select.ll b/llvm/test/Transforms/GVN/PRE/pre-load-through-select.ll
index dc4974e804c80..2a2019e80323e 100644
--- a/llvm/test/Transforms/GVN/PRE/pre-load-through-select.ll
+++ b/llvm/test/Transforms/GVN/PRE/pre-load-through-select.ll
@@ -1148,3 +1148,75 @@ for.body:
   %res.0.lcssa = phi i32 [ 0, %entry ], [ %spec.select, %for.body ]
   ret i32 %res.0.lcssa
 }
+
+; The indvars-canonicalized form of a min-index loop: the induction variable is
+; an i64 `%iv`, `arr[i]` is addressed directly with `%iv`, while the select's
+; true value is `trunc nuw nsw %iv` and the running index is zero-extended back
+; for `arr[minloc]`.  The load through the select index should still be
+; eliminated: translating the true side yields `zext nneg (trunc nuw %iv)` which
+; folds back to `%iv`, matching the existing `arr[i]` address.
+define i32 @test_phi_select_index_loop_trunc(ptr %arr, i64 %n) {
+; MDEP-LABEL: @test_phi_select_index_loop_trunc(
+; MDEP-NEXT:  entry:
+; MDEP-NEXT:    [[DOTPRE:%.*]] = load i32, ptr [[ARR:%.*]], align 4
+; MDEP-NEXT:    br label [[FOR_BODY:%.*]]
+; MDEP:       for.body:
+; MDEP-NEXT:    [[TMP0:%.*]] = phi i32 [ [[DOTPRE]], [[ENTRY:%.*]] ], [ [[TMP3:%.*]], [[FOR_BODY]] ]
+; MDEP-NEXT:    [[IV:%.*]] = phi i64 [ 0, [[ENTRY]] ], [ [[IV_NEXT:%.*]], [[FOR_BODY]] ]
+; MDEP-NEXT:    [[MINLOC:%.*]] = phi i32 [ 0, [[ENTRY]] ], [ [[SEL:%.*]], [[FOR_BODY]] ]
+; MDEP-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds [4 x i8], ptr [[ARR]], i64 [[IV]]
+; MDEP-NEXT:    [[TMP1:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+; MDEP-NEXT:    [[IDXPROM:%.*]] = zext nneg i32 [[MINLOC]] to i64
+; MDEP-NEXT:    [[ARRAYIDX2:%.*]] = getelementptr inbounds [4 x i8], ptr [[ARR]], i64 [[IDXPROM]]
+; MDEP-NEXT:    [[CMP:%.*]] = icmp slt i32 [[TMP1]], [[TMP0]]
+; MDEP-NEXT:    [[TMP2:%.*]] = trunc nuw nsw i64 [[IV]] to i32
+; MDEP-NEXT:    [[SEL]] = select i1 [[CMP]], i32 [[TMP2]], i32 [[MINLOC]]
+; MDEP-NEXT:    [[IV_NEXT]] = add nuw nsw i64 [[IV]], 1
+; MDEP-NEXT:    [[EC:%.*]] = icmp eq i64 [[IV_NEXT]], [[N:%.*]]
+; MDEP-NEXT:    [[TMP3]] = select i1 [[CMP]], i32 [[TMP1]], i32 [[TMP0]]
+; MDEP-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[FOR_BODY]]
+; MDEP:       exit:
+; MDEP-NEXT:    ret i32 [[SEL]]
+;
+; MSSA-LABEL: @test_phi_select_index_loop_trunc(
+; MSSA-NEXT:  entry:
+; MSSA-NEXT:    br label [[FOR_BODY:%.*]]
+; MSSA:       for.body:
+; MSSA-NEXT:    [[IV:%.*]] = phi i64 [ 0, [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[FOR_BODY]] ]
+; MSSA-NEXT:    [[MINLOC:%.*]] = phi i32 [ 0, [[ENTRY]] ], [ [[SEL:%.*]], [[FOR_BODY]] ]
+; MSSA-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds [4 x i8], ptr [[ARR:%.*]], i64 [[IV]]
+; MSSA-NEXT:    [[TMP0:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+; MSSA-NEXT:    [[IDXPROM:%.*]] = zext nneg i32 [[MINLOC]] to i64
+; MSSA-NEXT:    [[ARRAYIDX2:%.*]] = getelementptr inbounds [4 x i8], ptr [[ARR]], i64 [[IDXPROM]]
+; MSSA-NEXT:    [[TMP1:%.*]] = load i32, ptr [[ARRAYIDX2]], align 4
+; MSSA-NEXT:    [[CMP:%.*]] = icmp slt i32 [[TMP0]], [[TMP1]]
+; MSSA-NEXT:    [[TMP2:%.*]] = trunc nuw nsw i64 [[IV]] to i32
+; MSSA-NEXT:    [[SEL]] = select i1 [[CMP]], i32 [[TMP2]], i32 [[MINLOC]]
+; MSSA-NEXT:    [[IV_NEXT]] = add nuw nsw i64 [[IV]], 1
+; MSSA-NEXT:    [[EC:%.*]] = icmp eq i64 [[IV_NEXT]], [[N:%.*]]
+; MSSA-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[FOR_BODY]]
+; MSSA:       exit:
+; MSSA-NEXT:    ret i32 [[SEL]]
+;
+entry:
+  br label %for.body
+
+for.body:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %for.body ]
+  %minloc = phi i32 [ 0, %entry ], [ %sel, %for.body ]
+  %arrayidx = getelementptr inbounds [4 x i8], ptr %arr, i64 %iv
+  %0 = load i32, ptr %arrayidx, align 4
+  %idxprom = zext nneg i32 %minloc to i64
+  %arrayidx2 = getelementptr inbounds [4 x i8], ptr %arr, i64 %idxprom
+  %1 = load i32, ptr %arrayidx2, align 4
+  %cmp = icmp slt i32 %0, %1
+  %2 = trunc nuw nsw i64 %iv to i32
+  %sel = select i1 %cmp, i32 %2, i32 %minloc
+  %iv.next = add nuw nsw i64 %iv, 1
+  %ec = icmp eq i64 %iv.next, %n
+  br i1 %ec, label %exit, label %for.body
+
+exit:
+  %r = phi i32 [ %sel, %for.body ]
+  ret i32 %r
+}

diff  --git a/llvm/test/Transforms/InstSimplify/cast.ll b/llvm/test/Transforms/InstSimplify/cast.ll
index 8178f05be5cb9..8de8d0e0e013f 100644
--- a/llvm/test/Transforms/InstSimplify/cast.ll
+++ b/llvm/test/Transforms/InstSimplify/cast.ll
@@ -56,3 +56,69 @@ define i32 @test5() {
   %add_to_int = ptrtoint ptr %add to i32                            ; 4
   ret i32 %add_to_int                                               ; 4
 }
+
+; A value-preserving zext of a no-unsigned-wrap trunc folds back to the source.
+define i64 @zext_of_trunc_nuw(i64 %x) {
+; CHECK-LABEL: @zext_of_trunc_nuw(
+; CHECK-NEXT:    ret i64 [[X:%.*]]
+;
+  %t = trunc nuw i64 %x to i32
+  %z = zext i32 %t to i64
+  ret i64 %z
+}
+
+; A value-preserving sext of a no-signed-wrap trunc folds back to the source.
+define i64 @sext_of_trunc_nsw(i64 %x) {
+; CHECK-LABEL: @sext_of_trunc_nsw(
+; CHECK-NEXT:    ret i64 [[X:%.*]]
+;
+  %t = trunc nsw i64 %x to i32
+  %s = sext i32 %t to i64
+  ret i64 %s
+}
+
+; Same folds work on vectors.
+define <2 x i64> @zext_of_trunc_nuw_vec(<2 x i64> %x) {
+; CHECK-LABEL: @zext_of_trunc_nuw_vec(
+; CHECK-NEXT:    ret <2 x i64> [[X:%.*]]
+;
+  %t = trunc nuw <2 x i64> %x to <2 x i32>
+  %z = zext <2 x i32> %t to <2 x i64>
+  ret <2 x i64> %z
+}
+
+; Negative: without nuw, zext(trunc) is not value-preserving for InstSimplify.
+define i64 @zext_of_trunc_no_flag(i64 %x) {
+; CHECK-LABEL: @zext_of_trunc_no_flag(
+; CHECK-NEXT:    [[T:%.*]] = trunc i64 [[X:%.*]] to i32
+; CHECK-NEXT:    [[Z:%.*]] = zext i32 [[T]] to i64
+; CHECK-NEXT:    ret i64 [[Z]]
+;
+  %t = trunc i64 %x to i32
+  %z = zext i32 %t to i64
+  ret i64 %z
+}
+
+; Negative: zext of an nsw-only trunc does not guarantee the unsigned round-trip.
+define i64 @zext_of_trunc_nsw_only(i64 %x) {
+; CHECK-LABEL: @zext_of_trunc_nsw_only(
+; CHECK-NEXT:    [[T:%.*]] = trunc nsw i64 [[X:%.*]] to i32
+; CHECK-NEXT:    [[Z:%.*]] = zext i32 [[T]] to i64
+; CHECK-NEXT:    ret i64 [[Z]]
+;
+  %t = trunc nsw i64 %x to i32
+  %z = zext i32 %t to i64
+  ret i64 %z
+}
+
+; Negative: sext of an nuw-only trunc does not guarantee the signed round-trip.
+define i64 @sext_of_trunc_nuw_only(i64 %x) {
+; CHECK-LABEL: @sext_of_trunc_nuw_only(
+; CHECK-NEXT:    [[T:%.*]] = trunc nuw i64 [[X:%.*]] to i32
+; CHECK-NEXT:    [[S:%.*]] = sext i32 [[T]] to i64
+; CHECK-NEXT:    ret i64 [[S]]
+;
+  %t = trunc nuw i64 %x to i32
+  %s = sext i32 %t to i64
+  ret i64 %s
+}


        


More information about the llvm-commits mailing list