[polly] [Polly] Narrow IV to lower type when possible (PR #212708)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 29 01:30:40 PDT 2026
https://github.com/anilkund created https://github.com/llvm/llvm-project/pull/212708
This patch tries to lower the LLVM-IR type of IVs which are set to i64 by default during polly codegen. This is specially beneficial for Hexagon as it uses Hardware Loops which requires 32 bit wide IVs.
>From 5861ebf99fb76e44e42228c1c16c46f37e297868 Mon Sep 17 00:00:00 2001
From: Anilava Kundu <anilkund at hu-anilkund-blr.qualcomm.com>
Date: Wed, 29 Jul 2026 13:54:48 +0530
Subject: [PATCH] [Polly] Narrow IV to lower type when possible
This patch tries to lower the LLVM-IR type of IVs which are
set to i64 by default during polly codegen. This is specially beneficial
for Hexagon as it uses Hardware Loops which requires 32 bit wide IVs.
---
polly/lib/CodeGen/IslNodeBuilder.cpp | 64 +++++++++++++---
polly/test/CodeGen/iv_narrow_32bit_target.ll | 77 ++++++++++++++++++++
2 files changed, 129 insertions(+), 12 deletions(-)
create mode 100644 polly/test/CodeGen/iv_narrow_32bit_target.ll
diff --git a/polly/lib/CodeGen/IslNodeBuilder.cpp b/polly/lib/CodeGen/IslNodeBuilder.cpp
index 93822e5c61615..debd385e1205a 100644
--- a/polly/lib/CodeGen/IslNodeBuilder.cpp
+++ b/polly/lib/CodeGen/IslNodeBuilder.cpp
@@ -464,6 +464,25 @@ static bool hasLoopCarriedDependence(isl::ast_node_for For, const Scop &S) {
return false;
}
+static Value *adjustToType(IRBuilderBase &Builder, Value *V, Type *Ty) {
+ if (V->getType() == Ty)
+ return V;
+ if (V->getType()->getIntegerBitWidth() < Ty->getIntegerBitWidth())
+ return Builder.CreateSExt(V, Ty);
+ return Builder.CreateTrunc(V, Ty);
+}
+
+// Returns true when V is known to fit in IntPtrTy without data loss.
+// Accepts i64 constants such as 0 and 1 that ISL materialises as i64 even on
+// 32-bit targets.
+static bool fitsInPtrTy(Value *V, IntegerType *IntPtrTy) {
+ if (V->getType()->getIntegerBitWidth() <= IntPtrTy->getBitWidth())
+ return true;
+ if (auto *CI = dyn_cast<ConstantInt>(V))
+ return CI->getValue().isSignedIntN(IntPtrTy->getBitWidth());
+ return false;
+}
+
void IslNodeBuilder::createForSequential(isl::ast_node_for For,
bool MarkParallel) {
Value *ValueLB, *ValueUB, *ValueInc;
@@ -497,12 +516,23 @@ void IslNodeBuilder::createForSequential(isl::ast_node_for For,
MaxType = ExprBuilder.getWidestType(MaxType, ValueUB->getType());
MaxType = ExprBuilder.getWidestType(MaxType, ValueInc->getType());
- if (MaxType != ValueLB->getType())
- ValueLB = Builder.CreateSExt(ValueLB, MaxType);
- if (MaxType != ValueUB->getType())
- ValueUB = Builder.CreateSExt(ValueUB, MaxType);
- if (MaxType != ValueInc->getType())
- ValueInc = Builder.CreateSExt(ValueInc, MaxType);
+ // Narrow the IV type to pointer size when all three bounds are known to fit.
+ // On 32-bit targets (e.g. Hexagon) this avoids i64 IVs and the truncations
+ // they cause in loop bodies. This also allows Hexagon to represent loops as
+ // Hardware loops. ISL materializes constants (e.g. LB=0, Inc=1)
+ // as i64 even when they fit in i32, so we accept those via isSignedIntN.
+ // Non-constant variables with a type wider than PtrBits are left unchanged
+ // to avoid an unsafe truncation.
+ IntegerType *IntPtrTy = Builder.getIntPtrTy(DL);
+ if (MaxType->getIntegerBitWidth() > IntPtrTy->getBitWidth() &&
+ fitsInPtrTy(ValueLB, IntPtrTy) && fitsInPtrTy(ValueUB, IntPtrTy) &&
+ fitsInPtrTy(ValueInc, IntPtrTy))
+ MaxType = IntPtrTy;
+
+ // Coerce each bound to MaxType, using trunc when MaxType was narrowed.
+ ValueLB = adjustToType(Builder, ValueLB, MaxType);
+ ValueUB = adjustToType(Builder, ValueUB, MaxType);
+ ValueInc = adjustToType(Builder, ValueInc, MaxType);
// If we can show that LB <Predicate> UB holds at least once, we can
// omit the GuardBB in front of the loop.
@@ -577,12 +607,22 @@ void IslNodeBuilder::createForParallel(__isl_take isl_ast_node *For) {
MaxType = ExprBuilder.getWidestType(MaxType, ValueUB->getType());
MaxType = ExprBuilder.getWidestType(MaxType, ValueInc->getType());
- if (MaxType != ValueLB->getType())
- ValueLB = Builder.CreateSExt(ValueLB, MaxType);
- if (MaxType != ValueUB->getType())
- ValueUB = Builder.CreateSExt(ValueUB, MaxType);
- if (MaxType != ValueInc->getType())
- ValueInc = Builder.CreateSExt(ValueInc, MaxType);
+ // Narrow the IV type to pointer size when all three bounds are known to fit.
+ // On 32-bit targets (e.g. Hexagon) this avoids i64 IVs and the truncations
+ // they cause in loop bodies. ISL materializes constants (e.g. LB=0, Inc=1)
+ // as i64 even when they fit in i32, so we accept those via isSignedIntN.
+ // Non-constant variables with a type wider than PtrBits are left unchanged
+ // to avoid an unsafe truncation.
+ IntegerType *IntPtrTy = Builder.getIntPtrTy(DL);
+ if (MaxType->getIntegerBitWidth() > IntPtrTy->getBitWidth() &&
+ fitsInPtrTy(ValueLB, IntPtrTy) && fitsInPtrTy(ValueUB, IntPtrTy) &&
+ fitsInPtrTy(ValueInc, IntPtrTy))
+ MaxType = IntPtrTy;
+
+ // Coerce each bound to MaxType, using trunc when MaxType was narrowed.
+ ValueLB = adjustToType(Builder, ValueLB, MaxType);
+ ValueUB = adjustToType(Builder, ValueUB, MaxType);
+ ValueInc = adjustToType(Builder, ValueInc, MaxType);
BasicBlock::iterator LoopBody;
diff --git a/polly/test/CodeGen/iv_narrow_32bit_target.ll b/polly/test/CodeGen/iv_narrow_32bit_target.ll
new file mode 100644
index 0000000000000..25401bd80aa1e
--- /dev/null
+++ b/polly/test/CodeGen/iv_narrow_32bit_target.ll
@@ -0,0 +1,77 @@
+; RUN: opt %loadNPMPolly '-passes=polly<no-default-opts>' -S < %s | FileCheck %s
+
+; Verify that Polly narrows the loop induction variable to i32 on a 32-bit
+; target (e.g. Hexagon) when all loop bounds fit in 32 bits, and that it does
+; NOT narrow when the lower bound is a wide non-constant i64 variable.
+;
+; void narrow(int *A, int n) {
+; for (int i = 0; i < n; i++)
+; A[i] = i;
+; }
+
+; 32-bit Hexagon-like target: pointer size = 32 bits.
+target datalayout = "e-m:e-p:32:32:32-i64:64:64-i128:128:128-n32-S128"
+target triple = "hexagon-unknown-linux-musl"
+
+; CHECK-LABEL: @narrow
+define void @narrow(ptr noalias %A, i32 %n) {
+entry:
+ br label %for.header
+
+for.header:
+ %i = phi i32 [ 0, %entry ], [ %i.next, %for.body ]
+ %exitcond = icmp slt i32 %i, %n
+ br i1 %exitcond, label %for.body, label %exit
+
+for.body:
+ %gep = getelementptr inbounds i32, ptr %A, i32 %i
+ store i32 %i, ptr %gep
+ %i.next = add nsw i32 %i, 1
+ br label %for.header
+
+exit:
+ ret void
+}
+
+; The IV must be i32 and all loop arithmetic must stay in i32.
+; CHECK: polly.loop_header:
+; CHECK-NEXT: %polly.indvar = phi i32
+; CHECK: %polly.indvar_next = add nsw i32 %polly.indvar
+; CHECK: %polly.loop_cond = icmp slt i32 %polly.indvar_next
+
+
+; When the lower bound is a non-constant i64 variable the guard must block
+; narrowing to avoid an unsafe truncation of %start.
+;
+; void no_narrow_wide_lb(int *A, long start, int n) {
+; for (long i = start; i < n; i++)
+; A[i] = (int)i;
+; }
+
+; CHECK-LABEL: @no_narrow_wide_lb
+define void @no_narrow_wide_lb(ptr noalias %A, i64 %start, i32 %n) {
+entry:
+ br label %for.header
+
+for.header:
+ %i = phi i64 [ %start, %entry ], [ %i.next, %for.body ]
+ %n64 = sext i32 %n to i64
+ %exitcond = icmp slt i64 %i, %n64
+ br i1 %exitcond, label %for.body, label %exit
+
+for.body:
+ %gep = getelementptr inbounds i32, ptr %A, i64 %i
+ %ival = trunc i64 %i to i32
+ store i32 %ival, ptr %gep
+ %i.next = add nsw i64 %i, 1
+ br label %for.header
+
+exit:
+ ret void
+}
+
+; The IV must remain i64 because %start is a wide non-constant variable.
+; Polly normalises the loop to start from 0, but keeps the type as i64.
+; CHECK: polly.loop_header:
+; CHECK-NEXT: %polly.indvar = phi i64
+; CHECK: %polly.indvar_next = add nsw i64 %polly.indvar
More information about the llvm-commits
mailing list