[clang] [clang][bytecode] Use a special opcode for trivial defaulted copy/move operator calls (PR #222332)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Sep 9 06:55:27 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Timm Baeder (tbaederr)
<details>
<summary>Changes</summary>
The default code that clang synthesizes for them contains calls to `__builtin_memcpy`, e.g. in
```
CXXMethodDecl 0x7d471ee7cc90 <array.cpp:1983:10> col:10 implicit used constexpr operator= 'array &(const array &) noexcept' inline default trivial external-linkage
|-ParmVarDecl 0x7d471ee7cdd0 <col:10> col:10 used 'const array &'
`-CompoundStmt 0x7d471ee7e340 <col:10>
|-CallExpr 0x7d471ee7e268 <col:10> 'void *'
| |-ImplicitCastExpr 0x7d471ee7e248 <col:10> 'void *(*)(void *, const void *, __size_t) noexcept' <BuiltinFnToFnPtr>
| | `-DeclRefExpr 0x7d471ee7e160 <col:10> '<builtin fn type>' Function 0x7d471ee7de88 '__builtin_memcpy' 'void *(void *, const void *, __size_t) noexcept'
| |-ImplicitCastExpr 0x7d471ee7e2b0 <col:10> 'void *' <BitCast>
| | `-UnaryOperator 0x7d471ee7dd18 <col:10> 'int (*)[4]' prefix '&' cannot overflow
| | `-MemberExpr 0x7d471ee7dca0 <col:10> 'int[4]' lvalue ->_M_elems 0x7d471ee52780
| | `-CXXThisExpr 0x7d471ee7dc88 <col:10> 'array *' this
| |-ImplicitCastExpr 0x7d471ee7e2d0 <col:10> 'const void *' <BitCast>
| | `-UnaryOperator 0x7d471ee7db38 <col:10> 'const int (*)[4]' prefix '&' cannot overflow
| | `-MemberExpr 0x7d471ee7dac0 <col:10> 'const int[4]' lvalue ._M_elems 0x7d471ee52780
| | `-DeclRefExpr 0x7d471ee7da98 <col:10> 'const array' lvalue ParmVar 0x7d471ee7cdd0 depth 0 index 0 'const array &'
| `-IntegerLiteral 0x7d471ee7e1a8 <col:10> '__size_t':'unsigned long' 16
`-ReturnStmt 0x7d471ee7e328 <col:10>
`-UnaryOperator 0x7d471ee7e308 <col:10> 'array' lvalue prefix '*' cannot overflow
`-CXXThisExpr 0x7d471ee7e2f0 <col:10> 'array *' this
```
However, this violates a contract of `__builtin_memcpy` if source and destination are the same pointer, as in `*__first = *__first`, which is happening in one of the attached test cases. The check for this is correct though, so add a new opcode for this case. The functionality is similar to `__builtin_memcpy` but it doesn't check for overlapping regions (among others).
Fixes https://github.com/llvm/llvm-project/issues/221403
---
Patch is 21.05 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/222332.diff
12 Files Affected:
- (modified) clang/lib/AST/ByteCode/Compiler.cpp (+39-3)
- (modified) clang/lib/AST/ByteCode/Interp.cpp (+66-8)
- (modified) clang/lib/AST/ByteCode/Interp.h (+9-20)
- (modified) clang/lib/AST/ByteCode/InterpBuiltin.cpp (+29-16)
- (modified) clang/lib/AST/ByteCode/InterpHelpers.h (+6-1)
- (modified) clang/lib/AST/ByteCode/Opcodes.td (+3)
- (modified) clang/lib/AST/ByteCode/Pointer.h (+1)
- (modified) clang/lib/AST/ExprConstShared.h (+3)
- (modified) clang/lib/AST/ExprConstant.cpp (+3-3)
- (modified) clang/test/AST/ByteCode/cxx14.cpp (+20)
- (modified) clang/test/AST/ByteCode/cxx26.cpp (+56)
- (modified) clang/test/AST/ByteCode/cxx2a.cpp (+17)
``````````diff
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index daa5307c92298..e4f9c1d501fff 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -6142,6 +6142,15 @@ bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
return true;
}
+static bool isTrivialMemoryOperation(const CXXMethodDecl *MD) {
+ if (!MD || !MD->isDefaulted())
+ return false;
+ if (!MD->isCopyAssignmentOperator() && !MD->isMoveAssignmentOperator())
+ return false;
+ return MD->getParent()->isUnion() ||
+ (MD->isTrivial() && isReadByLvalueToRvalueConversion(MD->getParent()));
+}
+
template <class Emitter>
bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
if (E->containsErrors())
@@ -6174,6 +6183,35 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
}
LocalScope<Emitter> CallScope(this, ScopeKind::Call);
+ ArrayRef<const Expr *> Args(E->getArgs(), E->getNumArgs());
+ bool ActivateLHS = false;
+
+ // Emit a special op for trivial copy/move operators.
+ if (isTrivialMemoryOperation(dyn_cast_if_present<CXXMethodDecl>(FuncDecl))) {
+ const Function *Func = getFunction(FuncDecl);
+ if (!Func)
+ return false;
+
+ if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(E);
+ OCE && OCE->isAssignmentOp()) {
+ const CXXRecordDecl *LHSRecord = Args[0]->getType()->getAsCXXRecordDecl();
+ ActivateLHS = LHSRecord && LHSRecord->hasTrivialDefaultConstructor();
+ }
+ if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(E))
+ if (!this->visit(MCE->getImplicitObjectArgument()))
+ return false;
+
+ if (!this->visitCallArgs(Args, FuncDecl, /*ActivateLHS=*/ActivateLHS,
+ isa<CXXOperatorCallExpr>(E)))
+ return false;
+
+ if (!this->emitTrivialCopy(ActivateLHS, Func, E))
+ return false;
+
+ if (!DiscardResult)
+ return CallScope.destroyLocals();
+ return this->emitPopPtr(E) && CallScope.destroyLocals();
+ }
QualType ReturnType = E->getCallReturnType(Ctx.getASTContext());
OptPrimType T = classify(ReturnType);
@@ -6202,11 +6240,8 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
}
}
- ArrayRef<const Expr *> Args(E->getArgs(), E->getNumArgs());
const Expr *ReversedArgs[2];
-
bool IsAssignmentOperatorCall = false;
- bool ActivateLHS = false;
if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(E);
OCE && OCE->isAssignmentOp()) {
// Just like with regular assignments, we need to special-case assignment
@@ -6220,6 +6255,7 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
ReversedArgs[1] = Args[0];
Args = ReversedArgs;
}
+
// Calling a static operator will still
// pass the instance, but we don't need it.
// Discard it here.
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index a41cc1f5a564b..52ad35ed8826c 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -987,30 +987,30 @@ bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
}
bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
- bool WillBeActivated) {
+ AccessKinds AK, bool WillBeActivated) {
if (!Ptr.isBlockPointer() || Ptr.isZero())
return false;
if (!Ptr.block()->isAccessible()) {
- if (!CheckLive(S, OpPC, Ptr, AK_Assign))
+ if (!CheckLive(S, OpPC, Ptr, AK))
return false;
if (!CheckExtern(S, OpPC, Ptr))
return false;
- return CheckDummy(S, OpPC, Ptr.block(), AK_Assign);
+ return CheckDummy(S, OpPC, Ptr.block(), AK);
}
- if (!WillBeActivated && !CheckLifetime(S, OpPC, Ptr, AK_Assign))
+ if (!WillBeActivated && !CheckLifetime(S, OpPC, Ptr, AK))
return false;
- if (!CheckRange(S, OpPC, Ptr, AK_Assign))
+ if (!CheckRange(S, OpPC, Ptr, AK))
return false;
- if (!CheckActive(S, OpPC, Ptr, AK_Assign, WillBeActivated))
+ if (!CheckActive(S, OpPC, Ptr, AK, WillBeActivated))
return false;
if (!CheckGlobal(S, OpPC, Ptr))
return false;
if (!CheckConst(S, OpPC, Ptr))
return false;
- if (!CheckVolatile(S, OpPC, Ptr, AK_Assign))
+ if (!CheckVolatile(S, OpPC, Ptr, AK))
return false;
- if (!CheckMutable(S, OpPC, Ptr, AK_Assign))
+ if (!CheckMutable(S, OpPC, Ptr, AK))
return false;
if (isConstexprUnknown(Ptr))
return false;
@@ -3404,6 +3404,64 @@ std::optional<Pointer> addSubOffsetOpaque(InterpState &S, CodePtr OpPC,
return Pointer(OP.withPastEnd(true), NewOffset);
}
+bool Memcpy(InterpState &S, CodePtr OpPC) {
+ const Pointer &Src = S.Stk.pop<Pointer>();
+ Pointer &Dest = S.Stk.peek<Pointer>();
+
+ if (Src.isDummy() || !Src.isBlockPointer())
+ return false;
+ if (!Dest.isBlockPointer())
+ return false;
+
+ if ((Src.getRecord() && Src.getRecord()->isUnion() &&
+ !Src.getRecord()->isAnonymousUnion()) ||
+ Src.inUnion()) {
+ if (!CheckLoad(S, OpPC, Src))
+ return false;
+ }
+
+ return DoMemcpy(S, OpPC, Src, Dest);
+}
+
+bool TrivialCopy(InterpState &S, CodePtr OpPC, bool Activate,
+ const Function *Func) {
+ const Pointer &Src = S.Stk.pop<Pointer>();
+ Pointer &Dest = S.Stk.peek<Pointer>();
+
+ if (Src.isDummy() || Src.isConstexprUnknown() || !Src.isBlockPointer())
+ return false;
+ if (!Dest.isBlockPointer() || Dest.isDummy() || Dest.isConstexprUnknown())
+ return false;
+
+ if (!CheckStore(S, OpPC, Dest, AK_MemberCall,
+ /*WillBeActivated=*/Activate))
+ return false;
+
+ if (S.checkingPotentialConstantExpression())
+ return false;
+
+ // NOTE: This is a fake function frame that doesn't do anything except show up
+ // in the "in call to" diagnostics. Since the copies we replace with this
+ // opcode are always defaulted/trivial, they don't add much there either
+ // though. Once we default to the bytecode interpreter, we shoud consider just
+ // removing it.
+ auto Memory = std::make_unique<char[]>(InterpFrame::allocSize(Func));
+ auto *NewFrame =
+ new (Memory.get()) InterpFrame(S, Func, S.PC, /*VarArgSize=*/0);
+ InterpFrame *FrameBefore = S.Current;
+ S.Current = NewFrame;
+
+ if (!CheckLoad(S, OpPC, Src, AK_Read)) {
+ S.Current = FrameBefore;
+ return false;
+ }
+
+ bool Result = DoMemcpy(S, OpPC, Src, Dest, Activate, /*Diagnose=*/true);
+ S.Current = FrameBefore;
+
+ return Result;
+}
+
// FIXME: Would be nice to generate this instead of hardcoding it here.
[[maybe_unused]] static constexpr bool OpReturns(Opcode Op) {
return Op == OP_RetVoid || Op == OP_RetValue || Op == OP_NoRet ||
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 61118d77b7ac2..cbd569fe81777 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -82,9 +82,6 @@ bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr);
bool diagnoseUninitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
AccessKinds AK);
-bool diagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern,
- const Block *B, Lifetime LT = Lifetime::Started,
- AccessKinds AK = AK_Read);
/// Checks a direct load of a primitive value from a global or local variable.
bool CheckGlobalLoad(InterpState &S, CodePtr OpPC, const Block *B);
@@ -92,7 +89,7 @@ bool CheckLocalLoad(InterpState &S, CodePtr OpPC, const Block *B);
/// Checks if a value can be stored in a block.
bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
- bool WillBeActivated = false);
+ AccessKinds AK = AK_Assign, bool WillBeActivated = false);
/// Checks if a value can be initialized.
bool CheckInit(InterpState &S, CodePtr OpPC, const Pointer &Ptr);
@@ -2317,7 +2314,7 @@ bool StoreActivate(InterpState &S, CodePtr OpPC) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.peek<Pointer>();
- if (!CheckStore(S, OpPC, Ptr, /*WillBeActivated=*/true))
+ if (!CheckStore(S, OpPC, Ptr, AK_Assign, /*WillBeActivated=*/true))
return false;
if (Ptr.canBeInitialized()) {
Ptr.initialize();
@@ -2332,7 +2329,7 @@ bool StoreActivatePop(InterpState &S, CodePtr OpPC) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.pop<Pointer>();
- if (!CheckStore(S, OpPC, Ptr, /*WillBeActivated=*/true))
+ if (!CheckStore(S, OpPC, Ptr, AK_Assign, /*WillBeActivated=*/true))
return false;
if (Ptr.canBeInitialized()) {
Ptr.initialize();
@@ -2378,7 +2375,7 @@ bool StoreBitFieldActivate(InterpState &S, CodePtr OpPC) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.peek<Pointer>();
- if (!CheckStore(S, OpPC, Ptr, /*WillBeActivated=*/true))
+ if (!CheckStore(S, OpPC, Ptr, AK_Assign, /*WillBeActivated=*/true))
return false;
if (Ptr.canBeInitialized()) {
Ptr.initialize();
@@ -2396,7 +2393,7 @@ bool StoreBitFieldActivatePop(InterpState &S, CodePtr OpPC) {
const T &Value = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.pop<Pointer>();
- if (!CheckStore(S, OpPC, Ptr, /*WillBeActivated=*/true))
+ if (!CheckStore(S, OpPC, Ptr, AK_Assign, /*WillBeActivated=*/true))
return false;
if (Ptr.canBeInitialized()) {
Ptr.initialize();
@@ -2507,18 +2504,6 @@ bool InitElemPop(InterpState &S, CodePtr OpPC, uint32_t Idx) {
return true;
}
-inline bool Memcpy(InterpState &S, CodePtr OpPC) {
- const Pointer &Src = S.Stk.pop<Pointer>();
- Pointer &Dest = S.Stk.peek<Pointer>();
-
- if (!Src.getRecord() || !Src.getRecord()->isAnonymousUnion()) {
- if (!CheckLoad(S, OpPC, Src))
- return false;
- }
-
- return DoMemcpy(S, OpPC, Src, Dest);
-}
-
inline bool ToMemberPtr(InterpState &S) {
const auto &Member = S.Stk.pop<MemberPointer>();
const auto &Base = S.Stk.pop<Pointer>();
@@ -2537,6 +2522,10 @@ inline bool CastMemberPtrPtr(InterpState &S, CodePtr OpPC) {
return Invalid(S, OpPC);
}
+bool Memcpy(InterpState &S, CodePtr OpPC);
+bool TrivialCopy(InterpState &S, CodePtr OpPC, bool Activate,
+ const Function *Func);
+
//===----------------------------------------------------------------------===//
// AddOffset, SubOffset
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 0432308ef55f6..b9e96cf221c9a 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -6856,26 +6856,34 @@ static void zeroAll(PtrView Dest) {
}
static bool copyComposite(InterpState &S, CodePtr OpPC, PtrView Src,
- PtrView Dest, bool Activate);
+ PtrView Dest, bool Activate, bool Diagnose);
static bool copyRecord(InterpState &S, CodePtr OpPC, PtrView Src, PtrView Dest,
- bool Activate = false) {
+ bool Activate = false, bool Diagnose = true) {
[[maybe_unused]] const Descriptor *SrcDesc = Src.getFieldDesc();
const Descriptor *DestDesc = Dest.getFieldDesc();
auto copyField = [&](const Record::Field &F, bool Activate) -> bool {
PtrView DestField = Dest.atField(F.Offset);
+ PtrView SrcField = Src.atField(F.Offset);
+
if (OptPrimType FT = F.T) {
- TYPE_SWITCH(*FT, {
- DestField.deref<T>() = Src.atField(F.Offset).deref<T>();
- if (Src.atField(F.Offset).isInitialized())
- DestField.initialize();
- if (Activate)
- DestField.activate();
- });
+ if (!SrcField.isInitialized()) {
+ if (Diagnose)
+ return diagnoseUninitialized(S, OpPC, false, SrcField.block(),
+ SrcField.getLifetime(), AK_Read);
+ // Just skip.
+ return true;
+ }
+
+ TYPE_SWITCH(*FT, DestField.deref<T>() = SrcField.deref<T>(););
+ if (DestField.canBeInitialized())
+ DestField.initialize();
+ if (Activate)
+ DestField.activate();
return true;
}
- // Composite field.
- return copyComposite(S, OpPC, Src.atField(F.Offset), DestField, Activate);
+
+ return copyComposite(S, OpPC, SrcField, DestField, Activate, Diagnose);
};
assert(SrcDesc->isRecord());
@@ -6904,16 +6912,20 @@ static bool copyRecord(InterpState &S, CodePtr OpPC, PtrView Src, PtrView Dest,
for (const Record::Base &B : R->bases()) {
PtrView DestBase = Dest.atField(B.Offset);
- if (!copyRecord(S, OpPC, Src.atField(B.Offset), DestBase, Activate))
+ if (!copyRecord(S, OpPC, Src.atField(B.Offset), DestBase, Activate,
+ Diagnose))
return false;
}
Dest.initialize();
+ if (Activate)
+ Dest.activate();
return true;
}
static bool copyComposite(InterpState &S, CodePtr OpPC, PtrView Src,
- PtrView Dest, bool Activate = false) {
+ PtrView Dest, bool Activate = false,
+ bool Diagnose = false) {
assert(Src.isLive() && Dest.isLive());
[[maybe_unused]] const Descriptor *SrcDesc = Src.getFieldDesc();
@@ -6961,18 +6973,19 @@ static bool copyComposite(InterpState &S, CodePtr OpPC, PtrView Src,
if (DestDesc->isRecord()) {
if (!SrcDesc->isRecord())
return false;
- return copyRecord(S, OpPC, Src, Dest, Activate);
+ return copyRecord(S, OpPC, Src, Dest, Activate, Diagnose);
}
return Invalid(S, OpPC);
}
-bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest) {
+bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest,
+ bool Activate, bool Diagnose) {
if (!Src.isBlockPointer() || Src.getFieldDesc()->isPrimitive())
return false;
if (!Dest.isBlockPointer() || Dest.getFieldDesc()->isPrimitive())
return false;
- return copyComposite(S, OpPC, Src.view(), Dest.view());
+ return copyComposite(S, OpPC, Src.view(), Dest.view(), Activate, Diagnose);
}
} // namespace interp
diff --git a/clang/lib/AST/ByteCode/InterpHelpers.h b/clang/lib/AST/ByteCode/InterpHelpers.h
index 4c60670ac5a0c..713e6d2a592b2 100644
--- a/clang/lib/AST/ByteCode/InterpHelpers.h
+++ b/clang/lib/AST/ByteCode/InterpHelpers.h
@@ -80,12 +80,17 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC,
const Expr *NewExpr);
/// Copy the contents of Src into Dest.
-bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest);
+bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest,
+ bool Activate = true, bool Diagnose = false);
UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx,
unsigned Kind, Pointer &Ptr,
const Expr *E, bool IsDynamic = false);
+bool diagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern,
+ const Block *B, Lifetime LT = Lifetime::Started,
+ AccessKinds AK = AK_Read);
+
template <typename T>
bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) {
const Expr *E = S.Current->getExpr(OpPC);
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 4be5495a7ed25..ee9b15d533c68 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -955,6 +955,9 @@ def CheckNonNullArg : Opcode {
}
def Memcpy : Opcode;
+def TrivialCopy : Opcode {
+ let Args = [ArgBool, ArgFunction];
+}
def ToMemberPtr : SuccessOpcode;
def CastMemberPtrPtr : Opcode;
diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h
index fd09b4908f0b5..031acdee64d2a 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -53,6 +53,7 @@ struct PtrView {
bool inUnion() const { return getInlineDesc()->InUnion; };
bool inArray() const { return getFieldDesc()->IsArray; }
bool inPrimitiveArray() const { return getFieldDesc()->isPrimitiveArray(); }
+ bool canBeInitialized() const { return Pointee && Base > 0; }
const Block *block() const { return Pointee; }
unsigned getEvalID() { return Pointee->getEvalID(); }
diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h
index 0eee03dce57ab..2dec04b4d9932 100644
--- a/clang/lib/AST/ExprConstShared.h
+++ b/clang/lib/AST/ExprConstShared.h
@@ -112,4 +112,7 @@ EvalScalarMinMaxFp(const llvm::APFloat &A, const llvm::APFloat &B,
const Expr *ignorePointerCastsAndParens(const Expr *E);
+bool isReadByLvalueToRvalueConversion(const CXXRecordDecl *RD);
+bool isReadByLvalueToRvalueConversion(QualType T);
+
#endif
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index cc992309bc7a8..29beee30d6e1c 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -3697,12 +3697,12 @@ static void expandVector(APValue &Vec, unsigned NumElements) {
/// is trivial. Note that this is never true for a union type with fields
/// (because the copy always "reads" the active member) and always true for
/// a non-class type.
-static bool isReadByLvalueToRvalueConversion(const CXXRecordDecl *RD);
-static bool isReadByLvalueToRvalueConversion(QualType T) {
+bool isReadByLvalueToRvalueConversion(const CXXRecordDecl *RD);
+bool isReadByLvalueToRvalueConversion(QualType T) {
CXXRecordDecl *RD = T->getBaseElementTypeUnsafe()->getAsCXXRecordDecl();
return !RD || isReadByLvalueToRvalueConversion(RD);
}
-static bool isReadByLvalueToRvalueConversion(const CXXRecordDecl *RD) {
+bool isReadByLvalueToRvalueConversion(const CXXRecordDecl *RD) {
// FIXME: A trivial copy of a union copies the object representation, even if
// the union is empty.
if (RD->isUnion())
diff --git a/clang/test/AST/ByteCode/cxx14.cpp b/clang/test/AST/ByteCode/cxx14.cpp
index 97d2c0ac4f711..7dcf385bab084 100644
--- a/clang/test/AST/ByteCode/cxx14.cpp
+++ b/clang/test/AST/ByteCode/cxx14.cpp
@@ -47,3 +47,23 @@ namespace InitListModify {
constexpr Aggregate aggr2 = {};
static_assert(aggr2.x == 1 && aggr2.y == 1, "");
}
+
+namespace UnionTrivialCopy {
+ struct A {
+ constexpr A() {}
+ struct B {
+ union U {
+ constexpr U() : y(4) {}
+ int x;
+ int y;
+ } u;
+ } b;
+ };
+ constexpr int testA() {
+ A a, b;
+ a.b.u.y = 5;
+ b = a;
+ return b.b.u.y;
+ }
+ static_assert(testA() == 5, "");
+}
diff --git a/clang/test/AST/ByteCode/cxx26.cpp b/clang/test/AST/ByteCode/cxx26.cpp
index 9dc6270b9d551..68da8bae0e946 100644
--- a/clang/test/AST/ByteCode/cxx26.cpp
+++ b/clang/test/AST/ByteCode/cxx26.cpp
@@ -103,3 +103,59 @@ namespace UnknownSizeArrayString {
// ref-note {{initializer of 'foo' is unknown}} \
// both-error {{static assertion failed}}
}
+
+namespace TrivialAssignment {
+ struct array {
+ int _M_elems[4];
+ };
+
+ struct item {
+ array words;
+ unsigned priority;
+ };
+
+ constexpr void __insertion_sort(item * __first,
+ item *__last) {
+ *__first = *__first;
+ }
+
+ consteval unsigned sorted_first() {
+ item items[]{{{}, 1}};
+ __insertion_sort(items, items + 1);
+
+ return items[0].priority;
+ }
+ static_assert(sorted_first());
+
+ constexpr void __insertion_sort2(item * __first,
+ item *__last) {
+ *__first = *__last; // both-note {{read of dereferenced one-past-the-end pointer}} \
+ // both-note {{in call to}}
+ }
+
+ consteval unsigned sorted_first2() {
+ item items[]{{{}, 1}};
+ __insertion_sort2(items, items + 1); // both-note {{in call}}
+
+ return items[0].priority;
+ }
+ static_assert(sorted_first2()); // both-error {{not an integral constant expression}} \
+ // both-note {{in call}}
+
+
+
+ constexpr void __insertion_sort3(item * __first,
+ item *__last) {
+ *__last = *__first; // both-note {{member call on dereferenced one-past-the-end pointer}}
+ }
+
+ consteval unsigned sorted_first3() {
+ item items[]{{{}, 1}};
+ __insertion_sort3(items, items + 1); // both-note {{in call to '__insertion_sort3(&items[0], &items[1])'}}
+
+ return items[0].priority;
+ }
+ static_assert(sorted_first3()); // bo...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/222332
More information about the cfe-commits
mailing list