[llvm] [Hexagon] Do not promote unaligned loop access to memmove. (PR #205358)

via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 23 07:35:54 PDT 2026


https://github.com/anilkund created https://github.com/llvm/llvm-project/pull/205358

If source and destination both can never be aligned to 8-byte boundary then for hexagon target disable the promotion from loop access to memmove.

Co-authored-by: Awanish Pandey <awanpand at qti.qualcomm.com>


>From 50cce66ae301228bd4e1ecfb5e8b84be24112e16 Mon Sep 17 00:00:00 2001
From: Anilava Kundu <anilkund at hu-anilkund-blr.qualcomm.com>
Date: Tue, 23 Jun 2026 20:03:45 +0530
Subject: [PATCH] [Hexagon] Do not promote unaligned loop access to memmove.

If source and destination both can never be aligned to 8 byte boundary
then for hexagon target disable the promotion from loop access to memmove.

Co-authored-by: Awanish Pandey <awanpand at qti.qualcomm.com>
---
 .../llvm/Analysis/TargetTransformInfo.h       |   5 +
 .../llvm/Analysis/TargetTransformInfoImpl.h   |   5 +
 llvm/lib/Analysis/TargetTransformInfo.cpp     |   5 +
 .../Hexagon/HexagonTargetTransformInfo.cpp    |  39 ++++++
 .../Hexagon/HexagonTargetTransformInfo.h      |   4 +
 .../Transforms/Scalar/LoopIdiomRecognize.cpp  |  15 +++
 .../Hexagon/loop-idiom/disabled-memmove.ll    | 116 ++++++++++++++++++
 .../isMemmoveProfitable-different-ivs.ll      |  34 +++++
 8 files changed, 223 insertions(+)
 create mode 100644 llvm/test/CodeGen/Hexagon/loop-idiom/disabled-memmove.ll
 create mode 100644 llvm/test/CodeGen/Hexagon/loop-idiom/isMemmoveProfitable-different-ivs.ll

diff --git a/llvm/include/llvm/Analysis/TargetTransformInfo.h b/llvm/include/llvm/Analysis/TargetTransformInfo.h
index aaf5ed6de93b6..f16ab3f43d03e 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfo.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfo.h
@@ -499,6 +499,11 @@ class TargetTransformInfo {
   /// minimal.
   LLVM_ABI InstructionCost getBranchMispredictPenalty() const;
 
+  /// \return true if promoting loop access to memmove is profitable for
+  /// the architecture.
+  LLVM_ABI bool isMemmoveProfitable(Value *DestPtr, Value *SourcePtr,
+                                    ScalarEvolution *SE) const;
+
   /// Return true if branch divergence exists.
   ///
   /// Branch divergence has a significantly negative impact on GPU performance
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
index 3db7729df2476..43ddf5154b1ef 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
@@ -132,6 +132,11 @@ class LLVM_ABI TargetTransformInfoImplBase {
     return false;
   }
 
+  virtual bool isMemmoveProfitable(Value *DestPtr, Value *SourcePtr,
+                                   ScalarEvolution *SE) const {
+    return true;
+  }
+
   virtual ValueUniformity getValueUniformity(const Value *V) const {
     return ValueUniformity::Default;
   }
diff --git a/llvm/lib/Analysis/TargetTransformInfo.cpp b/llvm/lib/Analysis/TargetTransformInfo.cpp
index 8e0c30f26e975..4b3a3298c662e 100644
--- a/llvm/lib/Analysis/TargetTransformInfo.cpp
+++ b/llvm/lib/Analysis/TargetTransformInfo.cpp
@@ -1254,6 +1254,11 @@ InstructionCost TargetTransformInfo::getMemoryOpCost(
   return Cost;
 }
 
+bool TargetTransformInfo::isMemmoveProfitable(Value *DestPtr, Value *SourcePtr,
+                                              ScalarEvolution *SE) const {
+  return TTIImpl->isMemmoveProfitable(DestPtr, SourcePtr, SE);
+}
+
 InstructionCost TargetTransformInfo::getInterleavedMemoryOpCost(
     unsigned Opcode, Type *VecTy, unsigned Factor, ArrayRef<unsigned> Indices,
     Align Alignment, unsigned AddressSpace, TTI::TargetCostKind CostKind,
diff --git a/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.cpp b/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.cpp
index 1d9d2856e16c6..c88b31b8c61bf 100644
--- a/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.cpp
@@ -46,6 +46,10 @@ static cl::opt<bool> EmitLookupTables("hexagon-emit-lookup-tables",
 static cl::opt<bool> HexagonMaskedVMem("hexagon-masked-vmem", cl::init(true),
     cl::Hidden, cl::desc("Enable masked loads/stores for HVX"));
 
+static cl::opt<bool> HexagonEnableMemmove(
+    "hexagon-enable-memmove", cl::init(false), cl::Hidden,
+    cl::desc("Always promote safe loop access to memmove"));
+
 // Constant "cost factor" to make floating point operations more expensive
 // in terms of vectorization cost. This isn't the best way, but it should
 // do. Ultimately, the cost should use cycles.
@@ -229,6 +233,41 @@ InstructionCost HexagonTTIImpl::getMemoryOpCost(unsigned Opcode, Type *Src,
                                 OpInfo, I);
 }
 
+/* Do not promote loop access to memmove if source or destination
+ * array cannot be aligned to 8 byte boundary. memmove is beneficial
+ * only if source and destination are 8 byte aligned or memmove
+ * supports unaligned accesses. For example
+ * char a[100] ;
+ * for(i=n; i> 0; i--)
+ *     a[i] = a[i-1];
+ * We can never align source and destination simultaneously at
+ * 8 byte boundary. If we convert this loop to memmove then
+ * memmove will do alignment checks and it fails so eventually
+ * do copy byte by byte. As a result, memmove will be relatively
+ * slow (due to call overhead and checks inside memmove)
+ * as compared to the loop access for unaligned accesses.*/
+
+bool HexagonTTIImpl::isMemmoveProfitable(Value *DestPtr, Value *SourcePtr,
+                                         ScalarEvolution *SE) const {
+  if (HexagonEnableMemmove)
+    return true;
+
+  // memmove is only efficient on Hexagon when src and dst can be simultaneously
+  // 8-byte aligned. Use SE to compute the constant byte difference between the
+  // two pointers. If it is not a multiple of 8, they can never be
+  // simultaneously aligned — keep the original loop instead.
+
+  std::optional<APInt> Diff = SE->computeConstantDifference(
+      SE->getSCEV(DestPtr), SE->getSCEV(SourcePtr));
+
+  // Conservatively return false if we can't reason the difference between the
+  // pointers
+  if (!Diff)
+    return false;
+
+  return Diff->getSExtValue() % 8 == 0;
+}
+
 InstructionCost
 HexagonTTIImpl::getShuffleCost(TTI::ShuffleKind Kind, VectorType *DstTy,
                                VectorType *SrcTy, ArrayRef<int> Mask,
diff --git a/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.h b/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.h
index 86b3195222bdb..baffdbe9b357f 100644
--- a/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.h
+++ b/llvm/lib/Target/Hexagon/HexagonTargetTransformInfo.h
@@ -108,6 +108,10 @@ class HexagonTTIImpl final : public BasicTTIImplBase<HexagonTTIImpl> {
   InstructionCost getCallInstrCost(Function *F, Type *RetTy,
                                    ArrayRef<Type *> Tys,
                                    TTI::TargetCostKind CostKind) const override;
+
+  bool isMemmoveProfitable(Value *DestPtr, Value *SourcePtr,
+                           ScalarEvolution *SE) const override;
+
   InstructionCost
   getIntrinsicInstrCost(const IntrinsicCostAttributes &ICA,
                         TTI::TargetCostKind CostKind) const override;
diff --git a/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp b/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
index a285b965c9239..a095cffde5c45 100644
--- a/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
@@ -1376,7 +1376,22 @@ bool LoopIdiomRecognize::processLoopStoreOfLoopLoad(
     // the only user of TheLoad.
     if (!TheLoad->hasOneUse())
       return Changed;
+
     IgnoredInsts.insert(TheLoad);
+
+    if (!TTI->isMemmoveProfitable(DestPtr, SourcePtr, SE)) {
+      ORE.emit([&]() {
+        return OptimizationRemarkMissed(DEBUG_TYPE,
+                                        "LoopMayAccessUnalignedStore", TheStore)
+               << ore::NV("Inst", InstRemark) << " in "
+               << ore::NV("Function", TheStore->getFunction())
+               << " function will not be hoisted: "
+               << ore::NV("Reason",
+                          "The conversion is not profitable for the target");
+      });
+      return Changed;
+    }
+
     if (mayLoopAccessLocation(StoreBasePtr, ModRefInfo::ModRef, CurLoop,
                               BECount, StoreSizeSCEV, *AA, IgnoredInsts)) {
       ORE.emit([&]() {
diff --git a/llvm/test/CodeGen/Hexagon/loop-idiom/disabled-memmove.ll b/llvm/test/CodeGen/Hexagon/loop-idiom/disabled-memmove.ll
new file mode 100644
index 0000000000000..3840e59ffafbf
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/loop-idiom/disabled-memmove.ll
@@ -0,0 +1,116 @@
+; RUN: opt -passes='mem2reg,loop-simplify,lcssa,loop(loop-rotate,loop-idiom)' \
+; RUN:   -mtriple=hexagon-unknown-unknown -S < %s | FileCheck %s
+
+; C Code for generating the input file
+;void foo(int n){
+;  int i;
+;  char a[100];
+;  int b[100];
+;
+;  for(i=n; i> 8; i--)
+;    a[i] = a[i-8]; // should promote to memmove
+;  for(i=n; i> 1; i--)
+;    b[i] = b[i-2]; // should promote to memmove
+;
+;  for(i=n; i> 0; i--)
+;    a[i] = a[i-1]; // unaligned load or store, should not promote to memmove
+;
+;}
+
+; CHECK-LABEL: @foo(
+; CHECK:       call {{.*}}memmove
+; CHECK:       call {{.*}}memmove
+; CHECK-NOT:   call {{.*}}memmove
+; CHECK:       getelementptr {{.*}} %a
+; CHECK:       load i8
+; CHECK:       getelementptr {{.*}} %a
+; CHECK:       store i8
+target datalayout = "e-m:e-p:32:32:32-a:0-n16:32-i64:64:64-i32:32:32-i16:16:16-i1:8:8-f32:32:32-f64:64:64-v32:32:32-v64:64:64-v512:512:512-v1024:1024:1024-v2048:2048:2048"
+target triple = "hexagon-unknown-unknown-elf"
+
+define dso_local void @foo(i32 %n)  {
+entry:
+  %n.addr = alloca i32, align 4
+  %i = alloca i32, align 4
+  %a = alloca [100 x i8], align 8
+  %b = alloca [100 x i32], align 8
+  store i32 %n, i32* %n.addr, align 4
+  %0 = load i32, i32* %n.addr, align 4
+  store i32 %0, i32* %i, align 4
+  br label %for.cond
+
+for.cond:                                         ; preds = %for.inc, %entry
+  %1 = load i32, i32* %i, align 4
+  %cmp = icmp sgt i32 %1, 8
+  br i1 %cmp, label %for.body, label %for.end
+
+for.body:                                         ; preds = %for.cond
+  %2 = load i32, i32* %i, align 4
+  %sub = sub nsw i32 %2, 8
+  %arrayidx = getelementptr inbounds [100 x i8], [100 x i8]* %a, i32 0, i32 %sub
+  %3 = load i8, i8* %arrayidx, align 1
+  %4 = load i32, i32* %i, align 4
+  %arrayidx1 = getelementptr inbounds [100 x i8], [100 x i8]* %a, i32 0, i32 %4
+  store i8 %3, i8* %arrayidx1, align 1
+  br label %for.inc
+
+for.inc:                                          ; preds = %for.body
+  %5 = load i32, i32* %i, align 4
+  %dec = add nsw i32 %5, -1
+  store i32 %dec, i32* %i, align 4
+  br label %for.cond
+
+for.end:                                          ; preds = %for.cond
+  %6 = load i32, i32* %n.addr, align 4
+  store i32 %6, i32* %i, align 4
+  br label %for.cond2
+for.cond2:                                        ; preds = %for.inc8, %for.end
+  %7 = load i32, i32* %i, align 4
+  %cmp3 = icmp sgt i32 %7, 1
+  br i1 %cmp3, label %for.body4, label %for.end10
+
+for.body4:                                        ; preds = %for.cond2
+  %8 = load i32, i32* %i, align 4
+  %sub5 = sub nsw i32 %8, 2
+  %arrayidx6 = getelementptr inbounds [100 x i32], [100 x i32]* %b, i32 0, i32 %sub5
+  %9 = load i32, i32* %arrayidx6, align 4
+  %10 = load i32, i32* %i, align 4
+  %arrayidx7 = getelementptr inbounds [100 x i32], [100 x i32]* %b, i32 0, i32 %10
+  store i32 %9, i32* %arrayidx7, align 4
+  br label %for.inc8
+
+for.inc8:                                         ; preds = %for.body4
+  %11 = load i32, i32* %i, align 4
+  %dec9 = add nsw i32 %11, -1
+  store i32 %dec9, i32* %i, align 4
+  br label %for.cond2
+
+for.end10:                                        ; preds = %for.cond2
+  %12 = load i32, i32* %n.addr, align 4
+  store i32 %12, i32* %i, align 4
+  br label %for.cond11
+
+for.cond11:                                       ; preds = %for.inc17, %for.end10
+  %13 = load i32, i32* %i, align 4
+  %cmp12 = icmp sgt i32 %13, 0
+  br i1 %cmp12, label %for.body13, label %for.end19
+
+for.body13:                                       ; preds = %for.cond11
+  %14 = load i32, i32* %i, align 4
+  %sub14 = sub nsw i32 %14, 1
+  %arrayidx15 = getelementptr inbounds [100 x i8], [100 x i8]* %a, i32 0, i32 %sub14
+  %15 = load i8, i8* %arrayidx15, align 1
+  %16 = load i32, i32* %i, align 4
+  %arrayidx16 = getelementptr inbounds [100 x i8], [100 x i8]* %a, i32 0, i32 %16
+  store i8 %15, i8* %arrayidx16, align 1
+  br label %for.inc17
+
+for.inc17:                                        ; preds = %for.body13
+  %17 = load i32, i32* %i, align 4
+  %dec18 = add nsw i32 %17, -1
+  store i32 %dec18, i32* %i, align 4
+  br label %for.cond11
+
+for.end19:                                        ; preds = %for.cond11
+  ret void
+}
diff --git a/llvm/test/CodeGen/Hexagon/loop-idiom/isMemmoveProfitable-different-ivs.ll b/llvm/test/CodeGen/Hexagon/loop-idiom/isMemmoveProfitable-different-ivs.ll
new file mode 100644
index 0000000000000..02a4ac2731e31
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/loop-idiom/isMemmoveProfitable-different-ivs.ll
@@ -0,0 +1,34 @@
+; RUN: opt -passes='loop(loop-idiom),dce,loop(loop-deletion)' -S -mtriple=hexagon-unknown-elf < %s 2>&1 | FileCheck %s
+;
+; Test to check for profitable memmove replacement for different ivs (8 byte aligned) in Hexagon
+;
+; CHECK: @llvm.memmove
+
+define void @test_different_ivs(ptr %a, i32 %n) {
+entry:
+  %guard = icmp sgt i32 %n, 0
+  br i1 %guard, label %loop.ph, label %exit
+
+loop.ph:
+  br label %loop
+
+loop:
+  ; dst index starts at 0 (lower address), src index starts at 8 (higher).
+  ; Both step +1 each iteration, keeping a constant 8-byte separation.
+  %i = phi i32 [ 0, %loop.ph ], [ %i.next, %loop ]
+  %j = phi i32 [ 8, %loop.ph ], [ %j.next, %loop ]
+
+  %dst.ptr = getelementptr inbounds i8, ptr %a, i32 %i
+  %src.ptr = getelementptr inbounds i8, ptr %a, i32 %j
+
+  %val = load i8, ptr %src.ptr, align 1
+  store i8 %val, ptr %dst.ptr, align 1
+
+  %i.next = add nuw nsw i32 %i, 1
+  %j.next = add nuw nsw i32 %j, 1
+  %cmp = icmp slt i32 %i.next, %n
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret void
+}



More information about the llvm-commits mailing list