[llvm] 034e5d6 - [MemCpyOpt] Extend `performMemCpyToMemSetOptzn` to partially memset'd region
Antonio Frighetto via llvm-commits
llvm-commits at lists.llvm.org
Fri Jan 30 01:10:06 PST 2026
Author: Antonio Frighetto
Date: 2026-01-30T10:09:08+01:00
New Revision: 034e5d6f8671e84c9fee5a3236bf2ec0cfa4a731
URL: https://github.com/llvm/llvm-project/commit/034e5d6f8671e84c9fee5a3236bf2ec0cfa4a731
DIFF: https://github.com/llvm/llvm-project/commit/034e5d6f8671e84c9fee5a3236bf2ec0cfa4a731.diff
LOG: [MemCpyOpt] Extend `performMemCpyToMemSetOptzn` to partially memset'd region
While doing memset-to-memcpy forwarding, take into account memset
that covers memory regions from a given offset, and the leading
bytes of such a region are undef.
Fixes: https://github.com/llvm/llvm-project/issues/172326.
Added:
Modified:
llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp
llvm/test/Transforms/MemCpyOpt/lifetime-missing.ll
llvm/test/Transforms/MemCpyOpt/memset-memcpy-oversized.ll
Removed:
################################################################################
diff --git a/llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp b/llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp
index 5de3dfa148314..0f75e53cb9998 100644
--- a/llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp
+++ b/llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp
@@ -1427,7 +1427,6 @@ static bool overreadUndefContents(MemorySSA *MSSA, MemCpyInst *MemCpy,
/// memset(dst1, c, dst1_size);
/// memset(dst2, c, dst2_size);
/// \endcode
-/// When dst2_size <= dst1_size.
bool MemCpyOptPass::performMemCpyToMemSetOptzn(MemCpyInst *MemCpy,
MemSetInst *MemSet,
BatchAAResults &BAA) {
@@ -1441,42 +1440,61 @@ bool MemCpyOptPass::performMemCpyToMemSetOptzn(MemCpyInst *MemCpy,
if (MemCpy->getSource() != MemSet->getDest()) {
std::optional<int64_t> Offset =
MemCpy->getSource()->getPointerOffsetFrom(MemSet->getDest(), DL);
- if (!Offset || *Offset < 0)
+ if (!Offset)
return false;
+ // On positive offsets, the memcpy source is at a offset into the memset'd
+ // region. On negative offsets, the copy starts at a offset prior to the
+ // previously memset'd area, namely, we memcpy from a partially initialized
+ // region.
MOffset = *Offset;
}
if (MOffset != 0 || MemSetSize != CopySize) {
// Make sure the memcpy doesn't read any more than what the memset wrote,
- // other than undef. Don't worry about sizes larger than i64.
+ // other than undef. Likewise, the memcpy should not read from an area not
+ // covered by the memset unless undef bytes. Don't worry about sizes larger
+ // than i64.
auto *CMemSetSize = dyn_cast<ConstantInt>(MemSetSize);
auto *CCopySize = dyn_cast<ConstantInt>(CopySize);
- if (!CMemSetSize || !CCopySize ||
+ if (!CMemSetSize || !CCopySize || MOffset < 0 ||
CCopySize->getZExtValue() + MOffset > CMemSetSize->getZExtValue()) {
if (!overreadUndefContents(MSSA, MemCpy, MemSet, BAA))
return false;
if (CMemSetSize && CCopySize) {
- // If both have constant sizes and offsets, clip the memcpy to the
- // bounds of the memset if applicable.
- assert(CCopySize->getZExtValue() + MOffset >
- CMemSetSize->getZExtValue());
- if (MOffset == 0)
- CopySize = MemSetSize;
- else
- CopySize =
- ConstantInt::get(CopySize->getType(),
- CMemSetSize->getZExtValue() <= (uint64_t)MOffset
- ? 0
- : CMemSetSize->getZExtValue() - MOffset);
+ uint64_t MemSetSizeVal = CMemSetSize->getZExtValue();
+ uint64_t MemCpySizeVal = CCopySize->getZExtValue();
+ uint64_t NewSize;
+
+ if (MOffset < 0) {
+ // Offset from beginning of the initialized region.
+ uint64_t Offset = -MOffset;
+ NewSize = MemCpySizeVal <= Offset ? 0 : MemCpySizeVal - Offset;
+ } else if (MOffset == 0) {
+ NewSize = MemSetSizeVal;
+ } else {
+ NewSize =
+ MemSetSizeVal <= (uint64_t)MOffset ? 0 : MemSetSizeVal - MOffset;
+ }
+ CopySize = ConstantInt::get(CopySize->getType(), NewSize);
+ } else {
+ if (MOffset < 0)
+ return false;
}
}
}
IRBuilder<> Builder(MemCpy);
+ Value *DestPtr = MemCpy->getRawDest();
+ MaybeAlign Align = MemCpy->getDestAlign();
+ if (MOffset < 0) {
+ DestPtr = Builder.CreatePtrAdd(DestPtr, Builder.getInt64(-MOffset));
+ if (Align)
+ Align = commonAlignment(*Align, -MOffset);
+ }
+
Instruction *NewM =
- Builder.CreateMemSet(MemCpy->getRawDest(), MemSet->getOperand(1),
- CopySize, MemCpy->getDestAlign());
+ Builder.CreateMemSet(DestPtr, MemSet->getOperand(1), CopySize, Align);
auto *LastDef = cast<MemoryDef>(MSSA->getMemoryAccess(MemCpy));
auto *NewAccess = MSSAU->createMemoryAccessAfter(NewM, nullptr, LastDef);
MSSAU->insertDef(cast<MemoryDef>(NewAccess), /*RenameUses=*/true);
diff --git a/llvm/test/Transforms/MemCpyOpt/lifetime-missing.ll b/llvm/test/Transforms/MemCpyOpt/lifetime-missing.ll
index 06d9434e8c5c8..35b53f787373e 100644
--- a/llvm/test/Transforms/MemCpyOpt/lifetime-missing.ll
+++ b/llvm/test/Transforms/MemCpyOpt/lifetime-missing.ll
@@ -14,9 +14,13 @@ define void @test() {
; CHECK-LABEL: define void @test() {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[AGG_TMP_SROA_14:%.*]] = alloca [20 x i8], align 4
-; CHECK-NEXT: [[AGG_TMP_SROA_14_128_SROA_IDX:%.*]] = getelementptr i8, ptr [[AGG_TMP_SROA_14]], i64 4
+; CHECK-NEXT: [[AGG_TMP_SROA_15:%.*]] = alloca [20 x i8], align 4
+; CHECK-NEXT: [[AGG_TMP_SROA_14_128_SROA_IDX:%.*]] = getelementptr i8, ptr [[AGG_TMP_SROA_15]], i64 4
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr [[AGG_TMP_SROA_14_128_SROA_IDX]], i8 0, i64 1, i1 false)
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG_TMP_SROA_14]])
; CHECK-NEXT: [[AGG_TMP3_SROA_35_128_SROA_IDX:%.*]] = getelementptr i8, ptr [[AGG_TMP_SROA_14]], i64 4
+; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr [[AGG_TMP3_SROA_35_128_SROA_IDX]], i8 0, i64 16, i1 false)
+; CHECK-NEXT: [[AGG_TMP3_SROA_35_128_SROA_IDX1:%.*]] = getelementptr i8, ptr [[AGG_TMP_SROA_14]], i64 4
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr inttoptr (i64 4 to ptr), i8 0, i64 1, i1 false)
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr null, i8 0, i64 1, i1 false)
; CHECK-NEXT: ret void
diff --git a/llvm/test/Transforms/MemCpyOpt/memset-memcpy-oversized.ll b/llvm/test/Transforms/MemCpyOpt/memset-memcpy-oversized.ll
index 1371f2e4bbd16..a9310ef5c1407 100644
--- a/llvm/test/Transforms/MemCpyOpt/memset-memcpy-oversized.ll
+++ b/llvm/test/Transforms/MemCpyOpt/memset-memcpy-oversized.ll
@@ -153,7 +153,8 @@ define void @test_negative_offset_memset(ptr %result) {
; CHECK-NEXT: [[A1:%.*]] = alloca [16 x i8], align 8
; CHECK-NEXT: [[A:%.*]] = getelementptr i8, ptr [[A1]], i32 4
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[A]], i8 0, i64 12, i1 false)
-; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[RESULT:%.*]], ptr align 8 [[A1]], i64 12, i1 false)
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[RESULT:%.*]], i64 4
+; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr [[TMP1]], i8 0, i64 8, i1 false)
; CHECK-NEXT: ret void
;
%a = alloca [ 16 x i8 ], align 8
@@ -201,7 +202,8 @@ define void @test_negative_offset_memset_2(ptr %out) {
; CHECK-NEXT: [[ALLOCA:%.*]] = alloca <{ [2 x i8], i64, i64, i64 }>, align 8
; CHECK-NEXT: [[OFFSET:%.*]] = getelementptr i8, ptr [[ALLOCA]], i64 2
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr [[OFFSET]], i8 0, i64 24, i1 false)
-; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[OUT:%.*]], ptr [[ALLOCA]], i64 26, i1 false)
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[OUT:%.*]], i64 2
+; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr [[TMP1]], i8 0, i64 24, i1 false)
; CHECK-NEXT: ret void
;
%alloca = alloca <{ [2 x i8], i64, i64, i64 }>
@@ -218,7 +220,8 @@ define void @test_negative_offset_memset_3(ptr %out) {
; CHECK-NEXT: [[ALLOCA:%.*]] = alloca [20 x i8], align 1
; CHECK-NEXT: [[OFFSET:%.*]] = getelementptr i8, ptr [[ALLOCA]], i64 10
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr [[OFFSET]], i8 0, i64 10, i1 false)
-; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[OUT:%.*]], ptr [[ALLOCA]], i64 15, i1 false)
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[OUT:%.*]], i64 10
+; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr [[TMP1]], i8 0, i64 5, i1 false)
; CHECK-NEXT: ret void
;
%alloca = alloca [20 x i8]
@@ -234,7 +237,8 @@ define void @test_negative_offset_memset_
diff erent_alignment(ptr %out) {
; CHECK-NEXT: [[ALLOCA:%.*]] = alloca [20 x i8], align 1
; CHECK-NEXT: [[OFFSET:%.*]] = getelementptr i8, ptr [[ALLOCA]], i64 4
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 1 [[OFFSET]], i8 0, i64 16, i1 false)
-; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[OUT:%.*]], ptr align 1 [[ALLOCA]], i64 20, i1 false)
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[OUT:%.*]], i64 4
+; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[TMP1]], i8 0, i64 16, i1 false)
; CHECK-NEXT: ret void
;
%alloca = alloca [20 x i8], align 1
@@ -252,7 +256,8 @@ define void @test_negative_offset_memset_and_negative_offset_memcpy_dest(ptr %ou
; CHECK-NEXT: [[OFFSET:%.*]] = getelementptr i8, ptr [[ALLOCA]], i64 10
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr [[OFFSET]], i8 0, i64 10, i1 false)
; CHECK-NEXT: [[OFFSET_OUT:%.*]] = getelementptr i8, ptr [[OUT:%.*]], i64 -10
-; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr [[OFFSET_OUT]], ptr [[ALLOCA]], i64 15, i1 false)
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[OFFSET_OUT]], i64 10
+; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr [[TMP1]], i8 0, i64 5, i1 false)
; CHECK-NEXT: ret void
;
%alloca = alloca [20 x i8]
More information about the llvm-commits
mailing list