[llvm] [InstCombine] Fold length-one memset with variable fillFold length-one memset with variable fill (PR #217224)

via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 19 02:51:26 PDT 2026


https://github.com/hsnaveen2u updated https://github.com/llvm/llvm-project/pull/217224

>From b25c7315070d183ce8486fc7b2586772ee3f6547 Mon Sep 17 00:00:00 2001
From: Naveen <naveen.siddegowda at oss.qualcomm.com>
Date: Fri, 31 Jul 2026 03:01:29 -0700
Subject: [PATCH 1/3] Fold length-one memset with variable fill

A one-byte memset does not require replicating the fill byte into a
wider integer value. Allow a nonconstant i8 fill value to be stored
directly when the memset length is one.

Keep the existing constant-fill handling for lengths 1, 2, 4 and 8.
Preserve volatility and unordered atomic ordering on the generated
store.

Allow volatile AnyMemSetInst operations to reach SimplifyAnyMemSet
while continuing to block other volatile memory-intrinsic
transformations.

This is the InstCombine prerequisite for #213027.

Assisted-by: GPT-5

Signed-off-by: Naveen <naveen.siddegowda at oss.qualcomm.com>
---
 .../InstCombine/InstCombineCalls.cpp          | 49 +++++++++++----
 .../InstCombine/memset-variable-fill.ll       | 59 +++++++++++++++++++
 2 files changed, 97 insertions(+), 11 deletions(-)
 create mode 100644 llvm/test/Transforms/InstCombine/memset-variable-fill.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index c41af564d29de..4c75203cf3ee5 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -221,6 +221,24 @@ Instruction *InstCombinerImpl::SimplifyAnyMemTransfer(AnyMemTransferInst *MI) {
 }
 
 Instruction *InstCombinerImpl::SimplifyAnyMemSet(AnyMemSetInst *MI) {
+  ConstantInt *LenC = dyn_cast<ConstantInt>(MI->getLength());
+  Value *Fill = MI->getValue();
+
+  // Keep volatile memset scalarization limited to the single-byte case
+  // where the replacement is exactly one volatile byte store.
+  if (MI->isVolatile()) {
+    if (!LenC || !LenC->isOne() || !Fill->getType()->isIntegerTy(8))
+      return nullptr;
+
+    StoreInst *S = Builder.CreateStore(Fill, MI->getDest(), true);
+    S->copyMetadata(*MI, LLVMContext::MD_DIAssignID);
+    S->setAlignment(MI->getDestAlign().valueOrOne());
+
+    // Set the size of the copy to 0 and will be deleted on the next iteration.
+    MI->setLength((uint64_t)0);
+    return MI;
+  }
+
   const Align KnownAlignment =
       getKnownAlignment(MI->getDest(), DL, MI, &AC, &DT);
   MaybeAlign MemSetAlign = MI->getDestAlign();
@@ -247,10 +265,8 @@ Instruction *InstCombinerImpl::SimplifyAnyMemSet(AnyMemSetInst *MI) {
     return MI;
   }
 
-  // Extract the length and alignment and fill if they are constant.
-  ConstantInt *LenC = dyn_cast<ConstantInt>(MI->getLength());
-  ConstantInt *FillC = dyn_cast<ConstantInt>(MI->getValue());
-  if (!LenC || !FillC || !FillC->getType()->isIntegerTy(8))
+  // Extract the length and validate the fill type.
+  if (!LenC || !Fill->getType()->isIntegerTy(8))
     return nullptr;
   const uint64_t Len = LenC->getLimitedValue();
   assert(Len && "0-sized memory setting should be removed already.");
@@ -267,14 +283,22 @@ Instruction *InstCombinerImpl::SimplifyAnyMemSet(AnyMemSetInst *MI) {
   if (Len <= 8 && isPowerOf2_32((uint32_t)Len)) {
     Value *Dest = MI->getDest();
 
-    // Extract the fill value and store.
-    Constant *FillVal = ConstantInt::get(
-        MI->getContext(), APInt::getSplat(Len * 8, FillC->getValue()));
+    // Extract the fill value and store.  A one-byte memset does not need
+    // replication so a nonconstant i8 fill can be stored directly.
+    Value *FillVal;
+    if (auto *FillC = dyn_cast<ConstantInt>(Fill))
+      FillVal = ConstantInt::get(MI->getContext(),
+                                 APInt::getSplat(Len * 8, FillC->getValue()));
+    else if (Len == 1)
+      FillVal = Fill;
+    else
+      return nullptr;
+
     StoreInst *S = Builder.CreateStore(FillVal, Dest, MI->isVolatile());
     S->copyMetadata(*MI, LLVMContext::MD_DIAssignID);
     for (DbgVariableRecord *DbgAssign : at::getDVRAssignmentMarkers(S)) {
-      if (llvm::is_contained(DbgAssign->location_ops(), FillC))
-        DbgAssign->replaceVariableLocationOp(FillC, FillVal);
+      if (llvm::is_contained(DbgAssign->location_ops(), Fill))
+        DbgAssign->replaceVariableLocationOp(Fill, FillVal);
     }
 
     S->setAlignment(Alignment);
@@ -2029,8 +2053,9 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
       }
     }
 
-    // No other transformations apply to volatile transfers.
-    if (MI->isVolatile())
+    // Apart from memset-to-store scalarization below no other transformations
+    // apply to volatile transfers.
+    if (MI->isVolatile() && !isa<AnyMemSetInst>(MI))
       return nullptr;
 
     if (AnyMemTransferInst *MTI = dyn_cast<AnyMemTransferInst>(MI)) {
@@ -2055,6 +2080,8 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
     } else if (auto *MSI = dyn_cast<AnyMemSetInst>(MI)) {
       if (Instruction *I = SimplifyAnyMemSet(MSI))
         return I;
+      if (MI->isVolatile())
+        return nullptr;
     }
 
     // If src/dest is null, this memory intrinsic must be a noop.
diff --git a/llvm/test/Transforms/InstCombine/memset-variable-fill.ll b/llvm/test/Transforms/InstCombine/memset-variable-fill.ll
new file mode 100644
index 0000000000000..b5547071a05d5
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/memset-variable-fill.ll
@@ -0,0 +1,59 @@
+; RUN: opt -passes=instcombine -S < %s | FileCheck %s
+
+declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg)
+declare void @llvm.memset.p1.i64(ptr addrspace(1) nocapture writeonly, i8, i64, i1 immarg)
+declare void @llvm.memset.element.unordered.atomic.p0.i64(ptr nocapture writeonly, i8, i64, i32 immarg)
+
+define void @variable_fill_len1(ptr %dst, i8 %value) {
+; CHECK-LABEL: define void @variable_fill_len1(
+; CHECK-SAME: ptr [[DST:%.*]], i8 [[VALUE:%.*]]) {
+; CHECK-NEXT:    store i8 [[VALUE]], ptr [[DST]], align 1
+; CHECK-NEXT:    ret void
+  call void @llvm.memset.p0.i64(ptr align 1 %dst, i8 %value, i64 1, i1 false)
+  ret void
+}
+
+define void @variable_fill_len1_volatile(ptr %dst, i8 %value) {
+; CHECK-LABEL: define void @variable_fill_len1_volatile(
+; CHECK-SAME: ptr [[DST:%.*]], i8 [[VALUE:%.*]]) {
+; CHECK-NEXT:    store volatile i8 [[VALUE]], ptr [[DST]], align 1
+; CHECK-NEXT:    ret void
+  call void @llvm.memset.p0.i64(ptr align 1 %dst, i8 %value, i64 1, i1 true)
+  ret void
+}
+
+define void @variable_fill_len1_align8(ptr %dst, i8 %value) {
+; CHECK-LABEL: define void @variable_fill_len1_align8(
+; CHECK-SAME: ptr [[DST:%.*]], i8 [[VALUE:%.*]]) {
+; CHECK-NEXT:    store i8 [[VALUE]], ptr [[DST]], align 8
+; CHECK-NEXT:    ret void
+  call void @llvm.memset.p0.i64(ptr align 8 %dst, i8 %value, i64 1, i1 false)
+  ret void
+}
+
+define void @variable_fill_len1_addrspace(ptr addrspace(1) %dst, i8 %value) {
+; CHECK-LABEL: define void @variable_fill_len1_addrspace(
+; CHECK-SAME: ptr addrspace(1) [[DST:%.*]], i8 [[VALUE:%.*]]) {
+; CHECK-NEXT:    store i8 [[VALUE]], ptr addrspace(1) [[DST]], align 1
+; CHECK-NEXT:    ret void
+  call void @llvm.memset.p1.i64(ptr addrspace(1) align 1 %dst, i8 %value, i64 1, i1 false)
+  ret void
+}
+
+define void @variable_fill_len1_atomic(ptr %dst, i8 %value) {
+; CHECK-LABEL: define void @variable_fill_len1_atomic(
+; CHECK-SAME: ptr [[DST:%.*]], i8 [[VALUE:%.*]]) {
+; CHECK-NEXT:    store atomic i8 [[VALUE]], ptr [[DST]] unordered, align 1
+; CHECK-NEXT:    ret void
+  call void @llvm.memset.element.unordered.atomic.p0.i64(ptr align 1 %dst, i8 %value, i64 1, i32 1)
+  ret void
+}
+
+define void @variable_fill_len2(ptr %dst, i8 %value) {
+; CHECK-LABEL: define void @variable_fill_len2(
+; CHECK-SAME: ptr [[DST:%.*]], i8 [[VALUE:%.*]]) {
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr {{.*}}[[DST]], i8 [[VALUE]], i64 2, i1 false)
+; CHECK-NEXT:    ret void
+  call void @llvm.memset.p0.i64(ptr align 1 %dst, i8 %value, i64 2, i1 false)
+  ret void
+}

>From 9c22f0f11eb619f736969639548aecb6a0da5719 Mon Sep 17 00:00:00 2001
From: Naveen <naveen.siddegowda at oss.qualcomm.com>
Date: Wed, 19 Aug 2026 01:34:08 -0700
Subject: [PATCH 2/3] fixup! Fold length-one memset with variable fill

---
 .../Transforms/InstCombine/InstCombineCalls.cpp  | 16 ++--------------
 .../InstCombine/memset-variable-fill.ll          | 11 +++++++++--
 2 files changed, 11 insertions(+), 16 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 4c75203cf3ee5..2d5bfa065366f 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -224,20 +224,8 @@ Instruction *InstCombinerImpl::SimplifyAnyMemSet(AnyMemSetInst *MI) {
   ConstantInt *LenC = dyn_cast<ConstantInt>(MI->getLength());
   Value *Fill = MI->getValue();
 
-  // Keep volatile memset scalarization limited to the single-byte case
-  // where the replacement is exactly one volatile byte store.
-  if (MI->isVolatile()) {
-    if (!LenC || !LenC->isOne() || !Fill->getType()->isIntegerTy(8))
-      return nullptr;
-
-    StoreInst *S = Builder.CreateStore(Fill, MI->getDest(), true);
-    S->copyMetadata(*MI, LLVMContext::MD_DIAssignID);
-    S->setAlignment(MI->getDestAlign().valueOrOne());
-
-    // Set the size of the copy to 0 and will be deleted on the next iteration.
-    MI->setLength((uint64_t)0);
-    return MI;
-  }
+  if (MI->isVolatile())
+    return nullptr;
 
   const Align KnownAlignment =
       getKnownAlignment(MI->getDest(), DL, MI, &AC, &DT);
diff --git a/llvm/test/Transforms/InstCombine/memset-variable-fill.ll b/llvm/test/Transforms/InstCombine/memset-variable-fill.ll
index b5547071a05d5..a9bcf0f1379b5 100644
--- a/llvm/test/Transforms/InstCombine/memset-variable-fill.ll
+++ b/llvm/test/Transforms/InstCombine/memset-variable-fill.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
 ; RUN: opt -passes=instcombine -S < %s | FileCheck %s
 
 declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg)
@@ -9,6 +10,7 @@ define void @variable_fill_len1(ptr %dst, i8 %value) {
 ; CHECK-SAME: ptr [[DST:%.*]], i8 [[VALUE:%.*]]) {
 ; CHECK-NEXT:    store i8 [[VALUE]], ptr [[DST]], align 1
 ; CHECK-NEXT:    ret void
+;
   call void @llvm.memset.p0.i64(ptr align 1 %dst, i8 %value, i64 1, i1 false)
   ret void
 }
@@ -16,8 +18,9 @@ define void @variable_fill_len1(ptr %dst, i8 %value) {
 define void @variable_fill_len1_volatile(ptr %dst, i8 %value) {
 ; CHECK-LABEL: define void @variable_fill_len1_volatile(
 ; CHECK-SAME: ptr [[DST:%.*]], i8 [[VALUE:%.*]]) {
-; CHECK-NEXT:    store volatile i8 [[VALUE]], ptr [[DST]], align 1
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr align 1 [[DST]], i8 [[VALUE]], i64 1, i1 true)
 ; CHECK-NEXT:    ret void
+;
   call void @llvm.memset.p0.i64(ptr align 1 %dst, i8 %value, i64 1, i1 true)
   ret void
 }
@@ -27,6 +30,7 @@ define void @variable_fill_len1_align8(ptr %dst, i8 %value) {
 ; CHECK-SAME: ptr [[DST:%.*]], i8 [[VALUE:%.*]]) {
 ; CHECK-NEXT:    store i8 [[VALUE]], ptr [[DST]], align 8
 ; CHECK-NEXT:    ret void
+;
   call void @llvm.memset.p0.i64(ptr align 8 %dst, i8 %value, i64 1, i1 false)
   ret void
 }
@@ -36,6 +40,7 @@ define void @variable_fill_len1_addrspace(ptr addrspace(1) %dst, i8 %value) {
 ; CHECK-SAME: ptr addrspace(1) [[DST:%.*]], i8 [[VALUE:%.*]]) {
 ; CHECK-NEXT:    store i8 [[VALUE]], ptr addrspace(1) [[DST]], align 1
 ; CHECK-NEXT:    ret void
+;
   call void @llvm.memset.p1.i64(ptr addrspace(1) align 1 %dst, i8 %value, i64 1, i1 false)
   ret void
 }
@@ -45,6 +50,7 @@ define void @variable_fill_len1_atomic(ptr %dst, i8 %value) {
 ; CHECK-SAME: ptr [[DST:%.*]], i8 [[VALUE:%.*]]) {
 ; CHECK-NEXT:    store atomic i8 [[VALUE]], ptr [[DST]] unordered, align 1
 ; CHECK-NEXT:    ret void
+;
   call void @llvm.memset.element.unordered.atomic.p0.i64(ptr align 1 %dst, i8 %value, i64 1, i32 1)
   ret void
 }
@@ -52,8 +58,9 @@ define void @variable_fill_len1_atomic(ptr %dst, i8 %value) {
 define void @variable_fill_len2(ptr %dst, i8 %value) {
 ; CHECK-LABEL: define void @variable_fill_len2(
 ; CHECK-SAME: ptr [[DST:%.*]], i8 [[VALUE:%.*]]) {
-; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr {{.*}}[[DST]], i8 [[VALUE]], i64 2, i1 false)
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr noundef nonnull align 1 dereferenceable(2) [[DST]], i8 [[VALUE]], i64 2, i1 false)
 ; CHECK-NEXT:    ret void
+;
   call void @llvm.memset.p0.i64(ptr align 1 %dst, i8 %value, i64 2, i1 false)
   ret void
 }

>From fe836e0ca7c9df875a6ebefccd71f6a4e310703f Mon Sep 17 00:00:00 2001
From: Naveen <naveen.siddegowda at oss.qualcomm.com>
Date: Wed, 19 Aug 2026 02:50:47 -0700
Subject: [PATCH 3/3] fixup! Fold length-one memset with variable fill

---
 .../InstCombine/InstCombineCalls.cpp          | 15 ++++---------
 .../InstCombine/memset-variable-fill.ll       | 21 ++++++++++---------
 2 files changed, 15 insertions(+), 21 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 2d5bfa065366f..b373d7c196ccc 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -221,12 +221,6 @@ Instruction *InstCombinerImpl::SimplifyAnyMemTransfer(AnyMemTransferInst *MI) {
 }
 
 Instruction *InstCombinerImpl::SimplifyAnyMemSet(AnyMemSetInst *MI) {
-  ConstantInt *LenC = dyn_cast<ConstantInt>(MI->getLength());
-  Value *Fill = MI->getValue();
-
-  if (MI->isVolatile())
-    return nullptr;
-
   const Align KnownAlignment =
       getKnownAlignment(MI->getDest(), DL, MI, &AC, &DT);
   MaybeAlign MemSetAlign = MI->getDestAlign();
@@ -254,6 +248,8 @@ Instruction *InstCombinerImpl::SimplifyAnyMemSet(AnyMemSetInst *MI) {
   }
 
   // Extract the length and validate the fill type.
+  ConstantInt *LenC = dyn_cast<ConstantInt>(MI->getLength());
+  Value *Fill = MI->getValue();
   if (!LenC || !Fill->getType()->isIntegerTy(8))
     return nullptr;
   const uint64_t Len = LenC->getLimitedValue();
@@ -2041,9 +2037,8 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
       }
     }
 
-    // Apart from memset-to-store scalarization below no other transformations
-    // apply to volatile transfers.
-    if (MI->isVolatile() && !isa<AnyMemSetInst>(MI))
+    // No other transformations apply to volatile transfers.
+    if (MI->isVolatile())
       return nullptr;
 
     if (AnyMemTransferInst *MTI = dyn_cast<AnyMemTransferInst>(MI)) {
@@ -2068,8 +2063,6 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
     } else if (auto *MSI = dyn_cast<AnyMemSetInst>(MI)) {
       if (Instruction *I = SimplifyAnyMemSet(MSI))
         return I;
-      if (MI->isVolatile())
-        return nullptr;
     }
 
     // If src/dest is null, this memory intrinsic must be a noop.
diff --git a/llvm/test/Transforms/InstCombine/memset-variable-fill.ll b/llvm/test/Transforms/InstCombine/memset-variable-fill.ll
index a9bcf0f1379b5..e6da776bd1ce8 100644
--- a/llvm/test/Transforms/InstCombine/memset-variable-fill.ll
+++ b/llvm/test/Transforms/InstCombine/memset-variable-fill.ll
@@ -4,6 +4,7 @@
 declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg)
 declare void @llvm.memset.p1.i64(ptr addrspace(1) nocapture writeonly, i8, i64, i1 immarg)
 declare void @llvm.memset.element.unordered.atomic.p0.i64(ptr nocapture writeonly, i8, i64, i32 immarg)
+declare void @llvm.experimental.memset.pattern.p0.i32.i64(ptr nocapture writeonly, i32, i64, i1 immarg)
 
 define void @variable_fill_len1(ptr %dst, i8 %value) {
 ; CHECK-LABEL: define void @variable_fill_len1(
@@ -15,16 +16,6 @@ define void @variable_fill_len1(ptr %dst, i8 %value) {
   ret void
 }
 
-define void @variable_fill_len1_volatile(ptr %dst, i8 %value) {
-; CHECK-LABEL: define void @variable_fill_len1_volatile(
-; CHECK-SAME: ptr [[DST:%.*]], i8 [[VALUE:%.*]]) {
-; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr align 1 [[DST]], i8 [[VALUE]], i64 1, i1 true)
-; CHECK-NEXT:    ret void
-;
-  call void @llvm.memset.p0.i64(ptr align 1 %dst, i8 %value, i64 1, i1 true)
-  ret void
-}
-
 define void @variable_fill_len1_align8(ptr %dst, i8 %value) {
 ; CHECK-LABEL: define void @variable_fill_len1_align8(
 ; CHECK-SAME: ptr [[DST:%.*]], i8 [[VALUE:%.*]]) {
@@ -64,3 +55,13 @@ define void @variable_fill_len2(ptr %dst, i8 %value) {
   call void @llvm.memset.p0.i64(ptr align 1 %dst, i8 %value, i64 2, i1 false)
   ret void
 }
+
+define void @variable_pattern_fill_len1(ptr %dst, i32 %value) {
+; CHECK-LABEL: define void @variable_pattern_fill_len1(
+; CHECK-SAME: ptr [[DST:%.*]], i32 [[VALUE:%.*]]) {
+; CHECK-NEXT:    call void @llvm.experimental.memset.pattern.p0.i32.i64(ptr align 4 [[DST]], i32 [[VALUE]], i64 1, i1 false)
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.memset.pattern.p0.i32.i64(ptr align 4 %dst, i32 %value, i64 1, i1 false)
+  ret void
+}



More information about the llvm-commits mailing list