https://github.com/xlauko updated https://github.com/llvm/llvm-project/pull/186702
>From ad6d3c7e1cc914f4855903aa974eac9374460b66 Mon Sep 17 00:00:00 2001 From: xlauko <[email protected]> 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 | 76 +++++++++++++++++++ clang/test/CIR/CodeGen/no-unique-address.cpp | 9 ++- 4 files changed, 98 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 8e63a64b3e664..bb31b4ca23f25 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp @@ -1244,16 +1244,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..bcad743a2c9db --- /dev/null +++ b/clang/test/CIR/CodeGen/aggregate-copy-overlap.cpp @@ -0,0 +1,76 @@ +// 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 +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu \ +// RUN: -emit-llvm %s -o %t.og.ll +// RUN: FileCheck --check-prefix=OGCG --input-file=%t.og.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: %[[GEP:.*]] = getelementptr %struct.Outer, ptr %{{.+}}, i32 0, i32 1 +// LLVM: call void @llvm.memcpy.p0.p0.i64(ptr %[[GEP]], ptr %{{.+}}, i64 5, i1 false) + +// OGCG-LABEL: define {{.*}} void @_ZN5OuterC1ERK10HasPaddingc( +// OGCG: %[[GEP:.*]] = getelementptr inbounds nuw %struct.Outer, ptr %{{.+}}, i32 0, i32 1 +// OGCG: call void @llvm.memcpy.p0.p0.i64(ptr {{.*}} %[[GEP]], 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: %[[GEP:.*]] = getelementptr %struct.NonOverlapping, ptr %{{.+}}, i32 0, i32 0 +// LLVM: call void @llvm.memcpy.p0.p0.i64(ptr %[[GEP]], ptr %{{.+}}, i64 8, i1 false) + +// OGCG-LABEL: define {{.*}} void @_ZN14NonOverlappingC2ERK10HasPaddingc( +// OGCG: %[[GEP:.*]] = getelementptr inbounds nuw %struct.NonOverlapping, ptr %{{.+}}, i32 0, i32 0 +// OGCG: call void @llvm.memcpy.p0.p0.i64(ptr {{.*}} %[[GEP]], 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) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
