[llvm] [InstCombine] Fold variable extract of a constant-strided pointer vector (PR #217078)
Domenic Nutile via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 18 09:47:23 PDT 2026
https://github.com/saxlungs created https://github.com/llvm/llvm-project/pull/217078
A vector built from pointers that all point into the same object at a constant stride is a table of addresses, so a variable extract from it is address arithmetic:
%v0 = insertelement <4 x ptr> poison, ptr %b, i64 0
%v1 = insertelement <4 x ptr> %v0, ptr %b + 8, i64 1
%v2 = insertelement <4 x ptr> %v1, ptr %b + 16, i64 2
%v3 = insertelement <4 x ptr> %v2, ptr %b + 24, i64 3
%r = extractelement <4 x ptr> %v3, i32 %i
-->
%r = getelementptr i8, ptr %b, i64 %i * 8
Fold it to a single GEP. Every element must be reachable from one base pointer at a constant byte offset, every lane of the vector must be defined, and the offsets must form an arithmetic sequence; otherwise the pattern is left alone.
>From 9ed44dce8649e9437e8f9b681a59a4a9815425f9 Mon Sep 17 00:00:00 2001
From: Domenic Nutile <domenic.nutile at gmail.com>
Date: Tue, 18 Aug 2026 12:44:36 -0400
Subject: [PATCH] [InstCombine] Fold variable extract of a constant-strided
pointer vector
A vector built from pointers that all point into the same object at a
constant stride is a table of addresses, so a variable extract from it is
address arithmetic:
%v0 = insertelement <4 x ptr> poison, ptr %b, i64 0
%v1 = insertelement <4 x ptr> %v0, ptr %b + 8, i64 1
%v2 = insertelement <4 x ptr> %v1, ptr %b + 16, i64 2
%v3 = insertelement <4 x ptr> %v2, ptr %b + 24, i64 3
%r = extractelement <4 x ptr> %v3, i32 %i
-->
%r = getelementptr i8, ptr %b, i64 %i * 8
Fold it to a single GEP. Every element must be reachable from one base
pointer at a constant byte offset, every lane of the vector must be
defined, and the offsets must form an arithmetic sequence; otherwise the
pattern is left alone.
---
.../InstCombine/InstCombineVectorOps.cpp | 71 +++++++++
.../extractelement-strided-ptr-vector.ll | 147 ++++++++++++++++++
2 files changed, 218 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/extractelement-strided-ptr-vector.ll
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp b/llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp
index 226253a7de3f8..c88bce5859b19 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineVectorOps.cpp
@@ -410,6 +410,71 @@ static ConstantInt *getPreferredVectorIndex(ConstantInt *IndexC) {
IndexC->getValue().zextOrTrunc(64));
}
+/// Fold a variable extract from a vector of pointers that all point into the
+/// same object at a constant stride
+static Value *
+foldExtractOfStridedPointerVector(ExtractElementInst &EI,
+ InstCombiner::BuilderTy &Builder,
+ const DataLayout &DL) {
+ auto *VecTy = dyn_cast<FixedVectorType>(EI.getVectorOperandType());
+ if (!VecTy || !VecTy->getElementType()->isPointerTy())
+ return nullptr;
+
+ unsigned NumElts = VecTy->getNumElements();
+ if (NumElts < 2)
+ return nullptr;
+
+ // Walk the insertelement chain outermost-first, so the first value seen for
+ // an index is the one that survives
+ SmallVector<Value *> Elts(NumElts, nullptr);
+ unsigned NumFound = 0;
+ for (Value *V = EI.getVectorOperand(); NumFound != NumElts;) {
+ auto *IE = dyn_cast<InsertElementInst>(V);
+ if (!IE)
+ return nullptr;
+ auto *IdxC = dyn_cast<ConstantInt>(IE->getOperand(2));
+ if (!IdxC || IdxC->uge(NumElts))
+ return nullptr;
+ unsigned Idx = IdxC->getZExtValue();
+ if (!Elts[Idx]) {
+ Elts[Idx] = IE->getOperand(1);
+ ++NumFound;
+ }
+ V = IE->getOperand(0);
+ }
+
+ // Every element must be the same base pointer plus a constant byte offset
+ unsigned IdxWidth = DL.getIndexTypeSizeInBits(VecTy->getElementType());
+ Value *Base = nullptr;
+ SmallVector<APInt> Offsets;
+ for (Value *Elt : Elts) {
+ APInt Offset(IdxWidth, 0);
+ Value *EltBase =
+ Elt->stripAndAccumulateConstantOffsets(DL, Offset,
+ /*AllowNonInbounds=*/true);
+ if (!Base)
+ Base = EltBase;
+ else if (Base != EltBase)
+ return nullptr;
+ Offsets.push_back(Offset);
+ }
+
+ // The offsets must form an arithmetic sequence.
+ APInt Stride = Offsets[1] - Offsets[0];
+ for (unsigned I = 1; I != NumElts; ++I)
+ if (Offsets[I] - Offsets[0] != Stride * I)
+ return nullptr;
+
+ // Element 0 already carries Offsets[0], so index off it directly.
+ if (Stride.isZero())
+ return Elts[0];
+
+ Type *IdxTy = DL.getIndexType(VecTy->getElementType());
+ Value *Idx = Builder.CreateZExtOrTrunc(EI.getIndexOperand(), IdxTy);
+ Value *ByteOff = Builder.CreateMul(Idx, ConstantInt::get(IdxTy, Stride));
+ return Builder.CreateGEP(Builder.getInt8Ty(), Elts[0], ByteOff);
+}
+
Instruction *InstCombinerImpl::visitExtractElementInst(ExtractElementInst &EI) {
Value *SrcVec = EI.getVectorOperand();
Value *Index = EI.getIndexOperand();
@@ -431,6 +496,12 @@ Instruction *InstCombinerImpl::visitExtractElementInst(ExtractElementInst &EI) {
if (Instruction *R = FoldOpIntoSelect(EI, SI))
return R;
+ // Fold a variable index into a table of pointers into one object into
+ // address arithmetic
+ if (!isa<ConstantInt>(Index))
+ if (Value *V = foldExtractOfStridedPointerVector(EI, Builder, DL))
+ return replaceInstUsesWith(EI, V);
+
// If extracting a specified index from the vector, see if we can recursively
// find a previously computed scalar that was inserted into the vector.
auto *IndexC = dyn_cast<ConstantInt>(Index);
diff --git a/llvm/test/Transforms/InstCombine/extractelement-strided-ptr-vector.ll b/llvm/test/Transforms/InstCombine/extractelement-strided-ptr-vector.ll
new file mode 100644
index 0000000000000..519657c945994
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/extractelement-strided-ptr-vector.ll
@@ -0,0 +1,147 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -S -passes=instcombine < %s | FileCheck %s
+
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+
+; A variable extract from a table of pointers into one object at a constant
+; stride is address arithmetic, so it folds to a single GEP.
+define ptr @strided(ptr %b, i32 %i) {
+; CHECK-LABEL: define ptr @strided(
+; CHECK-SAME: ptr [[B:%.*]], i32 [[I:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = zext i32 [[I]] to i64
+; CHECK-NEXT: [[TMP2:%.*]] = shl nuw nsw i64 [[TMP1]], 3
+; CHECK-NEXT: [[R:%.*]] = getelementptr i8, ptr [[B]], i64 [[TMP2]]
+; CHECK-NEXT: ret ptr [[R]]
+;
+ %g1 = getelementptr i8, ptr %b, i64 8
+ %g2 = getelementptr i8, ptr %b, i64 16
+ %g3 = getelementptr i8, ptr %b, i64 24
+ %v0 = insertelement <4 x ptr> poison, ptr %b, i64 0
+ %v1 = insertelement <4 x ptr> %v0, ptr %g1, i64 1
+ %v2 = insertelement <4 x ptr> %v1, ptr %g2, i64 2
+ %v3 = insertelement <4 x ptr> %v2, ptr %g3, i64 3
+ %r = extractelement <4 x ptr> %v3, i32 %i
+ ret ptr %r
+}
+
+; The base element may itself sit at a non-zero offset.
+define ptr @strided_nonzero_base(ptr %b, i32 %i) {
+; CHECK-LABEL: define ptr @strided_nonzero_base(
+; CHECK-SAME: ptr [[B:%.*]], i32 [[I:%.*]]) {
+; CHECK-NEXT: [[G0:%.*]] = getelementptr i8, ptr [[B]], i64 4
+; CHECK-NEXT: [[TMP1:%.*]] = zext i32 [[I]] to i64
+; CHECK-NEXT: [[TMP2:%.*]] = shl nuw nsw i64 [[TMP1]], 2
+; CHECK-NEXT: [[R:%.*]] = getelementptr i8, ptr [[G0]], i64 [[TMP2]]
+; CHECK-NEXT: ret ptr [[R]]
+;
+ %g0 = getelementptr i8, ptr %b, i64 4
+ %g1 = getelementptr i8, ptr %b, i64 8
+ %v0 = insertelement <2 x ptr> poison, ptr %g0, i64 0
+ %v1 = insertelement <2 x ptr> %v0, ptr %g1, i64 1
+ %r = extractelement <2 x ptr> %v1, i32 %i
+ ret ptr %r
+}
+
+; Inserts need not appear in index order.
+define ptr @strided_out_of_order(ptr %b, i32 %i) {
+; CHECK-LABEL: define ptr @strided_out_of_order(
+; CHECK-SAME: ptr [[B:%.*]], i32 [[I:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = zext i32 [[I]] to i64
+; CHECK-NEXT: [[TMP2:%.*]] = shl nuw nsw i64 [[TMP1]], 3
+; CHECK-NEXT: [[R:%.*]] = getelementptr i8, ptr [[B]], i64 [[TMP2]]
+; CHECK-NEXT: ret ptr [[R]]
+;
+ %g1 = getelementptr i8, ptr %b, i64 8
+ %v0 = insertelement <2 x ptr> poison, ptr %g1, i64 1
+ %v1 = insertelement <2 x ptr> %v0, ptr %b, i64 0
+ %r = extractelement <2 x ptr> %v1, i32 %i
+ ret ptr %r
+}
+
+; All elements equal: the extract is just that pointer.
+define ptr @zero_stride(ptr %b, i32 %i) {
+; CHECK-LABEL: define ptr @zero_stride(
+; CHECK-SAME: ptr [[B:%.*]], i32 [[I:%.*]]) {
+; CHECK-NEXT: ret ptr [[B]]
+;
+ %v0 = insertelement <2 x ptr> poison, ptr %b, i64 0
+ %v1 = insertelement <2 x ptr> %v0, ptr %b, i64 1
+ %r = extractelement <2 x ptr> %v1, i32 %i
+ ret ptr %r
+}
+
+; Negative: offsets 0, 8, 12, 24 are not an arithmetic sequence.
+define ptr @irregular_stride(ptr %b, i32 %i) {
+; CHECK-LABEL: define ptr @irregular_stride(
+; CHECK-SAME: ptr [[B:%.*]], i32 [[I:%.*]]) {
+; CHECK-NEXT: [[G1:%.*]] = getelementptr i8, ptr [[B]], i64 8
+; CHECK-NEXT: [[G2:%.*]] = getelementptr i8, ptr [[B]], i64 12
+; CHECK-NEXT: [[G3:%.*]] = getelementptr i8, ptr [[B]], i64 24
+; CHECK-NEXT: [[V0:%.*]] = insertelement <4 x ptr> poison, ptr [[B]], i64 0
+; CHECK-NEXT: [[V1:%.*]] = insertelement <4 x ptr> [[V0]], ptr [[G1]], i64 1
+; CHECK-NEXT: [[V2:%.*]] = insertelement <4 x ptr> [[V1]], ptr [[G2]], i64 2
+; CHECK-NEXT: [[V3:%.*]] = insertelement <4 x ptr> [[V2]], ptr [[G3]], i64 3
+; CHECK-NEXT: [[R:%.*]] = extractelement <4 x ptr> [[V3]], i32 [[I]]
+; CHECK-NEXT: ret ptr [[R]]
+;
+ %g1 = getelementptr i8, ptr %b, i64 8
+ %g2 = getelementptr i8, ptr %b, i64 12
+ %g3 = getelementptr i8, ptr %b, i64 24
+ %v0 = insertelement <4 x ptr> poison, ptr %b, i64 0
+ %v1 = insertelement <4 x ptr> %v0, ptr %g1, i64 1
+ %v2 = insertelement <4 x ptr> %v1, ptr %g2, i64 2
+ %v3 = insertelement <4 x ptr> %v2, ptr %g3, i64 3
+ %r = extractelement <4 x ptr> %v3, i32 %i
+ ret ptr %r
+}
+
+; Negative: elements point into two unrelated objects.
+define ptr @two_bases(ptr %b, ptr %c, i32 %i) {
+; CHECK-LABEL: define ptr @two_bases(
+; CHECK-SAME: ptr [[B:%.*]], ptr [[C:%.*]], i32 [[I:%.*]]) {
+; CHECK-NEXT: [[V0:%.*]] = insertelement <2 x ptr> poison, ptr [[B]], i64 0
+; CHECK-NEXT: [[V1:%.*]] = insertelement <2 x ptr> [[V0]], ptr [[C]], i64 1
+; CHECK-NEXT: [[R:%.*]] = extractelement <2 x ptr> [[V1]], i32 [[I]]
+; CHECK-NEXT: ret ptr [[R]]
+;
+ %v0 = insertelement <2 x ptr> poison, ptr %b, i64 0
+ %v1 = insertelement <2 x ptr> %v0, ptr %c, i64 1
+ %r = extractelement <2 x ptr> %v1, i32 %i
+ ret ptr %r
+}
+
+; Negative: element 3 is never defined, so the sequence is unknown.
+define ptr @partially_defined(ptr %b, i32 %i) {
+; CHECK-LABEL: define ptr @partially_defined(
+; CHECK-SAME: ptr [[B:%.*]], i32 [[I:%.*]]) {
+; CHECK-NEXT: [[G1:%.*]] = getelementptr i8, ptr [[B]], i64 8
+; CHECK-NEXT: [[G2:%.*]] = getelementptr i8, ptr [[B]], i64 16
+; CHECK-NEXT: [[V0:%.*]] = insertelement <4 x ptr> poison, ptr [[B]], i64 0
+; CHECK-NEXT: [[V1:%.*]] = insertelement <4 x ptr> [[V0]], ptr [[G1]], i64 1
+; CHECK-NEXT: [[V2:%.*]] = insertelement <4 x ptr> [[V1]], ptr [[G2]], i64 2
+; CHECK-NEXT: [[R:%.*]] = extractelement <4 x ptr> [[V2]], i32 [[I]]
+; CHECK-NEXT: ret ptr [[R]]
+;
+ %g1 = getelementptr i8, ptr %b, i64 8
+ %g2 = getelementptr i8, ptr %b, i64 16
+ %v0 = insertelement <4 x ptr> poison, ptr %b, i64 0
+ %v1 = insertelement <4 x ptr> %v0, ptr %g1, i64 1
+ %v2 = insertelement <4 x ptr> %v1, ptr %g2, i64 2
+ %r = extractelement <4 x ptr> %v2, i32 %i
+ ret ptr %r
+}
+
+; Negative: a non-pointer vector is left to the existing folds.
+define i32 @not_pointers(i32 %a, i32 %b, i32 %i) {
+; CHECK-LABEL: define i32 @not_pointers(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]], i32 [[I:%.*]]) {
+; CHECK-NEXT: [[V0:%.*]] = insertelement <2 x i32> poison, i32 [[A]], i64 0
+; CHECK-NEXT: [[V1:%.*]] = insertelement <2 x i32> [[V0]], i32 [[B]], i64 1
+; CHECK-NEXT: [[R:%.*]] = extractelement <2 x i32> [[V1]], i32 [[I]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %v0 = insertelement <2 x i32> poison, i32 %a, i64 0
+ %v1 = insertelement <2 x i32> %v0, i32 %b, i64 1
+ %r = extractelement <2 x i32> %v1, i32 %i
+ ret i32 %r
+}
More information about the llvm-commits
mailing list