[llvm-branch-commits] [clang] [CIR] Use data size in emitAggregateCopy for overlapping copies (PR #186702)

Henrich Lauko via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Sun Mar 15 13:57:33 PDT 2026


https://github.com/xlauko created https://github.com/llvm/llvm-project/pull/186702

None

>From a27ad57cfd49c279d8933bf59c3a66dcb939edaf Mon Sep 17 00:00:00 2001
From: xlauko <xlauko at mail.muni.cz>
Date: Sun, 15 Mar 2026 18:22:03 +0100
Subject: [PATCH] [CIR] Use data size in emitAggregateCopy for overlapping
 copies

---
 clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp | 22 +++++--
 clang/lib/CIR/CodeGen/CIRGenValue.h           |  2 +-
 .../CIR/CodeGen/aggregate-copy-overlap.cpp    | 63 +++++++++++++++++++
 clang/test/CIR/CodeGen/no-unique-address.cpp  |  9 +--
 4 files changed, 85 insertions(+), 11 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/aggregate-copy-overlap.cpp

diff --git a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
index 9f390fec97613..d4e0e45c8262d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
@@ -1100,16 +1100,26 @@ void CIRGenFunction::emitAggregateCopy(LValue dest, LValue src, QualType ty,
 
   assert(!cir::MissingFeatures::aggValueSlotVolatile());
 
-  // NOTE(cir): original codegen would normally convert destPtr and srcPtr to
-  // i8* since memcpy operates on bytes. We don't need that in CIR because
-  // cir.copy will operate on any CIR pointer that points to a sized type.
-
   // Don't do any of the memmove_collectable tests if GC isn't set.
   if (cgm.getLangOpts().getGC() != LangOptions::NonGC)
     cgm.errorNYI("emitAggregateCopy: GC");
 
-  [[maybe_unused]] cir::CopyOp copyOp =
-      builder.createCopy(destPtr.getPointer(), srcPtr.getPointer(), isVolatile);
+  // If the data size (excluding tail padding) differs from the full type size,
+  // we can't use cir.copy (which always copies the full pointee type). Instead,
+  // emit cir.libc.memcpy with the precise byte count to avoid clobbering tail
+  // padding that may be occupied by other objects (e.g. [[no_unique_address]]).
+  CharUnits dataSize = typeInfo.Width;
+  if (mayOverlap && dataSize != getContext().getTypeSizeInChars(ty)) {
+    mlir::Location loc = srcPtr.getPointer().getLoc();
+    Address destVoid = destPtr.withElementType(builder, voidTy);
+    Address srcVoid = srcPtr.withElementType(builder, voidTy);
+    mlir::Value sizeVal =
+        builder.getConstInt(loc, sizeTy, dataSize.getQuantity());
+    builder.createMemCpy(loc, destVoid.getPointer(), srcVoid.getPointer(),
+                         sizeVal);
+  } else {
+    builder.createCopy(destPtr.getPointer(), srcPtr.getPointer(), isVolatile);
+  }
 
   assert(!cir::MissingFeatures::opTBAA());
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenValue.h b/clang/lib/CIR/CodeGen/CIRGenValue.h
index e5b925f82a635..e1bbc54cfb9b1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenValue.h
+++ b/clang/lib/CIR/CodeGen/CIRGenValue.h
@@ -377,7 +377,7 @@ class AggValueSlot {
   enum IsDestructed_t { IsNotDestructed, IsDestructed };
   enum IsZeroed_t { IsNotZeroed, IsZeroed };
   enum IsAliased_t { IsNotAliased, IsAliased };
-  enum Overlap_t { MayOverlap, DoesNotOverlap };
+  enum Overlap_t { DoesNotOverlap, MayOverlap };
 
   /// Returns an aggregate value slot indicating that the aggregate
   /// value is being ignored.
diff --git a/clang/test/CIR/CodeGen/aggregate-copy-overlap.cpp b/clang/test/CIR/CodeGen/aggregate-copy-overlap.cpp
new file mode 100644
index 0000000000000..159658fccb473
--- /dev/null
+++ b/clang/test/CIR/CodeGen/aggregate-copy-overlap.cpp
@@ -0,0 +1,63 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu \
+// RUN:   -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu \
+// RUN:   -fclangir -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+
+// Test that emitAggregateCopy uses the data size (excluding tail padding)
+// when copying potentially-overlapping subobjects, and uses full type size
+// otherwise.
+
+struct Base { int x; };
+
+struct HasPadding : Base {
+  char c;
+  // sizeof(HasPadding) = 8 (4 for x, 1 for c, 3 tail padding)
+  // data size = 5
+};
+
+struct VBase { int v; };
+
+// Outer has a virtual base, so its nvsize (14) is smaller than its full
+// sizeof (24). Because [[no_unique_address]] HasPadding extends beyond
+// nvsize (offset 8 + sizeof 8 = 16 > 14), getOverlapForFieldInit returns
+// MayOverlap, and emitAggregateCopy must use the data size (5) instead of
+// the full sizeof (8).
+struct Outer : virtual VBase {
+  [[no_unique_address]] HasPadding hp;
+  char extra;
+  Outer(const HasPadding &hp, char e) : hp(hp), extra(e) {}
+};
+
+// With virtual bases, only the C1 (complete) constructor is emitted.
+// CIR-LABEL: cir.func {{.*}} @_ZN5OuterC1ERK10HasPaddingc(
+// CIR:         %[[DST:.*]] = cir.cast bitcast %{{.+}} : !cir.ptr<!rec_HasPadding> -> !cir.ptr<!void>
+// CIR-NEXT:    %[[SRC:.*]] = cir.cast bitcast %{{.+}} : !cir.ptr<!rec_HasPadding> -> !cir.ptr<!void>
+// CIR-NEXT:    %[[SIZE:.*]] = cir.const #cir.int<5> : !u64i
+// CIR-NEXT:    cir.libc.memcpy %[[SIZE]] bytes from %[[SRC]] to %[[DST]]
+
+// LLVM-LABEL: define {{.*}} void @_ZN5OuterC1ERK10HasPaddingc(
+// LLVM:         call void @llvm.memcpy.p0.p0.i64(ptr %{{.+}}, ptr %{{.+}}, i64 5, i1 false)
+
+void test_overlap(const HasPadding &hp) {
+  Outer o(hp, 'x');
+}
+
+// NonOverlapping does NOT have [[no_unique_address]], so the copy uses
+// cir.copy (full type size) rather than cir.libc.memcpy.
+struct NonOverlapping {
+  HasPadding hp;
+  char extra;
+  NonOverlapping(const HasPadding &hp, char e) : hp(hp), extra(e) {}
+};
+
+// CIR-LABEL: cir.func {{.*}} @_ZN14NonOverlappingC2ERK10HasPaddingc(
+// CIR:         cir.copy %{{.+}} to %{{.+}} : !cir.ptr<!rec_HasPadding>
+
+// LLVM-LABEL: define {{.*}} void @_ZN14NonOverlappingC2ERK10HasPaddingc(
+// LLVM:         call void @llvm.memcpy.p0.p0.i64(ptr %{{.+}}, ptr %{{.+}}, i64 8, i1 false)
+
+void test_no_overlap(const HasPadding &hp) {
+  NonOverlapping o(hp, 'x');
+}
diff --git a/clang/test/CIR/CodeGen/no-unique-address.cpp b/clang/test/CIR/CodeGen/no-unique-address.cpp
index 4f81e194783d3..41aa6388c1e8a 100644
--- a/clang/test/CIR/CodeGen/no-unique-address.cpp
+++ b/clang/test/CIR/CodeGen/no-unique-address.cpp
@@ -35,17 +35,18 @@ struct Outer {
 // CIR:         %[[THIS:.*]] = cir.load %{{.+}} : !cir.ptr<!cir.ptr<!rec_Outer>>, !cir.ptr<!rec_Outer>
 // CIR:         %[[M_BASE:.*]] = cir.get_member %[[THIS]][0] {name = "m"} : !cir.ptr<!rec_Outer> -> !cir.ptr<!rec_Middle2Ebase>
 // CIR-NEXT:    %[[M_COMPLETE:.*]] = cir.cast bitcast %[[M_BASE]] : !cir.ptr<!rec_Middle2Ebase> -> !cir.ptr<!rec_Middle>
-// CIR:         cir.copy %{{.+}} to %[[M_COMPLETE]] : !cir.ptr<!rec_Middle>
+// CIR:         %[[DST:.*]] = cir.cast bitcast %[[M_COMPLETE]] : !cir.ptr<!rec_Middle> -> !cir.ptr<!void>
+// CIR-NEXT:    %[[SRC:.*]] = cir.cast bitcast %{{.+}} : !cir.ptr<!rec_Middle> -> !cir.ptr<!void>
+// CIR-NEXT:    %[[SIZE:.*]] = cir.const #cir.int<5> : !u64i
+// CIR-NEXT:    cir.libc.memcpy %[[SIZE]] bytes from %[[SRC]] to %[[DST]]
 // CIR:         %[[EXTRA:.*]] = cir.get_member %[[THIS]][1] {name = "extra"} : !cir.ptr<!rec_Outer> -> !cir.ptr<!s8i>
 
 // LLVM-LABEL: define {{.*}} void @_ZN5OuterC2ERK6Middlec(
 // LLVM:         %[[GEP:.*]] = getelementptr %struct.Outer, ptr %{{.+}}, i32 0, i32 0
-// LLVM:         call void @llvm.memcpy.p0.p0.i64(ptr %[[GEP]], ptr %{{.+}}, i64 8, i1 false)
+// LLVM:         call void @llvm.memcpy.p0.p0.i64(ptr %[[GEP]], ptr %{{.+}}, i64 5, i1 false)
 
 // OGCG-LABEL: define {{.*}} void @_ZN5OuterC2ERK6Middlec(
 // OGCG:         %[[GEP:.*]] = getelementptr inbounds nuw %struct.Outer, ptr %{{.+}}, i32 0, i32 0
-// TODO(CIR): OG emits i64 5 here via ConstructorMemcpyizer, which CIR
-// doesn't have yet. CIR copies the full 8-byte type instead.
 // OGCG:         call void @llvm.memcpy.p0.p0.i64(ptr {{.*}} %[[GEP]], ptr {{.*}} %{{.+}}, i64 5, i1 false)
 
 void test(const Middle &m) {



More information about the llvm-branch-commits mailing list