[polly] [Polly] Narrow IV to lower type when possible (PR #212708)

via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 29 01:31:19 PDT 2026


https://github.com/anilkund updated https://github.com/llvm/llvm-project/pull/212708

>From b9e49d7757aef4bb3b37eafc094c91e3c4f02d35 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