[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