[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