Author: Adam Smith Date: 2026-07-31T15:49:40-05:00 New Revision: ec2bf56676ae4a46f47e3fbc62d5208b729792a1
URL: https://github.com/llvm/llvm-project/commit/ec2bf56676ae4a46f47e3fbc62d5208b729792a1 DIFF: https://github.com/llvm/llvm-project/commit/ec2bf56676ae4a46f47e3fbc62d5208b729792a1.diff LOG: [Clang][Sema] Synthesize a memcpy body for defaulted union assignment (#206579) A defaulted copy or move assignment operator for a union is synthesized with an empty body. `DefineImplicitCopyAssignment` / `DefineImplicitMoveAssignment` skip union members in the memberwise loop, and the implied copy of the object representation has no AST representation, a FIXME that has sat at that skip for a long time. The operator ends up copying nothing. Classic CodeGen hides this at ordinary call sites by lowering a trivial assignment to a memcpy at the call site, so `u1 = u2` works even though the operator body is a no-op. But when the operator is genuinely called, through a pointer-to-member for instance, it silently copies nothing. ClangIR calls the assignment operator at the call site rather than eliding it, so it hits the empty body directly and drops every union assignment. The no-op is then deleted at `-O3`. That is the MultiSource `kc` miscompile, where a `YYSTYPE` union assignment (`*++yyvsp = yylval`) becomes a no-op. This implements the FIXME by mirroring the defaulted union copy constructor (`CGClass.cpp`: "union copy constructor, we must emit a memcpy") in the AST: when the class is a union, `DefineImplicit{Copy,Move}Assignment` emits a single whole-object copy through `buildMemcpyForAssignmentOp`, the helper already used for trivially-copyable array members, instead of the skipped per-member assignments. The operator stays trivial (triviality is fixed at declaration time, before the body is synthesized), so constant evaluation, which copies trivial unions through its own semantic path and never executes the body, is unchanged. Copying the object representation is correct even when a union member is not trivially copyable, but the synthesized memcpy would then trip `-Wnontrivial-memcall`. A `void*` cast would drop pointee qualifiers such as the address space and inject an explicit cast node the source never wrote. Instead the call keeps its typed union-pointer arguments, and the union branch in `DefineImplicit{Copy,Move}Assignment` wraps the build in `IgnoreAllWarningDiagRAII`, a save-and-restore around the engine's existing ignore-all-warnings state. `CheckMemaccessArguments` defers the warning and its note through `DiagRuntimeBehavior`, and that queue flushes after the RAII has restored the previous ignore state, so `DiagIfReachable` samples that state as it enqueues and drops what is not error-class. Under `-w` the same queue is discarded before it flushes, so nothing outside the scoped region changes. Classic CodeGen now emits the copy when the operator is odr-used, and ClangIR's union assignment lowers to a real memcpy. On the Sema side, tests cover the synthesized AST for the implicit-`this` and C++23 explicit-object spellings, the `-Wnontrivial-memcall` suppression, the preserved address space, the clean `-ast-print` output, and constexpr active-member copy. The classic and ClangIR lowering is checked across named, anonymous, and tail-padded unions. #198918 added a defaulted-union `errorNYI` in ClangIR and deferred this AST fix to a separate change. This PR makes that fix and drops the guard, so the now-non-empty body lowers to a `cir.call @memcpy` instead of erroring. Its own `union-copy-move-assignment.cpp` CIR test, which expects that call, is the regression check. Added: clang/test/AST/ast-dump-union-assign-address-space.clcpp clang/test/AST/ast-dump-union-assign-explicit-object.cpp clang/test/AST/ast-dump-union-copy-move-assign.cpp clang/test/AST/ast-print-union-assign.cpp clang/test/CIR/CodeGen/union-copy-move-assignment.cpp clang/test/CodeGenCXX/union-copy-move-assignment.cpp clang/test/SemaCXX/union-assign-constexpr.cpp clang/test/SemaCXX/union-assign-memcpy-nontrivial.cpp Modified: clang/docs/ReleaseNotes.md clang/include/clang/Basic/Diagnostic.h clang/lib/CIR/CodeGen/CIRGenClass.cpp clang/lib/Sema/SemaDeclCXX.cpp clang/lib/Sema/SemaExpr.cpp Removed: clang/test/CIR/CodeGen/trivial-union-assign-nyi.cpp ################################################################################ diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index ca0cdf5e32a10..a38b99ff8e075 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -418,6 +418,12 @@ features cannot lower the translation-unit ABI level; libstdc++15 has been extended to support preprocessed input. Previously, splitting the preprocessing and compilation step would result in the fix not being applied. (#GH160314) +- A defaulted copy or move assignment operator for a union was left with an + empty body and copied nothing when the operator was actually called, for + example through a pointer to member. Clang now synthesizes a whole-object + copy so the union's object representation is copied, matching the defaulted + union copy constructor. + #### Bug Fixes to AST Handling - Fixed a non-deterministic ordering of unused local typedefs that made diff --git a/clang/include/clang/Basic/Diagnostic.h b/clang/include/clang/Basic/Diagnostic.h index c4325ba410655..834f026aff62d 100644 --- a/clang/include/clang/Basic/Diagnostic.h +++ b/clang/include/clang/Basic/Diagnostic.h @@ -1124,6 +1124,22 @@ class DiagnosticErrorTrap { } }; +/// RAII class that temporarily sets the "ignore all warnings" state on a +/// DiagnosticsEngine and restores the previous state on destruction. Use it to +/// silence warnings around a self-contained region of diagnostics, such as a +/// compiler-synthesized call whose arguments are known to be correct. +class IgnoreAllWarningDiagRAII { + DiagnosticsEngine &Diag; + bool OldValue; + +public: + explicit IgnoreAllWarningDiagRAII(DiagnosticsEngine &Diag) + : Diag(Diag), OldValue(Diag.getIgnoreAllWarnings()) { + Diag.setIgnoreAllWarnings(true); + } + ~IgnoreAllWarningDiagRAII() { Diag.setIgnoreAllWarnings(OldValue); } +}; + /// The streaming interface shared between DiagnosticBuilder and /// PartialDiagnostic. This class is not intended to be constructed directly /// but only as base class of DiagnosticBuilder and PartialDiagnostic builder. diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp index 31e09320a43d6..8ead754d7cf3c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -901,20 +901,6 @@ void CIRGenFunction::emitImplicitAssignmentOperatorBody(FunctionArgList &args) { assert(!cir::MissingFeatures::incrementProfileCounter()); assert(!cir::MissingFeatures::runCleanupsScope()); - // A defaulted union copy/move assignment has an empty synthesized body: - // Sema skips union fields (the FIXME in SemaDeclCXX::buildSingleCopyAssign), - // so there is no AST expression for the implied whole-object memcpy. - // Emitting that body would silently drop the copy, so report NYI instead. - // Struct/array memcpy-equivalent assignments carry the implicit memberwise - // copies in the AST (per-field assignment expressions, or a builtin memcpy - // call for array members) and lower correctly through the loop below. - if (assignOp->isMemcpyEquivalentSpecialMember(getContext()) && - assignOp->getParent()->isUnion()) { - cgm.errorNYI(assignOp->getSourceRange(), - "defaulted union copy/move assignment operator"); - return; - } - // Classic codegen uses a special class to attempt to replace member // initializers with memcpy. We could possibly defer that to the // lowering or optimization phases to keep the memory accesses more diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index e5a5963db27c2..47b01b913b428 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -15507,10 +15507,30 @@ void Sema::DefineImplicitCopyAssignment(SourceLocation CurrentLocation, Statements.push_back(Copy.getAs<Expr>()); } + // A defaulted copy assignment operator for a union copies the object + // representation as if by a memcpy, the same way the defaulted union copy + // constructor does. The memberwise loop below skips union members. + if (ClassDecl->isUnion()) { + ExprBuilder &To = ExplicitObject + ? static_cast<ExprBuilder &>(*ExplicitObject) + : static_cast<ExprBuilder &>(*DerefThis); + // Copying the object representation is correct even for a union that is + // not trivially copyable, so -Wnontrivial-memcall is a false positive + // here. Ignoring warnings rather than casting the arguments to void* + // keeps them typed, which preserves their address space. + IgnoreAllWarningDiagRAII IgnoreWarnings(Diags); + StmtResult Copy = buildMemcpyForAssignmentOp( + *this, Loc, Context.getCanonicalTagType(ClassDecl), To, OtherRef); + if (Copy.isInvalid()) { + CopyAssignOperator->setInvalidDecl(); + return; + } + Statements.push_back(Copy.getAs<Stmt>()); + } + // Assign non-static members. for (auto *Field : ClassDecl->fields()) { - // FIXME: We should form some kind of AST representation for the implied - // memcpy in a union copy operation. + // Union members are copied by the whole-object memcpy emitted above. if (Field->isUnnamedBitField() || Field->getParent()->isUnion()) continue; @@ -15897,10 +15917,30 @@ void Sema::DefineImplicitMoveAssignment(SourceLocation CurrentLocation, Statements.push_back(Move.getAs<Expr>()); } + // A defaulted move assignment operator for a union copies the object + // representation as if by a memcpy, the same way the defaulted union copy + // constructor does. The memberwise loop below skips union members. + if (ClassDecl->isUnion()) { + ExprBuilder &To = ExplicitObject + ? static_cast<ExprBuilder &>(*ExplicitObject) + : static_cast<ExprBuilder &>(*DerefThis); + // Copying the object representation is correct even for a union that is + // not trivially copyable, so -Wnontrivial-memcall is a false positive + // here. Ignoring warnings rather than casting the arguments to void* + // keeps them typed, which preserves their address space. + IgnoreAllWarningDiagRAII IgnoreWarnings(Diags); + StmtResult Copy = buildMemcpyForAssignmentOp( + *this, Loc, Context.getCanonicalTagType(ClassDecl), To, OtherRef); + if (Copy.isInvalid()) { + MoveAssignOperator->setInvalidDecl(); + return; + } + Statements.push_back(Copy.getAs<Stmt>()); + } + // Assign non-static members. for (auto *Field : ClassDecl->fields()) { - // FIXME: We should form some kind of AST representation for the implied - // memcpy in a union copy operation. + // Union members are copied by the whole-object memcpy emitted above. if (Field->isUnnamedBitField() || Field->getParent()->isUnion()) continue; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index ed3d27b5adc27..59b8c9b60663c 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -21114,6 +21114,14 @@ bool Sema::DiagIfReachable(SourceLocation Loc, ArrayRef<const Stmt *> Stmts, } if (getCurFunction()) { + // This queue flushes after the function is analyzed, by which time an + // ignore-all-warnings region live here is gone, so sample it now. A note + // is not error-class either, so this also drops the notes that accompany a + // skipped warning. They arrive on their own call, out of reach of the + // engine's rule that drops a note whose warning was ignored. + if (Diags.getIgnoreAllWarnings() && + Diags.getDiagnosticIDs()->isWarningOrExtension(PD.getDiagID())) + return false; FunctionScopes.back()->PossiblyUnreachableDiags.push_back( sema::PossiblyUnreachableDiag(PD, Loc, Stmts)); return true; diff --git a/clang/test/AST/ast-dump-union-assign-address-space.clcpp b/clang/test/AST/ast-dump-union-assign-address-space.clcpp new file mode 100644 index 0000000000000..069d375afa046 --- /dev/null +++ b/clang/test/AST/ast-dump-union-assign-address-space.clcpp @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -triple spir-unknown-unknown -cl-std=clc++2021 -x cl \ +// RUN: -ast-dump %s | FileCheck %s + +union U { + int a; + float b; +}; + +__kernel void k(__global U *g, __global const U *s) { + *g = *s; +} + +// Converting the typed union pointers preserves the address space, which a +// compiler-written cast to plain void * would drop. + +// CHECK: CXXMethodDecl {{.*}} implicit {{.*}}operator= '__generic U &(const __generic U &) +// CHECK: CompoundStmt +// CHECK: CallExpr +// CHECK: DeclRefExpr {{.*}} '__builtin_memcpy' +// CHECK: ImplicitCastExpr {{.*}} '__generic void *' <BitCast> +// CHECK: UnaryOperator {{.*}} '__generic U *' prefix '&' +// CHECK: ImplicitCastExpr {{.*}} 'const __generic void *' <BitCast> +// CHECK: UnaryOperator {{.*}} 'const __generic U *' prefix '&' diff --git a/clang/test/AST/ast-dump-union-assign-explicit-object.cpp b/clang/test/AST/ast-dump-union-assign-explicit-object.cpp new file mode 100644 index 0000000000000..f37d3ee32103a --- /dev/null +++ b/clang/test/AST/ast-dump-union-assign-explicit-object.cpp @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++2b -ast-dump %s | FileCheck %s + +union U { + int a; + float b; + U &operator=(this U &self, const U &) = default; + U &operator=(this U &self, U &&) = default; +}; + +void odr_use(U &x, const U &y, U &&z) { + x = y; + x = static_cast<U &&>(z); +} + +// C++23 explicit-object form uses the same typed-pointer memcpy. + +// CHECK: CXXMethodDecl {{.*}} operator= 'U &(U &, const U &) +// CHECK: CompoundStmt +// CHECK: CallExpr +// CHECK: DeclRefExpr {{.*}} '__builtin_memcpy' +// CHECK: ImplicitCastExpr {{.*}} 'void *' <BitCast> +// CHECK: UnaryOperator {{.*}} 'U *' prefix '&' +// CHECK: ImplicitCastExpr {{.*}} 'const void *' <BitCast> +// CHECK: UnaryOperator {{.*}} 'const U *' prefix '&' +// CHECK: ReturnStmt + +// CHECK: CXXMethodDecl {{.*}} operator= 'U &(U &, U &&) +// CHECK: CompoundStmt +// CHECK: CallExpr +// CHECK: DeclRefExpr {{.*}} '__builtin_memcpy' +// CHECK: ImplicitCastExpr {{.*}} 'void *' <BitCast> +// CHECK: UnaryOperator {{.*}} 'U *' prefix '&' +// CHECK: ImplicitCastExpr {{.*}} 'const void *' <BitCast> +// CHECK: UnaryOperator {{.*}} 'U *' prefix '&' +// CHECK: ReturnStmt diff --git a/clang/test/AST/ast-dump-union-copy-move-assign.cpp b/clang/test/AST/ast-dump-union-copy-move-assign.cpp new file mode 100644 index 0000000000000..ed393420eaf3b --- /dev/null +++ b/clang/test/AST/ast-dump-union-copy-move-assign.cpp @@ -0,0 +1,34 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -ast-dump %s | FileCheck %s + +union U { + int a; + float b; +}; + +void odr_use(U &x, const U &y, U &&z) { + x = y; + x = static_cast<U &&>(z); +} + +// The memcpy operands are typed union pointers, converted to void * only by +// the builtin's parameter. + +// CHECK: CXXMethodDecl {{.*}} implicit {{.*}}operator= 'U &(const U &) +// CHECK: CompoundStmt +// CHECK: CallExpr +// CHECK: DeclRefExpr {{.*}} '__builtin_memcpy' +// CHECK: ImplicitCastExpr {{.*}} 'void *' <BitCast> +// CHECK: UnaryOperator {{.*}} 'U *' prefix '&' +// CHECK: ImplicitCastExpr {{.*}} 'const void *' <BitCast> +// CHECK: UnaryOperator {{.*}} 'const U *' prefix '&' +// CHECK: ReturnStmt + +// CHECK: CXXMethodDecl {{.*}} implicit {{.*}}operator= 'U &(U &&) +// CHECK: CompoundStmt +// CHECK: CallExpr +// CHECK: DeclRefExpr {{.*}} '__builtin_memcpy' +// CHECK: ImplicitCastExpr {{.*}} 'void *' <BitCast> +// CHECK: UnaryOperator {{.*}} 'U *' prefix '&' +// CHECK: ImplicitCastExpr {{.*}} 'const void *' <BitCast> +// CHECK: UnaryOperator {{.*}} 'U *' prefix '&' +// CHECK: ReturnStmt diff --git a/clang/test/AST/ast-print-union-assign.cpp b/clang/test/AST/ast-print-union-assign.cpp new file mode 100644 index 0000000000000..ba158804e8802 --- /dev/null +++ b/clang/test/AST/ast-print-union-assign.cpp @@ -0,0 +1,21 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -ast-print %s | FileCheck %s + +union U { + int a; + float b; + U &operator=(const U &) = default; + U &operator=(U &&) = default; +}; + +void odr_use(U &x, const U &y, U &&z) { + x = y; + x = static_cast<U &&>(z); +} + +// The synthesized memcpy body must not leak into -ast-print. + +// CHECK: union U { +// CHECK: U &operator=(const U &) noexcept = default; +// CHECK: U &operator=(U &&) noexcept = default; +// CHECK-NOT: __builtin_memcpy +// CHECK-NOT: (void *) diff --git a/clang/test/CIR/CodeGen/trivial-union-assign-nyi.cpp b/clang/test/CIR/CodeGen/trivial-union-assign-nyi.cpp deleted file mode 100644 index e457dca4fd23d..0000000000000 --- a/clang/test/CIR/CodeGen/trivial-union-assign-nyi.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -verify %s - -// The defaulted copy/move assignment operator of a union has an empty -// synthesized body -- Sema skips union fields, leaving no AST expression for -// the implied whole-object copy. Emitting that body would silently drop the -// copy, so CIRGen reports NYI instead. - -// expected-error@+1 2 {{ClangIR code gen Not Yet Implemented: defaulted union copy/move assignment operator}} -union U { - void *p; - int i; -}; - -void copy_assign(U &a, U &b) { a = b; } -void move_assign(U &a, U &b) { a = static_cast<U &&>(b); } diff --git a/clang/test/CIR/CodeGen/union-copy-move-assignment.cpp b/clang/test/CIR/CodeGen/union-copy-move-assignment.cpp new file mode 100644 index 0000000000000..aeb3461990e5c --- /dev/null +++ b/clang/test/CIR/CodeGen/union-copy-move-assignment.cpp @@ -0,0 +1,33 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefixes=LLVM,LLVMCIR --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefixes=LLVM,OGCG --input-file=%t.ll %s + +union U { + int a; + float b; +}; + +// Odr-use both defaulted assignment operators out of line so their bodies are +// emitted under both backends. +auto get_copy = static_cast<U &(U::*)(const U &)>(&U::operator=); +auto get_move = static_cast<U &(U::*)(U &&)>(&U::operator=); + +// CIR: cir.func{{.*}}@_ZN1UaSERKS_{{.*}}cxx_assign<!rec_U, copy, trivial true> +// CIR: cir.call @memcpy( +// CIR: cir.func{{.*}}@_ZN1UaSEOS_{{.*}}cxx_assign<!rec_U, move, trivial true> +// CIR: cir.call @memcpy( + +// The CIR backend calls the memcpy libcall where the classic backend emits the +// llvm.memcpy intrinsic. + +// LLVM: define linkonce_odr noundef nonnull align 4 dereferenceable(4) ptr @_ZN1UaSERKS_(ptr noundef nonnull align 4 dereferenceable(4) %{{.+}}, ptr noundef nonnull align 4 dereferenceable(4) %{{.+}}) +// LLVMCIR: call ptr @memcpy(ptr noundef %{{.+}}, ptr noundef %{{.+}}, i64 noundef 4) +// LLVMCIR-NOT: call ptr @memcpy +// OGCG: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %{{.+}}, ptr align 4 %{{.+}}, i64 4, i1 false) +// OGCG-NOT: call void @llvm.memcpy +// LLVM: define linkonce_odr noundef nonnull align 4 dereferenceable(4) ptr @_ZN1UaSEOS_(ptr noundef nonnull align 4 dereferenceable(4) %{{.+}}, ptr noundef nonnull align 4 dereferenceable(4) %{{.+}}) +// LLVMCIR: call ptr @memcpy(ptr noundef %{{.+}}, ptr noundef %{{.+}}, i64 noundef 4) +// OGCG: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %{{.+}}, ptr align 4 %{{.+}}, i64 4, i1 false) diff --git a/clang/test/CodeGenCXX/union-copy-move-assignment.cpp b/clang/test/CodeGenCXX/union-copy-move-assignment.cpp new file mode 100644 index 0000000000000..406285cd3d353 --- /dev/null +++ b/clang/test/CodeGenCXX/union-copy-move-assignment.cpp @@ -0,0 +1,59 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -emit-llvm %s -o - | FileCheck %s + +union U { + int a; + float b; +}; + +// Odr-use both defaulted assignment operators out of line so their bodies are +// emitted (a trivial assignment at a call site is otherwise memcpy'd directly). +auto get_copy = static_cast<U &(U::*)(const U &)>(&U::operator=); +auto get_move = static_cast<U &(U::*)(U &&)>(&U::operator=); + +// Exactly one whole-object memcpy per assignment body. +// CHECK-LABEL: define linkonce_odr noundef nonnull align 4 dereferenceable(4) ptr @_ZN1UaSERKS_(ptr noundef nonnull align 4 dereferenceable(4) %{{.+}}, ptr noundef nonnull align 4 dereferenceable(4) %{{.+}}) +// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %{{.+}}, ptr align 4 %{{.+}}, i64 4, i1 false) +// CHECK-NOT: memcpy +// CHECK: ret ptr + +// CHECK-LABEL: define linkonce_odr noundef nonnull align 4 dereferenceable(4) ptr @_ZN1UaSEOS_(ptr noundef nonnull align 4 dereferenceable(4) %{{.+}}, ptr noundef nonnull align 4 dereferenceable(4) %{{.+}}) +// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %{{.+}}, ptr align 4 %{{.+}}, i64 4, i1 false) +// CHECK-NOT: memcpy +// CHECK: ret ptr + +union Padded { + int a; + char b[5]; +}; + +// sizeof(Padded) == 8, so the whole-object copy includes the tail padding. +auto get_copy_padded = static_cast<Padded &(Padded::*)(const Padded &)>(&Padded::operator=); + +// CHECK-LABEL: define linkonce_odr noundef nonnull align 4 dereferenceable(8) ptr @_ZN6PaddedaSERKS_(ptr noundef nonnull align 4 dereferenceable(8) %{{.+}}, ptr noundef nonnull align 4 dereferenceable(8) %{{.+}}) +// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %{{.+}}, ptr align 4 %{{.+}}, i64 8, i1 false) +// CHECK-NOT: memcpy +// CHECK: ret ptr + +struct WithNamedUnion { + U u; + int x; +}; + +// A named union member is copied as part of the containing class's defaulted +// assignment. +void assign_named(WithNamedUnion *d, const WithNamedUnion *s) { *d = *s; } +// CHECK-LABEL: define dso_local void @_Z12assign_namedP14WithNamedUnionPKS_(ptr noundef %{{.+}}, ptr noundef %{{.+}}) +// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %{{.+}}, ptr align 4 %{{.+}}, i64 8, i1 false) + +struct WithAnonUnion { + union { + int a; + float b; + }; + int x; +}; + +// An anonymous union member is likewise copied. +void assign_anon(WithAnonUnion *d, const WithAnonUnion *s) { *d = *s; } +// CHECK-LABEL: define dso_local void @_Z11assign_anonP13WithAnonUnionPKS_(ptr noundef %{{.+}}, ptr noundef %{{.+}}) +// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %{{.+}}, ptr align 4 %{{.+}}, i64 8, i1 false) diff --git a/clang/test/SemaCXX/union-assign-constexpr.cpp b/clang/test/SemaCXX/union-assign-constexpr.cpp new file mode 100644 index 0000000000000..3a86442820c7f --- /dev/null +++ b/clang/test/SemaCXX/union-assign-constexpr.cpp @@ -0,0 +1,28 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -fsyntax-only -verify %s +// expected-no-diagnostics + +// The memcpy body must not block constant evaluation of a union assignment. + +union U { + int a; + float b; +}; + +constexpr int copy_active() { + U x{}; + x.a = 7; + U y{}; + y = x; + return y.a; +} + +constexpr int move_active() { + U x{}; + x.a = 9; + U y{}; + y = static_cast<U &&>(x); + return y.a; +} + +static_assert(copy_active() == 7); +static_assert(move_active() == 9); diff --git a/clang/test/SemaCXX/union-assign-memcpy-nontrivial.cpp b/clang/test/SemaCXX/union-assign-memcpy-nontrivial.cpp new file mode 100644 index 0000000000000..53dec05e077b4 --- /dev/null +++ b/clang/test/SemaCXX/union-assign-memcpy-nontrivial.cpp @@ -0,0 +1,33 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -fsyntax-only \ +// RUN: -Wnontrivial-memcall -Wdeprecated-copy-with-user-provided-dtor -verify %s + +struct NonTrivialDtor { + ~NonTrivialDtor(); +}; + +union U { + NonTrivialDtor n; + int i; +}; + +// Odr-use both defaulted assignment operators so their bodies are synthesized. +// The synthesized memcpy must not warn. +auto get_copy = static_cast<U &(U::*)(const U &)>(&U::operator=); +auto get_move = static_cast<U &(U::*)(U &&)>(&U::operator=); + +// A user-written memcpy of the same union is not suppressed and still warns. +void user_memcpy(U *d, const U *s) { + __builtin_memcpy(d, s, sizeof(U)); // expected-warning {{first argument in call to '__builtin_memcpy' is a pointer to non-trivially copyable type 'U'}} expected-note {{explicitly cast the pointer to silence this warning}} +} + +// The memcpy suppression is scoped to the synthesized call, so an unrelated +// warning for the same union still fires: a user-provided destructor deprecates +// the implicit copy assignment. +union V { + int i; + ~V() {} // expected-warning {{definition of implicit copy assignment operator for 'V' is deprecated because it has a user-provided destructor}} +}; + +void use_deprecated_copy(V &a, const V &b) { + a = b; // expected-note {{in implicit copy assignment operator for 'V' first required here}} +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
