[llvm] [SeparateConstOffsetFromGEP] Fix offsets across trunc and ext (PR #225195)

Alex MacLean via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 21 14:01:21 PDT 2026


https://github.com/AlexMaclean created https://github.com/llvm/llvm-project/pull/225195

Stop extracting offsets from arithmetic through a truncation followed by sign or zero extension.

[Compiler Explorer reproducer](https://godbolt.org/z/xK1WTT7nY) and [Alive2 counterexample](https://alive2.llvm.org/ce/z/KWcMNZ).


>From 78d33f3485e432263908a37ce9a7822c9335e847 Mon Sep 17 00:00:00 2001
From: Alex Maclean <amaclean at nvidia.com>
Date: Mon, 21 Sep 2026 13:56:13 -0700
Subject: [PATCH] [SeparateConstOffsetFromGEP] Fix offsets across trunc and ext

---
 .../Scalar/SeparateConstOffsetFromGEP.cpp     |   2 +
 .../NVPTX/split-gep.ll                        |   8 +-
 .../NVPTX/trunc-ext.ll                        | 115 ++++++++++++++++++
 3 files changed, 122 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/Transforms/SeparateConstOffsetFromGEP/NVPTX/trunc-ext.ll

diff --git a/llvm/lib/Transforms/Scalar/SeparateConstOffsetFromGEP.cpp b/llvm/lib/Transforms/Scalar/SeparateConstOffsetFromGEP.cpp
index 477ef6d502c8c..aa7219d3057f2 100644
--- a/llvm/lib/Transforms/Scalar/SeparateConstOffsetFromGEP.cpp
+++ b/llvm/lib/Transforms/Scalar/SeparateConstOffsetFromGEP.cpp
@@ -742,6 +742,8 @@ APInt ConstantOffsetExtractor::find(Value *V, GetElementPtrInst *GEP,
     else if (BO->getOpcode() == Instruction::Xor)
       ConstantOffset = extractDisjointBitsFromXor(BO);
   } else if (isa<TruncInst>(V)) {
+    if (SignExtended || ZeroExtended)
+      return ConstantOffset;
     ConstantOffset =
         find(U->getOperand(0), GEP, Idx, SignExtended, ZeroExtended)
             .trunc(BitWidth);
diff --git a/llvm/test/Transforms/SeparateConstOffsetFromGEP/NVPTX/split-gep.ll b/llvm/test/Transforms/SeparateConstOffsetFromGEP/NVPTX/split-gep.ll
index e8cab36f7383c..950b0cff0f352 100644
--- a/llvm/test/Transforms/SeparateConstOffsetFromGEP/NVPTX/split-gep.ll
+++ b/llvm/test/Transforms/SeparateConstOffsetFromGEP/NVPTX/split-gep.ll
@@ -534,13 +534,15 @@ entry:
   ret ptr %ptr2
 }
 
-; Check that we can see through explicit trunc() instruction.
+; The GEP index is sign-extended to pointer size. Do not look through the
+; truncation. This case will be canonicalized by other passes anyway.
 define ptr @trunk_explicit(ptr %ptr, i64 %idx) {
 ; CHECK-LABEL: define ptr @trunk_explicit(
 ; CHECK-SAME: ptr [[PTR:%.*]], i64 [[IDX:%.*]]) {
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    [[TMP0:%.*]] = getelementptr [[STRUCT0:%.*]], ptr [[PTR]], i64 0, i32 3, i64 [[IDX]], i32 1
-; CHECK-NEXT:    [[PTR21:%.*]] = getelementptr i8, ptr [[TMP0]], i64 3216
+; CHECK-NEXT:    [[IDX0:%.*]] = trunc i64 1 to i32
+; CHECK-NEXT:    [[IDXPROM:%.*]] = sext i32 [[IDX0]] to i64
+; CHECK-NEXT:    [[PTR21:%.*]] = getelementptr inbounds [[STRUCT0:%.*]], ptr [[PTR]], i64 [[IDXPROM]], i32 3, i64 [[IDX]], i32 1
 ; CHECK-NEXT:    ret ptr [[PTR21]]
 ;
 entry:
diff --git a/llvm/test/Transforms/SeparateConstOffsetFromGEP/NVPTX/trunc-ext.ll b/llvm/test/Transforms/SeparateConstOffsetFromGEP/NVPTX/trunc-ext.ll
new file mode 100644
index 0000000000000..798478373b7a8
--- /dev/null
+++ b/llvm/test/Transforms/SeparateConstOffsetFromGEP/NVPTX/trunc-ext.ll
@@ -0,0 +1,115 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 3
+; RUN: opt -S -mtriple=nvptx64-nvidia-cuda -passes=separate-const-offset-from-gep -verify-each < %s | FileCheck %s --check-prefixes=CHECK,SPLIT
+; RUN: opt -S -mtriple=nvptx64-nvidia-cuda -passes='separate-const-offset-from-gep<lower-gep>' -verify-each < %s | FileCheck %s --check-prefixes=CHECK,LOWER
+
+; For x = 15, the index is -128. Extracting 113 across both casts would
+; instead give sext(trunc(15)) + 113 = 128, despite the i16 add being nsw.
+define ptr @sext_trunc_add(ptr %base, i16 %x) {
+; CHECK-LABEL: define ptr @sext_trunc_add(
+; CHECK-SAME: ptr [[BASE:%.*]], i16 [[X:%.*]]) {
+; CHECK-NEXT:    [[ADD:%.*]] = add nsw i16 [[X]], 113
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i16 [[ADD]] to i8
+; CHECK-NEXT:    [[EXT:%.*]] = sext i8 [[TRUNC]] to i64
+; CHECK-NEXT:    [[P:%.*]] = getelementptr i8, ptr [[BASE]], i64 [[EXT]]
+; CHECK-NEXT:    ret ptr [[P]]
+;
+  %add = add nsw i16 %x, 113
+  %trunc = trunc i16 %add to i8
+  %ext = sext i8 %trunc to i64
+  %p = getelementptr i8, ptr %base, i64 %ext
+  ret ptr %p
+}
+
+; For x = 143, the index is zero. Extracting 113 across both casts would
+; instead give zext(trunc(143)) + 113 = 256, despite the i16 add being nuw.
+define ptr @zext_trunc_add(ptr %base, i16 %x) {
+; CHECK-LABEL: define ptr @zext_trunc_add(
+; CHECK-SAME: ptr [[BASE:%.*]], i16 [[X:%.*]]) {
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i16 [[X]], 113
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i16 [[ADD]] to i8
+; CHECK-NEXT:    [[EXT:%.*]] = zext i8 [[TRUNC]] to i64
+; CHECK-NEXT:    [[P:%.*]] = getelementptr i8, ptr [[BASE]], i64 [[EXT]]
+; CHECK-NEXT:    ret ptr [[P]]
+;
+  %add = add nuw i16 %x, 113
+  %trunc = trunc i16 %add to i8
+  %ext = zext i8 %trunc to i64
+  %p = getelementptr i8, ptr %base, i64 %ext
+  ret ptr %p
+}
+
+; Failing to extract through truncation must still allow extracting an offset
+; outside the extension.
+define ptr @sext_trunc_add_outer_offset(ptr %base, i16 %x) {
+; SPLIT-LABEL: define ptr @sext_trunc_add_outer_offset(
+; SPLIT-SAME: ptr [[BASE:%.*]], i16 [[X:%.*]]) {
+; SPLIT-NEXT:    [[ADD:%.*]] = add nsw i16 [[X]], 113
+; SPLIT-NEXT:    [[TRUNC:%.*]] = trunc i16 [[ADD]] to i8
+; SPLIT-NEXT:    [[EXT:%.*]] = sext i8 [[TRUNC]] to i64
+; SPLIT-NEXT:    [[TMP1:%.*]] = getelementptr i8, ptr [[BASE]], i64 [[EXT]]
+; SPLIT-NEXT:    [[P2:%.*]] = getelementptr i8, ptr [[TMP1]], i64 16
+; SPLIT-NEXT:    ret ptr [[P2]]
+;
+; LOWER-LABEL: define ptr @sext_trunc_add_outer_offset(
+; LOWER-SAME: ptr [[BASE:%.*]], i16 [[X:%.*]]) {
+; LOWER-NEXT:    [[ADD:%.*]] = add nsw i16 [[X]], 113
+; LOWER-NEXT:    [[TRUNC:%.*]] = trunc i16 [[ADD]] to i8
+; LOWER-NEXT:    [[EXT:%.*]] = sext i8 [[TRUNC]] to i64
+; LOWER-NEXT:    [[UGLYGEP:%.*]] = getelementptr i8, ptr [[BASE]], i64 [[EXT]]
+; LOWER-NEXT:    [[UGLYGEP2:%.*]] = getelementptr i8, ptr [[UGLYGEP]], i64 16
+; LOWER-NEXT:    ret ptr [[UGLYGEP2]]
+;
+  %add = add nsw i16 %x, 113
+  %trunc = trunc i16 %add to i8
+  %ext = sext i8 %trunc to i64
+  %index = add i64 %ext, 16
+  %p = getelementptr i8, ptr %base, i64 %index
+  ret ptr %p
+}
+
+; Truncation alone can still be distributed over addition.
+define ptr @trunc_add(ptr %base, i128 %x) {
+; SPLIT-LABEL: define ptr @trunc_add(
+; SPLIT-SAME: ptr [[BASE:%.*]], i128 [[X:%.*]]) {
+; SPLIT-NEXT:    [[TMP1:%.*]] = trunc i128 [[X]] to i64
+; SPLIT-NEXT:    [[TMP2:%.*]] = getelementptr i8, ptr [[BASE]], i64 [[TMP1]]
+; SPLIT-NEXT:    [[P2:%.*]] = getelementptr i8, ptr [[TMP2]], i64 113
+; SPLIT-NEXT:    ret ptr [[P2]]
+;
+; LOWER-LABEL: define ptr @trunc_add(
+; LOWER-SAME: ptr [[BASE:%.*]], i128 [[X:%.*]]) {
+; LOWER-NEXT:    [[TMP1:%.*]] = trunc i128 [[X]] to i64
+; LOWER-NEXT:    [[UGLYGEP:%.*]] = getelementptr i8, ptr [[BASE]], i64 [[TMP1]]
+; LOWER-NEXT:    [[UGLYGEP2:%.*]] = getelementptr i8, ptr [[UGLYGEP]], i64 113
+; LOWER-NEXT:    ret ptr [[UGLYGEP2]]
+;
+  %add = add i128 %x, 113
+  %index = trunc i128 %add to i64
+  %p = getelementptr i8, ptr %base, i64 %index
+  ret ptr %p
+}
+
+; Truncating after extension does not introduce wrapping before the extension.
+define ptr @trunc_sext_add(ptr %base, i16 %x) {
+; SPLIT-LABEL: define ptr @trunc_sext_add(
+; SPLIT-SAME: ptr [[BASE:%.*]], i16 [[X:%.*]]) {
+; SPLIT-NEXT:    [[TMP1:%.*]] = sext i16 [[X]] to i128
+; SPLIT-NEXT:    [[TMP2:%.*]] = trunc i128 [[TMP1]] to i64
+; SPLIT-NEXT:    [[TMP3:%.*]] = getelementptr i8, ptr [[BASE]], i64 [[TMP2]]
+; SPLIT-NEXT:    [[P2:%.*]] = getelementptr i8, ptr [[TMP3]], i64 113
+; SPLIT-NEXT:    ret ptr [[P2]]
+;
+; LOWER-LABEL: define ptr @trunc_sext_add(
+; LOWER-SAME: ptr [[BASE:%.*]], i16 [[X:%.*]]) {
+; LOWER-NEXT:    [[TMP1:%.*]] = sext i16 [[X]] to i128
+; LOWER-NEXT:    [[TMP2:%.*]] = trunc i128 [[TMP1]] to i64
+; LOWER-NEXT:    [[UGLYGEP:%.*]] = getelementptr i8, ptr [[BASE]], i64 [[TMP2]]
+; LOWER-NEXT:    [[UGLYGEP2:%.*]] = getelementptr i8, ptr [[UGLYGEP]], i64 113
+; LOWER-NEXT:    ret ptr [[UGLYGEP2]]
+;
+  %add = add nsw i16 %x, 113
+  %ext = sext i16 %add to i128
+  %index = trunc i128 %ext to i64
+  %p = getelementptr i8, ptr %base, i64 %index
+  ret ptr %p
+}



More information about the llvm-commits mailing list