https://github.com/Fznamznon updated https://github.com/llvm/llvm-project/pull/236803
>From 3e885303010148ac74bfee16ac7f1fa1d15bad13 Mon Sep 17 00:00:00 2001 From: Mariya Podchishchaeva <[email protected]> Date: Thu, 29 Oct 2026 07:25:42 -0500 Subject: [PATCH 01/11] Bring FieldMemcpyizer --- clang/lib/CIR/CodeGen/CIRGenClass.cpp | 162 ++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp index 738f5464672435..b931be6365f3c8 100644 --- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -24,6 +24,168 @@ using namespace clang; using namespace clang::CIRGen; +namespace { +/// TODO(cir): a lot of what we see under this namespace is a strong candidate +/// to be shared between LLVM and CIR codegen. + +/// RAII object to indicate that codegen is copying the value representation +/// instead of the object representation. Useful when copying a struct or +/// class which has uninitialized members and we're only performing +/// lvalue-to-rvalue conversion on the object but not its members. +class CopyingValueRepresentation { +public: + explicit CopyingValueRepresentation(CIRGenFunction &CGF) + : CGF(CGF), OldSanOpts(CGF.sanOpts) { + CGF.sanOpts.set(SanitizerKind::Bool, false); + CGF.sanOpts.set(SanitizerKind::Enum, false); + } + ~CopyingValueRepresentation() { CGF.sanOpts = OldSanOpts; } + +private: + CIRGenFunction &CGF; + SanitizerSet OldSanOpts; +}; + +class FieldMemcpyizer { +public: + FieldMemcpyizer(CIRGenFunction &CGF, const CXXMethodDecl *MethodDecl, + const VarDecl *SrcRec) + : CGF(CGF), MethodDecl(MethodDecl), ClassDecl(MethodDecl->getParent()), + SrcRec(SrcRec), + RecLayout(CGF.getContext().getASTRecordLayout(ClassDecl)), + FirstField(nullptr), LastField(nullptr), FirstFieldOffset(0), + LastFieldOffset(0), LastAddedFieldIndex(0) {} + + bool isMemcpyableField(FieldDecl *F) const { + // Never memcpy fields when we are adding poised paddings. + if (CGF.getContext().getLangOpts().SanitizeAddressFieldPadding) + return false; + Qualifiers Qual = F->getType().getQualifiers(); + if (Qual.hasVolatile() || Qual.hasObjCLifetime()) + return false; + return true; + } + + void addMemcpyableField(FieldDecl *F) { + //assert(!cir::MissingFeatures::isEmptyFieldForLayout()); + if (F->isZeroSize(CGF.getContext())) + return; + if (!FirstField) + addInitialField(F); + else + addNextField(F); + } + + CharUnits getMemcpySize(uint64_t FirstByteOffset) const { + ASTContext &astContext = CGF.getContext(); + unsigned LastFieldSize = + LastField->isBitField() + ? LastField->getBitWidthValue() + : astContext.toBits( + astContext.getTypeInfoDataSizeInChars(LastField->getType()) + .Width); + uint64_t MemcpySizeBits = LastFieldOffset + LastFieldSize - + FirstByteOffset + astContext.getCharWidth() - 1; + CharUnits MemcpySize = astContext.toCharUnitsFromBits(MemcpySizeBits); + return MemcpySize; + } + + void emitMemcpy() { + // Give the subclass a chance to bail out if it feels the memcpy isn't worth + // it (e.g. Hasn't aggregated enough data). + if (!FirstField) { + return; + } + + uint64_t firstByteOffset; + if (FirstField->isBitField()) { + const CIRGenRecordLayout &rl = + CGF.getTypes().getCIRGenRecordLayout(FirstField->getParent()); + const CIRGenBitFieldInfo &bfInfo = rl.getBitFieldInfo(FirstField); + // FirstFieldOffset is not appropriate for bitfields, + // we need to use the storage offset instead. + firstByteOffset = CGF.getContext().toBits(bfInfo.storageOffset); + } else { + firstByteOffset = FirstFieldOffset; + } + + CharUnits memcpySize = getMemcpySize(firstByteOffset); + QualType recordTy = CGF.getContext().getTagType( + ElaboratedTypeKeyword::None, /*Qualifier=*/std::nullopt, ClassDecl, + /*OwnsTag=*/false); + Address thisPtr = CGF.loadCXXThisAddress(); + LValue destLv = CGF.makeAddrLValue(thisPtr, recordTy); + LValue dest = CGF.emitLValueForFieldInitialization(destLv, FirstField, + FirstField->getName()); + cir::LoadOp srcPtr = CGF.getBuilder().createLoad( + CGF.getLoc(MethodDecl->getLocation()), CGF.getAddrOfLocalVar(SrcRec)); + LValue srcLv = CGF.makeNaturalAlignAddrLValue(srcPtr, recordTy); + LValue src = CGF.emitLValueForFieldInitialization(srcLv, FirstField, + FirstField->getName()); + + emitMemcpyIR(dest.isBitField() ? dest.getBitFieldAddress() + : dest.getAddress(), + src.isBitField() ? src.getBitFieldAddress() : src.getAddress(), + memcpySize); + reset(); + } + + void reset() { FirstField = nullptr; } + +protected: + CIRGenFunction &CGF; + const CXXMethodDecl *MethodDecl; + const CXXRecordDecl *ClassDecl; + +private: + void emitMemcpyIR(Address DestPtr, Address SrcPtr, CharUnits Size) { + mlir::Location loc = CGF.getLoc(MethodDecl->getLocation()); + CIRGenBuilderTy &builder = CGF.getBuilder(); + mlir::Value sizeOp = + builder.getConstInt(loc, CGF.sizeTy, Size.getQuantity()); + builder.createMemCpy(loc, DestPtr.withElementType(builder, CGF.voidTy), + SrcPtr.withElementType(builder, CGF.voidTy), sizeOp); + } + + void addInitialField(FieldDecl *F) { + FirstField = F; + LastField = F; + FirstFieldOffset = RecLayout.getFieldOffset(F->getFieldIndex()); + LastFieldOffset = FirstFieldOffset; + LastAddedFieldIndex = F->getFieldIndex(); + } + + void addNextField(FieldDecl *F) { + // For the most part, the following invariant will hold: + // F->getFieldIndex() == LastAddedFieldIndex + 1 + // The one exception is that Sema won't add a copy-initializer for an + // unnamed bitfield, which will show up here as a gap in the sequence. + assert(F->getFieldIndex() >= LastAddedFieldIndex + 1 && + "Cannot aggregate fields out of order."); + LastAddedFieldIndex = F->getFieldIndex(); + + // The 'first' and 'last' fields are chosen by offset, rather than field + // index. This allows the code to support bitfields, as well as regular + // fields. + uint64_t FOffset = RecLayout.getFieldOffset(F->getFieldIndex()); + if (FOffset < FirstFieldOffset) { + FirstField = F; + FirstFieldOffset = FOffset; + } else if (FOffset >= LastFieldOffset) { + LastField = F; + LastFieldOffset = FOffset; + } + } + + const VarDecl *SrcRec; + const ASTRecordLayout &RecLayout; + FieldDecl *FirstField; + FieldDecl *LastField; + uint64_t FirstFieldOffset, LastFieldOffset; + unsigned LastAddedFieldIndex; +}; +} + /// Return the smallest possible amount of storage that might be allocated /// starting from the beginning of an object of a particular class. /// >From 7f37051252d91b6185a0465f59b5ed6f0338ee79 Mon Sep 17 00:00:00 2001 From: Mariya Podchishchaeva <[email protected]> Date: Thu, 29 Oct 2026 07:28:07 -0500 Subject: [PATCH 02/11] Bring assign memcpyizer --- clang/lib/CIR/CodeGen/CIRGenClass.cpp | 107 +++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 2 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp index b931be6365f3c8..2c8517815462ab 100644 --- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -67,7 +67,7 @@ class FieldMemcpyizer { } void addMemcpyableField(FieldDecl *F) { - //assert(!cir::MissingFeatures::isEmptyFieldForLayout()); + // assert(!cir::MissingFeatures::isEmptyFieldForLayout()); if (F->isZeroSize(CGF.getContext())) return; if (!FirstField) @@ -184,7 +184,110 @@ class FieldMemcpyizer { uint64_t FirstFieldOffset, LastFieldOffset; unsigned LastAddedFieldIndex; }; -} + +class AssignmentMemcpyizer : public FieldMemcpyizer { +private: + // Returns the memcpyable field copied by the given statement, if one + // exists. Otherwise returns null. + FieldDecl *getMemcpyableField(Stmt *S) { + if (!AssignmentsMemcpyable) + return nullptr; + if (BinaryOperator *BO = dyn_cast<BinaryOperator>(S)) { + // Recognise trivial assignments. + if (BO->getOpcode() != BO_Assign) + return nullptr; + MemberExpr *ME = dyn_cast<MemberExpr>(BO->getLHS()); + if (!ME) + return nullptr; + FieldDecl *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); + if (!Field || !isMemcpyableField(Field)) + return nullptr; + Stmt *RHS = BO->getRHS(); + if (ImplicitCastExpr *EC = dyn_cast<ImplicitCastExpr>(RHS)) + RHS = EC->getSubExpr(); + if (!RHS) + return nullptr; + if (MemberExpr *ME2 = dyn_cast<MemberExpr>(RHS)) { + if (ME2->getMemberDecl() == Field) + return Field; + } + return nullptr; + } else if (isa<CXXMemberCallExpr>(S)) { + // We want to represent all calls explicitly for analysis purposes. + return nullptr; + } else if (CallExpr *CE = dyn_cast<CallExpr>(S)) { + // TODO(cir): https://github.com/llvm/clangir/issues/1177: This can result + // in memcpys instead of calls to trivial member functions. + FunctionDecl *FD = dyn_cast<FunctionDecl>(CE->getCalleeDecl()); + if (!FD || FD->getBuiltinID() != Builtin::BI__builtin_memcpy) + return nullptr; + Expr *DstPtr = CE->getArg(0); + if (ImplicitCastExpr *DC = dyn_cast<ImplicitCastExpr>(DstPtr)) + DstPtr = DC->getSubExpr(); + UnaryOperator *DUO = dyn_cast<UnaryOperator>(DstPtr); + if (!DUO || DUO->getOpcode() != UO_AddrOf) + return nullptr; + MemberExpr *ME = dyn_cast<MemberExpr>(DUO->getSubExpr()); + if (!ME) + return nullptr; + FieldDecl *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); + if (!Field || !isMemcpyableField(Field)) + return nullptr; + Expr *SrcPtr = CE->getArg(1); + if (ImplicitCastExpr *SC = dyn_cast<ImplicitCastExpr>(SrcPtr)) + SrcPtr = SC->getSubExpr(); + UnaryOperator *SUO = dyn_cast<UnaryOperator>(SrcPtr); + if (!SUO || SUO->getOpcode() != UO_AddrOf) + return nullptr; + MemberExpr *ME2 = dyn_cast<MemberExpr>(SUO->getSubExpr()); + if (!ME2 || Field != dyn_cast<FieldDecl>(ME2->getMemberDecl())) + return nullptr; + return Field; + } + + return nullptr; + } + + bool AssignmentsMemcpyable; + SmallVector<Stmt *, 16> AggregatedStmts; + +public: + AssignmentMemcpyizer(CIRGenFunction &CGF, const CXXMethodDecl *AD, + FunctionArgList &Args) + : FieldMemcpyizer(CGF, AD, Args[Args.size() - 1]), + AssignmentsMemcpyable(CGF.getLangOpts().getGC() == LangOptions::NonGC) { + assert(Args.size() == 2); + } + + void emitAssignment(Stmt *S) { + FieldDecl *F = getMemcpyableField(S); + if (F) { + addMemcpyableField(F); + AggregatedStmts.push_back(S); + } else { + emitAggregatedStmts(); + if (CGF.emitStmt(S, /*useCurrentScope=*/true).failed()) + llvm_unreachable("Should not get here!"); + } + } + + void emitAggregatedStmts() { + if (AggregatedStmts.size() <= 1) { + if (!AggregatedStmts.empty()) { + CopyingValueRepresentation CVR(CGF); + if (CGF.emitStmt(AggregatedStmts[0], /*useCurrentScope=*/true).failed()) + llvm_unreachable("Should not get here!"); + } + reset(); + } + + emitMemcpy(); + AggregatedStmts.clear(); + } + + void finish() { emitAggregatedStmts(); } +}; +} // namespace /// Return the smallest possible amount of storage that might be allocated /// starting from the beginning of an object of a particular class. >From 112b628791a7a469064a11f2cadbd90cd117f817 Mon Sep 17 00:00:00 2001 From: Mariya Podchishchaeva <[email protected]> Date: Thu, 29 Oct 2026 07:44:23 -0500 Subject: [PATCH 03/11] Align codestyle, claude-assisted --- clang/lib/CIR/CodeGen/CIRGenClass.cpp | 293 +++++++++++++------------- 1 file changed, 147 insertions(+), 146 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp index 2c8517815462ab..ce985cd77665fa 100644 --- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -25,8 +25,8 @@ using namespace clang; using namespace clang::CIRGen; namespace { -/// TODO(cir): a lot of what we see under this namespace is a strong candidate -/// to be shared between LLVM and CIR codegen. +// TODO(cir): a lot of what we see under this namespace is a strong candidate +// to be shared between LLVM and CIR codegen. /// RAII object to indicate that codegen is copying the value representation /// instead of the object representation. Useful when copying a struct or @@ -34,94 +34,93 @@ namespace { /// lvalue-to-rvalue conversion on the object but not its members. class CopyingValueRepresentation { public: - explicit CopyingValueRepresentation(CIRGenFunction &CGF) - : CGF(CGF), OldSanOpts(CGF.sanOpts) { - CGF.sanOpts.set(SanitizerKind::Bool, false); - CGF.sanOpts.set(SanitizerKind::Enum, false); + explicit CopyingValueRepresentation(CIRGenFunction &cgf) + : cgf(cgf), oldSanOpts(cgf.sanOpts) { + cgf.sanOpts.set(SanitizerKind::Bool, false); + cgf.sanOpts.set(SanitizerKind::Enum, false); } - ~CopyingValueRepresentation() { CGF.sanOpts = OldSanOpts; } + ~CopyingValueRepresentation() { cgf.sanOpts = oldSanOpts; } private: - CIRGenFunction &CGF; - SanitizerSet OldSanOpts; + CIRGenFunction &cgf; + SanitizerSet oldSanOpts; }; class FieldMemcpyizer { public: - FieldMemcpyizer(CIRGenFunction &CGF, const CXXMethodDecl *MethodDecl, - const VarDecl *SrcRec) - : CGF(CGF), MethodDecl(MethodDecl), ClassDecl(MethodDecl->getParent()), - SrcRec(SrcRec), - RecLayout(CGF.getContext().getASTRecordLayout(ClassDecl)), - FirstField(nullptr), LastField(nullptr), FirstFieldOffset(0), - LastFieldOffset(0), LastAddedFieldIndex(0) {} - - bool isMemcpyableField(FieldDecl *F) const { + FieldMemcpyizer(CIRGenFunction &cgf, const CXXMethodDecl *methodDecl, + const VarDecl *srcRec) + : cgf(cgf), methodDecl(methodDecl), classDecl(methodDecl->getParent()), + srcRec(srcRec), + recLayout(cgf.getContext().getASTRecordLayout(classDecl)), + firstField(nullptr), lastField(nullptr), firstFieldOffset(0), + lastFieldOffset(0), lastAddedFieldIndex(0) {} + + bool isMemcpyableField(FieldDecl *f) const { // Never memcpy fields when we are adding poised paddings. - if (CGF.getContext().getLangOpts().SanitizeAddressFieldPadding) + if (cgf.getContext().getLangOpts().SanitizeAddressFieldPadding) return false; - Qualifiers Qual = F->getType().getQualifiers(); - if (Qual.hasVolatile() || Qual.hasObjCLifetime()) + Qualifiers qual = f->getType().getQualifiers(); + if (qual.hasVolatile() || qual.hasObjCLifetime()) return false; return true; } - void addMemcpyableField(FieldDecl *F) { - // assert(!cir::MissingFeatures::isEmptyFieldForLayout()); - if (F->isZeroSize(CGF.getContext())) + void addMemcpyableField(FieldDecl *f) { + // TODO(cir): isEmptyFieldForLayout is not handled here yet. + // Incubator has assert(!cir::MissingFeatures::isEmptyFieldForLayout()); + if (f->isZeroSize(cgf.getContext())) return; - if (!FirstField) - addInitialField(F); + if (!firstField) + addInitialField(f); else - addNextField(F); + addNextField(f); } - CharUnits getMemcpySize(uint64_t FirstByteOffset) const { - ASTContext &astContext = CGF.getContext(); - unsigned LastFieldSize = - LastField->isBitField() - ? LastField->getBitWidthValue() + CharUnits getMemcpySize(uint64_t firstByteOffset) const { + ASTContext &astContext = cgf.getContext(); + unsigned lastFieldSize = + lastField->isBitField() + ? lastField->getBitWidthValue() : astContext.toBits( - astContext.getTypeInfoDataSizeInChars(LastField->getType()) + astContext.getTypeInfoDataSizeInChars(lastField->getType()) .Width); - uint64_t MemcpySizeBits = LastFieldOffset + LastFieldSize - - FirstByteOffset + astContext.getCharWidth() - 1; - CharUnits MemcpySize = astContext.toCharUnitsFromBits(MemcpySizeBits); - return MemcpySize; + uint64_t memcpySizeBits = lastFieldOffset + lastFieldSize - + firstByteOffset + astContext.getCharWidth() - 1; + return astContext.toCharUnitsFromBits(memcpySizeBits); } void emitMemcpy() { // Give the subclass a chance to bail out if it feels the memcpy isn't worth // it (e.g. Hasn't aggregated enough data). - if (!FirstField) { + if (!firstField) return; - } uint64_t firstByteOffset; - if (FirstField->isBitField()) { + if (firstField->isBitField()) { const CIRGenRecordLayout &rl = - CGF.getTypes().getCIRGenRecordLayout(FirstField->getParent()); - const CIRGenBitFieldInfo &bfInfo = rl.getBitFieldInfo(FirstField); - // FirstFieldOffset is not appropriate for bitfields, + cgf.getTypes().getCIRGenRecordLayout(firstField->getParent()); + const CIRGenBitFieldInfo &bfInfo = rl.getBitFieldInfo(firstField); + // firstFieldOffset is not appropriate for bitfields, // we need to use the storage offset instead. - firstByteOffset = CGF.getContext().toBits(bfInfo.storageOffset); + firstByteOffset = cgf.getContext().toBits(bfInfo.storageOffset); } else { - firstByteOffset = FirstFieldOffset; + firstByteOffset = firstFieldOffset; } CharUnits memcpySize = getMemcpySize(firstByteOffset); - QualType recordTy = CGF.getContext().getTagType( - ElaboratedTypeKeyword::None, /*Qualifier=*/std::nullopt, ClassDecl, + QualType recordTy = cgf.getContext().getTagType( + ElaboratedTypeKeyword::None, /*Qualifier=*/std::nullopt, classDecl, /*OwnsTag=*/false); - Address thisPtr = CGF.loadCXXThisAddress(); - LValue destLv = CGF.makeAddrLValue(thisPtr, recordTy); - LValue dest = CGF.emitLValueForFieldInitialization(destLv, FirstField, - FirstField->getName()); - cir::LoadOp srcPtr = CGF.getBuilder().createLoad( - CGF.getLoc(MethodDecl->getLocation()), CGF.getAddrOfLocalVar(SrcRec)); - LValue srcLv = CGF.makeNaturalAlignAddrLValue(srcPtr, recordTy); - LValue src = CGF.emitLValueForFieldInitialization(srcLv, FirstField, - FirstField->getName()); + Address thisPtr = cgf.loadCXXThisAddress(); + LValue destLv = cgf.makeAddrLValue(thisPtr, recordTy); + LValue dest = cgf.emitLValueForFieldInitialization(destLv, firstField, + firstField->getName()); + cir::LoadOp srcPtr = cgf.getBuilder().createLoad( + cgf.getLoc(methodDecl->getLocation()), cgf.getAddrOfLocalVar(srcRec)); + LValue srcLv = cgf.makeNaturalAlignAddrLValue(srcPtr, recordTy); + LValue src = cgf.emitLValueForFieldInitialization(srcLv, firstField, + firstField->getName()); emitMemcpyIR(dest.isBitField() ? dest.getBitFieldAddress() : dest.getAddress(), @@ -130,159 +129,161 @@ class FieldMemcpyizer { reset(); } - void reset() { FirstField = nullptr; } + void reset() { firstField = nullptr; } protected: - CIRGenFunction &CGF; - const CXXMethodDecl *MethodDecl; - const CXXRecordDecl *ClassDecl; + CIRGenFunction &cgf; + const CXXMethodDecl *methodDecl; + const CXXRecordDecl *classDecl; private: - void emitMemcpyIR(Address DestPtr, Address SrcPtr, CharUnits Size) { - mlir::Location loc = CGF.getLoc(MethodDecl->getLocation()); - CIRGenBuilderTy &builder = CGF.getBuilder(); + void emitMemcpyIR(Address destPtr, Address srcPtr, CharUnits size) { + mlir::Location loc = cgf.getLoc(methodDecl->getLocation()); + CIRGenBuilderTy &builder = cgf.getBuilder(); mlir::Value sizeOp = - builder.getConstInt(loc, CGF.sizeTy, Size.getQuantity()); - builder.createMemCpy(loc, DestPtr.withElementType(builder, CGF.voidTy), - SrcPtr.withElementType(builder, CGF.voidTy), sizeOp); + builder.getConstInt(loc, cgf.sizeTy, size.getQuantity()); + builder.createMemCpy(loc, destPtr.withElementType(builder, cgf.voidTy), + srcPtr.withElementType(builder, cgf.voidTy), sizeOp); } - void addInitialField(FieldDecl *F) { - FirstField = F; - LastField = F; - FirstFieldOffset = RecLayout.getFieldOffset(F->getFieldIndex()); - LastFieldOffset = FirstFieldOffset; - LastAddedFieldIndex = F->getFieldIndex(); + void addInitialField(FieldDecl *f) { + firstField = f; + lastField = f; + firstFieldOffset = recLayout.getFieldOffset(f->getFieldIndex()); + lastFieldOffset = firstFieldOffset; + lastAddedFieldIndex = f->getFieldIndex(); } - void addNextField(FieldDecl *F) { + void addNextField(FieldDecl *f) { // For the most part, the following invariant will hold: - // F->getFieldIndex() == LastAddedFieldIndex + 1 + // f->getFieldIndex() == lastAddedFieldIndex + 1 // The one exception is that Sema won't add a copy-initializer for an // unnamed bitfield, which will show up here as a gap in the sequence. - assert(F->getFieldIndex() >= LastAddedFieldIndex + 1 && + assert(f->getFieldIndex() >= lastAddedFieldIndex + 1 && "Cannot aggregate fields out of order."); - LastAddedFieldIndex = F->getFieldIndex(); + lastAddedFieldIndex = f->getFieldIndex(); // The 'first' and 'last' fields are chosen by offset, rather than field // index. This allows the code to support bitfields, as well as regular // fields. - uint64_t FOffset = RecLayout.getFieldOffset(F->getFieldIndex()); - if (FOffset < FirstFieldOffset) { - FirstField = F; - FirstFieldOffset = FOffset; - } else if (FOffset >= LastFieldOffset) { - LastField = F; - LastFieldOffset = FOffset; + uint64_t fOffset = recLayout.getFieldOffset(f->getFieldIndex()); + if (fOffset < firstFieldOffset) { + firstField = f; + firstFieldOffset = fOffset; + } else if (fOffset >= lastFieldOffset) { + lastField = f; + lastFieldOffset = fOffset; } } - const VarDecl *SrcRec; - const ASTRecordLayout &RecLayout; - FieldDecl *FirstField; - FieldDecl *LastField; - uint64_t FirstFieldOffset, LastFieldOffset; - unsigned LastAddedFieldIndex; + const VarDecl *srcRec; + const ASTRecordLayout &recLayout; + FieldDecl *firstField; + FieldDecl *lastField; + uint64_t firstFieldOffset, lastFieldOffset; + unsigned lastAddedFieldIndex; }; class AssignmentMemcpyizer : public FieldMemcpyizer { -private: // Returns the memcpyable field copied by the given statement, if one // exists. Otherwise returns null. - FieldDecl *getMemcpyableField(Stmt *S) { - if (!AssignmentsMemcpyable) + FieldDecl *getMemcpyableField(Stmt *s) { + if (!assignmentsMemcpyable) return nullptr; - if (BinaryOperator *BO = dyn_cast<BinaryOperator>(S)) { + if (auto *bo = dyn_cast<BinaryOperator>(s)) { // Recognise trivial assignments. - if (BO->getOpcode() != BO_Assign) + if (bo->getOpcode() != BO_Assign) return nullptr; - MemberExpr *ME = dyn_cast<MemberExpr>(BO->getLHS()); - if (!ME) + auto *me = dyn_cast<MemberExpr>(bo->getLHS()); + if (!me) return nullptr; - FieldDecl *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); - if (!Field || !isMemcpyableField(Field)) + auto *field = dyn_cast<FieldDecl>(me->getMemberDecl()); + if (!field || !isMemcpyableField(field)) return nullptr; - Stmt *RHS = BO->getRHS(); - if (ImplicitCastExpr *EC = dyn_cast<ImplicitCastExpr>(RHS)) - RHS = EC->getSubExpr(); - if (!RHS) + Stmt *rhs = bo->getRHS(); + if (auto *ec = dyn_cast<ImplicitCastExpr>(rhs)) + rhs = ec->getSubExpr(); + if (!rhs) return nullptr; - if (MemberExpr *ME2 = dyn_cast<MemberExpr>(RHS)) { - if (ME2->getMemberDecl() == Field) - return Field; + if (auto *me2 = dyn_cast<MemberExpr>(rhs)) { + if (me2->getMemberDecl() == field) + return field; } return nullptr; - } else if (isa<CXXMemberCallExpr>(S)) { + } else if (isa<CXXMemberCallExpr>(s)) { // We want to represent all calls explicitly for analysis purposes. return nullptr; - } else if (CallExpr *CE = dyn_cast<CallExpr>(S)) { + } else if (auto *ce = dyn_cast<CallExpr>(s)) { // TODO(cir): https://github.com/llvm/clangir/issues/1177: This can result // in memcpys instead of calls to trivial member functions. - FunctionDecl *FD = dyn_cast<FunctionDecl>(CE->getCalleeDecl()); - if (!FD || FD->getBuiltinID() != Builtin::BI__builtin_memcpy) + auto *fd = dyn_cast<FunctionDecl>(ce->getCalleeDecl()); + if (!fd || fd->getBuiltinID() != Builtin::BI__builtin_memcpy) return nullptr; - Expr *DstPtr = CE->getArg(0); - if (ImplicitCastExpr *DC = dyn_cast<ImplicitCastExpr>(DstPtr)) - DstPtr = DC->getSubExpr(); - UnaryOperator *DUO = dyn_cast<UnaryOperator>(DstPtr); - if (!DUO || DUO->getOpcode() != UO_AddrOf) + Expr *dstPtr = ce->getArg(0); + if (auto *dc = dyn_cast<ImplicitCastExpr>(dstPtr)) + dstPtr = dc->getSubExpr(); + auto *duo = dyn_cast<UnaryOperator>(dstPtr); + if (!duo || duo->getOpcode() != UO_AddrOf) return nullptr; - MemberExpr *ME = dyn_cast<MemberExpr>(DUO->getSubExpr()); - if (!ME) + auto *me = dyn_cast<MemberExpr>(duo->getSubExpr()); + if (!me) return nullptr; - FieldDecl *Field = dyn_cast<FieldDecl>(ME->getMemberDecl()); - if (!Field || !isMemcpyableField(Field)) + auto *field = dyn_cast<FieldDecl>(me->getMemberDecl()); + if (!field || !isMemcpyableField(field)) return nullptr; - Expr *SrcPtr = CE->getArg(1); - if (ImplicitCastExpr *SC = dyn_cast<ImplicitCastExpr>(SrcPtr)) - SrcPtr = SC->getSubExpr(); - UnaryOperator *SUO = dyn_cast<UnaryOperator>(SrcPtr); - if (!SUO || SUO->getOpcode() != UO_AddrOf) + Expr *srcPtr = ce->getArg(1); + if (auto *sc = dyn_cast<ImplicitCastExpr>(srcPtr)) + srcPtr = sc->getSubExpr(); + auto *suo = dyn_cast<UnaryOperator>(srcPtr); + if (!suo || suo->getOpcode() != UO_AddrOf) return nullptr; - MemberExpr *ME2 = dyn_cast<MemberExpr>(SUO->getSubExpr()); - if (!ME2 || Field != dyn_cast<FieldDecl>(ME2->getMemberDecl())) + auto *me2 = dyn_cast<MemberExpr>(suo->getSubExpr()); + if (!me2 || field != dyn_cast<FieldDecl>(me2->getMemberDecl())) return nullptr; - return Field; + return field; } return nullptr; } - bool AssignmentsMemcpyable; - SmallVector<Stmt *, 16> AggregatedStmts; + bool assignmentsMemcpyable; + SmallVector<Stmt *, 16> aggregatedStmts; public: - AssignmentMemcpyizer(CIRGenFunction &CGF, const CXXMethodDecl *AD, - FunctionArgList &Args) - : FieldMemcpyizer(CGF, AD, Args[Args.size() - 1]), - AssignmentsMemcpyable(CGF.getLangOpts().getGC() == LangOptions::NonGC) { - assert(Args.size() == 2); + AssignmentMemcpyizer(CIRGenFunction &cgf, const CXXMethodDecl *ad, + FunctionArgList &args) + : FieldMemcpyizer(cgf, ad, args[args.size() - 1]), + assignmentsMemcpyable(cgf.getLangOpts().getGC() == LangOptions::NonGC) { + assert(args.size() == 2); } - void emitAssignment(Stmt *S) { - FieldDecl *F = getMemcpyableField(S); - if (F) { - addMemcpyableField(F); - AggregatedStmts.push_back(S); + void emitAssignment(Stmt *s) { + FieldDecl *f = getMemcpyableField(s); + if (f) { + addMemcpyableField(f); + aggregatedStmts.push_back(s); } else { emitAggregatedStmts(); - if (CGF.emitStmt(S, /*useCurrentScope=*/true).failed()) - llvm_unreachable("Should not get here!"); + if (cgf.emitStmt(s, /*useCurrentScope=*/true).failed()) + cgf.cgm.errorNYI(s->getSourceRange(), std::string("emitAssignment: ") + + s->getStmtClassName()); } } void emitAggregatedStmts() { - if (AggregatedStmts.size() <= 1) { - if (!AggregatedStmts.empty()) { - CopyingValueRepresentation CVR(CGF); - if (CGF.emitStmt(AggregatedStmts[0], /*useCurrentScope=*/true).failed()) - llvm_unreachable("Should not get here!"); + if (aggregatedStmts.size() <= 1) { + if (!aggregatedStmts.empty()) { + CopyingValueRepresentation cvr(cgf); + if (cgf.emitStmt(aggregatedStmts[0], /*useCurrentScope=*/true).failed()) + cgf.cgm.errorNYI(aggregatedStmts[0]->getSourceRange(), + std::string("emitAggregatedStmts: ") + + aggregatedStmts[0]->getStmtClassName()); } reset(); } emitMemcpy(); - AggregatedStmts.clear(); + aggregatedStmts.clear(); } void finish() { emitAggregatedStmts(); } >From cdb364aecaa82e5c9201328dec57d014c396b39f Mon Sep 17 00:00:00 2001 From: Mariya Podchishchaeva <[email protected]> Date: Thu, 29 Oct 2026 09:08:28 -0500 Subject: [PATCH 04/11] Use the new class --- clang/include/clang/CIR/MissingFeatures.h | 1 - clang/lib/CIR/CodeGen/CIRGenClass.cpp | 10 ++++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 1348206f1738ad..01cec9d3f31ece 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -189,7 +189,6 @@ struct MissingFeatures { static bool asmMemoryEffects() { return false; } static bool asmUnwindClobber() { return false; } static bool asmVectorType() { return false; } - static bool assignMemcpyizer() { return false; } static bool astVarDeclInterface() { return false; } static bool attributeNoBuiltin() { return false; } static bool branchProtection() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp index ce985cd77665fa..e19158126e0f25 100644 --- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -1144,12 +1144,10 @@ void CIRGenFunction::emitImplicitAssignmentOperatorBody(FunctionArgList &args) { // lowering or optimization phases to keep the memory accesses more // explicit. For now, we don't insert memcpy at all, though in some // cases the AST contains a call to memcpy. - assert(!cir::MissingFeatures::assignMemcpyizer()); - for (Stmt *s : rootCS->body()) - if (emitStmt(s, /*useCurrentScope=*/true).failed()) - cgm.errorNYI(s->getSourceRange(), - std::string("emitImplicitAssignmentOperatorBody: ") + - s->getStmtClassName()); + AssignmentMemcpyizer AM(*this, assignOp, args); + for (auto *i : rootCS->body()) + AM.emitAssignment(i); + AM.finish(); } void CIRGenFunction::emitForwardingCallToLambda( >From b39f1bc3e5957224685750c772512f0428a352bf Mon Sep 17 00:00:00 2001 From: Mariya Podchishchaeva <[email protected]> Date: Thu, 29 Oct 2026 09:10:05 -0500 Subject: [PATCH 05/11] Add ported test, claude assisted --- .../CIR/CodeGen/assign-operator-memcpy.cpp | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 clang/test/CIR/CodeGen/assign-operator-memcpy.cpp diff --git a/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp new file mode 100644 index 00000000000000..b1b57898238206 --- /dev/null +++ b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp @@ -0,0 +1,83 @@ +// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s + +// Runs of adjacent trivially copyable fields in an implicitly defined copy +// assignment operator are merged into a single memcpy, as classic codegen does. + +struct HasNonTrivialAssignOp { + HasNonTrivialAssignOp &operator=(const HasNonTrivialAssignOp &); +}; + +struct ContainsNonTrivial { + HasNonTrivialAssignOp start; + int i; + int *j; + HasNonTrivialAssignOp middle; + int k : 4; + int l : 4; + int m : 4; + HasNonTrivialAssignOp end; + ContainsNonTrivial &operator=(const ContainsNonTrivial &); +}; + +// Fields i and j are merged into one memcpy, k, l and m (bit-fields) into +// another. The non-trivial members are assigned with explicit calls in between. +// CHECK-LABEL: cir.func {{.*}} @_ZN18ContainsNonTrivialaSERKS_( +// CHECK: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( +// CHECK: %[[SIZE_IJ:.*]] = cir.const #cir.int<12> : !u64i +// CHECK: cir.libc.memcpy %[[SIZE_IJ]] bytes from +// CHECK: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( +// CHECK: %[[SIZE_KLM:.*]] = cir.const #cir.int<2> : !u64i +// CHECK: cir.libc.memcpy %[[SIZE_KLM]] bytes from +// CHECK: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( +// CHECK: cir.return +ContainsNonTrivial & +ContainsNonTrivial::operator=(const ContainsNonTrivial &) = default; + +struct Trivial { + int i; + int *j; + double k; + int l[3]; +}; + +// Trivial assignment operators are still called explicitly. +// CHECK-LABEL: cir.func {{.*}} @_Z11copyTrivialR7TrivialS0_( +// CHECK: cir.call @_ZN7TrivialaSERKS_( +void copyTrivial(Trivial &a, Trivial &b) { a = b; } + +// All fields are merged into one memcpy. Note that tail padding bytes are not +// included: sizeof(Trivial) is 40. +// CHECK-LABEL: cir.func {{.*}} @_ZN7TrivialaSERKS_( +// CHECK: %[[SIZE:.*]] = cir.const #cir.int<36> : !u64i +// CHECK-NEXT: cir.cast bitcast +// CHECK-NEXT: cir.cast bitcast +// CHECK-NEXT: cir.libc.memcpy %[[SIZE]] bytes from +// CHECK: cir.return + +struct ContainsTrivial { + Trivial t1; + Trivial t2; + ContainsTrivial &operator=(const ContainsTrivial &); +}; + +// Fields of trivial class type are assigned by calling their operator=. +// CHECK-LABEL: cir.func {{.*}} @_ZN15ContainsTrivialaSERKS_( +// CHECK: cir.call @_ZN7TrivialaSERKS_( +// CHECK: cir.call @_ZN7TrivialaSERKS_( +// CHECK-NOT: cir.libc.memcpy +// CHECK: cir.return +ContainsTrivial &ContainsTrivial::operator=(const ContainsTrivial &) = default; + +struct ContainsTrivialArray { + Trivial arr[2]; + ContainsTrivialArray &operator=(const ContainsTrivialArray &); +}; + +// An array of trivial class types is copied with a memcpy. +// CHECK-LABEL: cir.func {{.*}} @_ZN20ContainsTrivialArrayaSERKS_( +// CHECK: %[[SIZE_ARR:.*]] = cir.const #cir.int<80> : !u64i +// CHECK: cir.libc.memcpy %[[SIZE_ARR]] bytes from +// CHECK: cir.return +ContainsTrivialArray & +ContainsTrivialArray::operator=(const ContainsTrivialArray &) = default; >From 5e9c3459a085c99a574ef49064f2da79522d9873 Mon Sep 17 00:00:00 2001 From: Mariya Podchishchaeva <[email protected]> Date: Thu, 29 Oct 2026 09:18:18 -0500 Subject: [PATCH 06/11] Add LLVM IR checks, claude assisted --- .../CIR/CodeGen/assign-operator-memcpy.cpp | 100 +++++++++++++----- 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp index b1b57898238206..448aa4752fe013 100644 --- a/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp +++ b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp @@ -1,5 +1,9 @@ // RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir -// RUN: FileCheck --input-file=%t.cir %s +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s // Runs of adjacent trivially copyable fields in an implicitly defined copy // assignment operator are merged into a single memcpy, as classic codegen does. @@ -22,18 +26,32 @@ struct ContainsNonTrivial { // Fields i and j are merged into one memcpy, k, l and m (bit-fields) into // another. The non-trivial members are assigned with explicit calls in between. -// CHECK-LABEL: cir.func {{.*}} @_ZN18ContainsNonTrivialaSERKS_( -// CHECK: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( -// CHECK: %[[SIZE_IJ:.*]] = cir.const #cir.int<12> : !u64i -// CHECK: cir.libc.memcpy %[[SIZE_IJ]] bytes from -// CHECK: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( -// CHECK: %[[SIZE_KLM:.*]] = cir.const #cir.int<2> : !u64i -// CHECK: cir.libc.memcpy %[[SIZE_KLM]] bytes from -// CHECK: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( -// CHECK: cir.return +// CIR-LABEL: cir.func {{.*}} @_ZN18ContainsNonTrivialaSERKS_( +// CIR: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( +// CIR: %[[SIZE_IJ:.*]] = cir.const #cir.int<12> : !u64i +// CIR: cir.libc.memcpy %[[SIZE_IJ]] bytes from +// CIR: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( +// CIR: %[[SIZE_KLM:.*]] = cir.const #cir.int<2> : !u64i +// CIR: cir.libc.memcpy %[[SIZE_KLM]] bytes from +// CIR: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( +// CIR: cir.return ContainsNonTrivial & ContainsNonTrivial::operator=(const ContainsNonTrivial &) = default; +// LLVM-LABEL: define {{.*}} @_ZN18ContainsNonTrivialaSERKS_( +// LLVM: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_( +// LLVM: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 12, i1 false) +// LLVM: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_( +// LLVM: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 2, i1 false) +// LLVM: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_( + +// OGCG-LABEL: define {{.*}} @_ZN18ContainsNonTrivialaSERKS_( +// OGCG: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_( +// OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 12, i1 false) +// OGCG: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_( +// OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 2, i1 false) +// OGCG: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_( + struct Trivial { int i; int *j; @@ -42,18 +60,30 @@ struct Trivial { }; // Trivial assignment operators are still called explicitly. -// CHECK-LABEL: cir.func {{.*}} @_Z11copyTrivialR7TrivialS0_( -// CHECK: cir.call @_ZN7TrivialaSERKS_( +// CIR-LABEL: cir.func {{.*}} @_Z11copyTrivialR7TrivialS0_( +// CIR: cir.call @_ZN7TrivialaSERKS_( void copyTrivial(Trivial &a, Trivial &b) { a = b; } +// CIR emits the call; the operator body copies only up to the end of the last +// field (36 bytes). Classic codegen never emits the trivial operator and copies +// the whole object (40 bytes) inline. +// LLVM-LABEL: define {{.*}} @_Z11copyTrivialR7TrivialS0_( +// LLVM: call {{.*}} @_ZN7TrivialaSERKS_( +// LLVM-LABEL: define {{.*}} @_ZN7TrivialaSERKS_( +// LLVM: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 36, i1 false) + +// OGCG-LABEL: define {{.*}} @_Z11copyTrivialR7TrivialS0_( +// OGCG-NOT: call {{.*}} @_ZN7TrivialaSERKS_( +// OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 40, i1 false) + // All fields are merged into one memcpy. Note that tail padding bytes are not // included: sizeof(Trivial) is 40. -// CHECK-LABEL: cir.func {{.*}} @_ZN7TrivialaSERKS_( -// CHECK: %[[SIZE:.*]] = cir.const #cir.int<36> : !u64i -// CHECK-NEXT: cir.cast bitcast -// CHECK-NEXT: cir.cast bitcast -// CHECK-NEXT: cir.libc.memcpy %[[SIZE]] bytes from -// CHECK: cir.return +// CIR-LABEL: cir.func {{.*}} @_ZN7TrivialaSERKS_( +// CIR: %[[SIZE:.*]] = cir.const #cir.int<36> : !u64i +// CIR-NEXT: cir.cast bitcast +// CIR-NEXT: cir.cast bitcast +// CIR-NEXT: cir.libc.memcpy %[[SIZE]] bytes from +// CIR: cir.return struct ContainsTrivial { Trivial t1; @@ -62,22 +92,38 @@ struct ContainsTrivial { }; // Fields of trivial class type are assigned by calling their operator=. -// CHECK-LABEL: cir.func {{.*}} @_ZN15ContainsTrivialaSERKS_( -// CHECK: cir.call @_ZN7TrivialaSERKS_( -// CHECK: cir.call @_ZN7TrivialaSERKS_( -// CHECK-NOT: cir.libc.memcpy -// CHECK: cir.return +// CIR-LABEL: cir.func {{.*}} @_ZN15ContainsTrivialaSERKS_( +// CIR: cir.call @_ZN7TrivialaSERKS_( +// CIR: cir.call @_ZN7TrivialaSERKS_( +// CIR-NOT: cir.libc.memcpy +// CIR: cir.return ContainsTrivial &ContainsTrivial::operator=(const ContainsTrivial &) = default; +// LLVM-LABEL: define {{.*}} @_ZN15ContainsTrivialaSERKS_( +// LLVM: call {{.*}} @_ZN7TrivialaSERKS_( +// LLVM: call {{.*}} @_ZN7TrivialaSERKS_( +// LLVM-NOT: call void @llvm.memcpy + +// Classic codegen merges both fields into a single memcpy of the whole object. +// OGCG-LABEL: define {{.*}} @_ZN15ContainsTrivialaSERKS_( +// OGCG-NOT: call {{.*}} @_ZN7TrivialaSERKS_( +// OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 80, i1 false) + struct ContainsTrivialArray { Trivial arr[2]; ContainsTrivialArray &operator=(const ContainsTrivialArray &); }; // An array of trivial class types is copied with a memcpy. -// CHECK-LABEL: cir.func {{.*}} @_ZN20ContainsTrivialArrayaSERKS_( -// CHECK: %[[SIZE_ARR:.*]] = cir.const #cir.int<80> : !u64i -// CHECK: cir.libc.memcpy %[[SIZE_ARR]] bytes from -// CHECK: cir.return +// CIR-LABEL: cir.func {{.*}} @_ZN20ContainsTrivialArrayaSERKS_( +// CIR: %[[SIZE_ARR:.*]] = cir.const #cir.int<80> : !u64i +// CIR: cir.libc.memcpy %[[SIZE_ARR]] bytes from +// CIR: cir.return ContainsTrivialArray & ContainsTrivialArray::operator=(const ContainsTrivialArray &) = default; + +// LLVM-LABEL: define {{.*}} @_ZN20ContainsTrivialArrayaSERKS_( +// LLVM: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 80, i1 false) + +// OGCG-LABEL: define {{.*}} @_ZN20ContainsTrivialArrayaSERKS_( +// OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 80, i1 false) >From 1e13dccc629c5ba756ab46a3ac9af55d943b5e1e Mon Sep 17 00:00:00 2001 From: Mariya Podchishchaeva <[email protected]> Date: Thu, 29 Oct 2026 09:29:11 -0500 Subject: [PATCH 07/11] Add additional test case, claude assisted --- .../CIR/CodeGen/assign-operator-memcpy.cpp | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp index 448aa4752fe013..1f72a11e050fdd 100644 --- a/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp +++ b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp @@ -127,3 +127,34 @@ ContainsTrivialArray::operator=(const ContainsTrivialArray &) = default; // OGCG-LABEL: define {{.*}} @_ZN20ContainsTrivialArrayaSERKS_( // OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 80, i1 false) + +struct HasPadding { + int a; + long b[32]; + int c; +}; +// FIXME: CIR zeroes the temporary member by member (leaving its padding +// uninitialized) and the assignment operator copies 268 bytes (up to the end of +// the last field), not the 272 of the object, so the tail padding is not copied +// either. Update the CIR and LLVM checks when this is fixed. + +// CIR-LABEL: cir.func {{.*}} @_Z11resetPaddedP10HasPadding( +// CIR-NOT: cir.libc.memset %{{.*}} bytes +// CIR: cir.get_member %{{.*}}[0] {name = "a"} +// CIR: cir.libc.memset %{{.*}} bytes at +// CIR: cir.get_member %{{.*}}[2] {name = "c"} +// CIR: cir.call @_ZN10HasPaddingaSEOS_( +// CIR-LABEL: cir.func {{.*}} @_ZN10HasPaddingaSEOS_( +// CIR: %[[SIZE_PADDED:.*]] = cir.const #cir.int<268> : !u64i +// CIR: cir.libc.memcpy %[[SIZE_PADDED]] bytes from + +// LLVM-LABEL: define {{.*}} @_Z11resetPaddedP10HasPadding( +// LLVM: call void @llvm.memset.p0.i64({{.*}}, i64 256, i1 false) +// LLVM: call {{.*}} @_ZN10HasPaddingaSEOS_( +// LLVM-LABEL: define {{.*}} @_ZN10HasPaddingaSEOS_( +// LLVM: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 268, i1 false) + +// OGCG-LABEL: define {{.*}} @_Z11resetPaddedP10HasPadding( +// OGCG: call void @llvm.memset.p0.i64({{.*}}, i64 272, i1 false) +// OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 272, i1 false) +void resetPadded(HasPadding *p) { *p = {}; } >From 3a02b9363021f2f37f87876db1fe4c89386e06a8 Mon Sep 17 00:00:00 2001 From: Mariya Podchishchaeva <[email protected]> Date: Thu, 29 Oct 2026 09:46:38 -0500 Subject: [PATCH 08/11] Tighten the checks --- .../CIR/CodeGen/assign-operator-memcpy.cpp | 222 ++++++++++++------ 1 file changed, 156 insertions(+), 66 deletions(-) diff --git a/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp index 1f72a11e050fdd..c5ddcfffbd0d05 100644 --- a/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp +++ b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp @@ -5,9 +5,6 @@ // RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll // RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s -// Runs of adjacent trivially copyable fields in an implicitly defined copy -// assignment operator are merged into a single memcpy, as classic codegen does. - struct HasNonTrivialAssignOp { HasNonTrivialAssignOp &operator=(const HasNonTrivialAssignOp &); }; @@ -24,33 +21,59 @@ struct ContainsNonTrivial { ContainsNonTrivial &operator=(const ContainsNonTrivial &); }; -// Fields i and j are merged into one memcpy, k, l and m (bit-fields) into -// another. The non-trivial members are assigned with explicit calls in between. // CIR-LABEL: cir.func {{.*}} @_ZN18ContainsNonTrivialaSERKS_( +// CIR: %[[NT_THIS:.*]] = cir.load deref %{{.*}} : !cir.ptr<!cir.ptr<!rec_ContainsNonTrivial>> // CIR: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( -// CIR: %[[SIZE_IJ:.*]] = cir.const #cir.int<12> : !u64i -// CIR: cir.libc.memcpy %[[SIZE_IJ]] bytes from +// CIR-NEXT: %[[NT_I_DST:.*]] = cir.get_member %[[NT_THIS]][2] {name = "i"} +// CIR-NEXT: %[[NT_OTHER_I:.*]] = cir.load align(8) %{{.*}} : !cir.ptr<!cir.ptr<!rec_ContainsNonTrivial>> +// CIR-NEXT: %[[NT_I_SRC:.*]] = cir.get_member %[[NT_OTHER_I]][2] {name = "i"} +// CIR-NEXT: %[[NT_SIZE_IJ:.*]] = cir.const #cir.int<12> : !u64i +// CIR-NEXT: %[[NT_I_SRC_VOID:.*]] = cir.cast bitcast %[[NT_I_SRC]] : !cir.ptr<!s32i> -> !cir.ptr<!void> +// CIR-NEXT: %[[NT_I_DST_VOID:.*]] = cir.cast bitcast %[[NT_I_DST]] : !cir.ptr<!s32i> -> !cir.ptr<!void> +// CIR-NEXT: cir.libc.memcpy %[[NT_SIZE_IJ]] bytes from %[[NT_I_SRC_VOID]] align(4) to %[[NT_I_DST_VOID]] align(4) // CIR: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( -// CIR: %[[SIZE_KLM:.*]] = cir.const #cir.int<2> : !u64i -// CIR: cir.libc.memcpy %[[SIZE_KLM]] bytes from +// CIR-NEXT: %[[NT_K_DST:.*]] = cir.get_member %[[NT_THIS]][5] {name = "k"} +// CIR-NEXT: %[[NT_OTHER_K:.*]] = cir.load align(8) %{{.*}} : !cir.ptr<!cir.ptr<!rec_ContainsNonTrivial>> +// CIR-NEXT: %[[NT_K_SRC:.*]] = cir.get_member %[[NT_OTHER_K]][5] {name = "k"} +// CIR-NEXT: %[[NT_SIZE_KLM:.*]] = cir.const #cir.int<2> : !u64i +// CIR-NEXT: %[[NT_K_SRC_VOID:.*]] = cir.cast bitcast %[[NT_K_SRC]] : !cir.ptr<!u16i> -> !cir.ptr<!void> +// CIR-NEXT: %[[NT_K_DST_VOID:.*]] = cir.cast bitcast %[[NT_K_DST]] : !cir.ptr<!u16i> -> !cir.ptr<!void> +// CIR-NEXT: cir.libc.memcpy %[[NT_SIZE_KLM]] bytes from %[[NT_K_SRC_VOID]] align(1) to %[[NT_K_DST_VOID]] align(1) // CIR: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( // CIR: cir.return -ContainsNonTrivial & -ContainsNonTrivial::operator=(const ContainsNonTrivial &) = default; // LLVM-LABEL: define {{.*}} @_ZN18ContainsNonTrivialaSERKS_( +// LLVM: [[NT_THIS:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[NT_OTHER0:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_(ptr {{.*}} [[NT_THIS]], ptr {{.*}} [[NT_OTHER0]]) +// LLVM-NEXT: [[NT_I_DST:%.*]] = getelementptr inbounds nuw %struct.ContainsNonTrivial, ptr [[NT_THIS]], i32 0, i32 2 +// LLVM-NEXT: [[NT_OTHER_I:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[NT_I_SRC:%.*]] = getelementptr inbounds nuw %struct.ContainsNonTrivial, ptr [[NT_OTHER_I]], i32 0, i32 2 +// LLVM-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[NT_I_DST]], ptr align 4 [[NT_I_SRC]], i64 12, i1 false) // LLVM: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_( -// LLVM: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 12, i1 false) -// LLVM: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_( -// LLVM: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 2, i1 false) +// LLVM-NEXT: [[NT_K_DST:%.*]] = getelementptr inbounds nuw %struct.ContainsNonTrivial, ptr [[NT_THIS]], i32 0, i32 5 +// LLVM-NEXT: [[NT_OTHER_K:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[NT_K_SRC:%.*]] = getelementptr inbounds nuw %struct.ContainsNonTrivial, ptr [[NT_OTHER_K]], i32 0, i32 5 +// LLVM-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[NT_K_DST]], ptr align 1 [[NT_K_SRC]], i64 2, i1 false) // LLVM: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_( +// LLVM: ret ptr // OGCG-LABEL: define {{.*}} @_ZN18ContainsNonTrivialaSERKS_( +// OGCG: [[NT_THIS:%.*]] = load ptr, ptr %this.addr +// OGCG: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_(ptr {{.*}} [[NT_THIS]], +// OGCG-NEXT: [[NT_I_DST:%.*]] = getelementptr inbounds nuw %struct.ContainsNonTrivial, ptr [[NT_THIS]], i32 0, i32 1 +// OGCG-NEXT: [[NT_OTHER_I:%.*]] = load ptr, ptr %.addr +// OGCG-NEXT: [[NT_I_SRC:%.*]] = getelementptr inbounds nuw %struct.ContainsNonTrivial, ptr [[NT_OTHER_I]], i32 0, i32 1 +// OGCG-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[NT_I_DST]], ptr align 4 [[NT_I_SRC]], i64 12, i1 false) // OGCG: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_( -// OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 12, i1 false) -// OGCG: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_( -// OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 2, i1 false) +// OGCG-NEXT: [[NT_K_DST:%.*]] = getelementptr inbounds nuw %struct.ContainsNonTrivial, ptr [[NT_THIS]], i32 0, i32 4 +// OGCG-NEXT: [[NT_OTHER_K:%.*]] = load ptr, ptr %.addr +// OGCG-NEXT: [[NT_K_SRC:%.*]] = getelementptr inbounds nuw %struct.ContainsNonTrivial, ptr [[NT_OTHER_K]], i32 0, i32 4 +// OGCG-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[NT_K_DST]], ptr align 1 [[NT_K_SRC]], i64 2, i1 false) // OGCG: call {{.*}} @_ZN21HasNonTrivialAssignOpaSERKS_( +// OGCG: ret ptr [[NT_THIS]] +ContainsNonTrivial & +ContainsNonTrivial::operator=(const ContainsNonTrivial &) = default; struct Trivial { int i; @@ -59,102 +82,169 @@ struct Trivial { int l[3]; }; -// Trivial assignment operators are still called explicitly. +// Trivial assignment operators are still called explicitly by CIR. Classic +// codegen never emits the trivial operator and copies the whole object (40 +// bytes) inline. + // CIR-LABEL: cir.func {{.*}} @_Z11copyTrivialR7TrivialS0_( -// CIR: cir.call @_ZN7TrivialaSERKS_( -void copyTrivial(Trivial &a, Trivial &b) { a = b; } +// CIR: %[[CT_B:.*]] = cir.load %{{.*}} : !cir.ptr<!cir.ptr<!rec_Trivial>>, !cir.ptr<!rec_Trivial> +// CIR-NEXT: %[[CT_A:.*]] = cir.load %{{.*}} : !cir.ptr<!cir.ptr<!rec_Trivial>>, !cir.ptr<!rec_Trivial> +// CIR-NEXT: cir.call @_ZN7TrivialaSERKS_(%[[CT_A]], %[[CT_B]]) +// CIR-NEXT: cir.return -// CIR emits the call; the operator body copies only up to the end of the last -// field (36 bytes). Classic codegen never emits the trivial operator and copies -// the whole object (40 bytes) inline. // LLVM-LABEL: define {{.*}} @_Z11copyTrivialR7TrivialS0_( -// LLVM: call {{.*}} @_ZN7TrivialaSERKS_( -// LLVM-LABEL: define {{.*}} @_ZN7TrivialaSERKS_( -// LLVM: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 36, i1 false) +// LLVM: [[CT_B:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[CT_A:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: call {{.*}} @_ZN7TrivialaSERKS_(ptr {{.*}} [[CT_A]], ptr {{.*}} [[CT_B]]) +// LLVM-NEXT: ret void // OGCG-LABEL: define {{.*}} @_Z11copyTrivialR7TrivialS0_( -// OGCG-NOT: call {{.*}} @_ZN7TrivialaSERKS_( -// OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 40, i1 false) +// OGCG: [[CT_B:%.*]] = load ptr, ptr %{{.*}} +// OGCG-NEXT: [[CT_A:%.*]] = load ptr, ptr %{{.*}} +// OGCG-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[CT_A]], ptr align 8 [[CT_B]], i64 40, i1 false) +// OGCG-NEXT: ret void +void copyTrivial(Trivial &a, Trivial &b) { a = b; } + +// The operator that CIR calls copies all fields in a single memcpy. Note that +// tail padding bytes are not included: sizeof(Trivial) is 40, the memcpy is 36. -// All fields are merged into one memcpy. Note that tail padding bytes are not -// included: sizeof(Trivial) is 40. // CIR-LABEL: cir.func {{.*}} @_ZN7TrivialaSERKS_( -// CIR: %[[SIZE:.*]] = cir.const #cir.int<36> : !u64i -// CIR-NEXT: cir.cast bitcast -// CIR-NEXT: cir.cast bitcast -// CIR-NEXT: cir.libc.memcpy %[[SIZE]] bytes from +// CIR: %[[TR_THIS:.*]] = cir.load deref %{{.*}} : !cir.ptr<!cir.ptr<!rec_Trivial>> +// CIR-NEXT: %[[TR_DST:.*]] = cir.get_member %[[TR_THIS]][0] {name = "i"} +// CIR-NEXT: %[[TR_OTHER:.*]] = cir.load align(8) %{{.*}} : !cir.ptr<!cir.ptr<!rec_Trivial>> +// CIR-NEXT: %[[TR_SRC:.*]] = cir.get_member %[[TR_OTHER]][0] {name = "i"} +// CIR-NEXT: %[[TR_SIZE:.*]] = cir.const #cir.int<36> : !u64i +// CIR-NEXT: %[[TR_SRC_VOID:.*]] = cir.cast bitcast %[[TR_SRC]] : !cir.ptr<!s32i> -> !cir.ptr<!void> +// CIR-NEXT: %[[TR_DST_VOID:.*]] = cir.cast bitcast %[[TR_DST]] : !cir.ptr<!s32i> -> !cir.ptr<!void> +// CIR-NEXT: cir.libc.memcpy %[[TR_SIZE]] bytes from %[[TR_SRC_VOID]] align(8) to %[[TR_DST_VOID]] align(8) +// CIR-NEXT: cir.store %[[TR_THIS]], %{{.*}} // CIR: cir.return +// LLVM-LABEL: define {{.*}} @_ZN7TrivialaSERKS_( +// LLVM: [[TR_THIS:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[TR_DST:%.*]] = getelementptr inbounds nuw %struct.Trivial, ptr [[TR_THIS]], i32 0, i32 0 +// LLVM-NEXT: [[TR_OTHER:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[TR_SRC:%.*]] = getelementptr inbounds nuw %struct.Trivial, ptr [[TR_OTHER]], i32 0, i32 0 +// LLVM-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TR_DST]], ptr align 8 [[TR_SRC]], i64 36, i1 false) +// LLVM: ret ptr + struct ContainsTrivial { Trivial t1; Trivial t2; ContainsTrivial &operator=(const ContainsTrivial &); }; -// Fields of trivial class type are assigned by calling their operator=. +// CIR assigns fields of trivial class type by calling their operator=. Classic +// codegen merges both fields into a single memcpy of the whole object. + // CIR-LABEL: cir.func {{.*}} @_ZN15ContainsTrivialaSERKS_( -// CIR: cir.call @_ZN7TrivialaSERKS_( -// CIR: cir.call @_ZN7TrivialaSERKS_( +// CIR: %[[CTR_THIS:.*]] = cir.load deref %{{.*}} : !cir.ptr<!cir.ptr<!rec_ContainsTrivial>> +// CIR-NEXT: %[[CTR_T1_DST:.*]] = cir.get_member %[[CTR_THIS]][0] {name = "t1"} +// CIR-NEXT: %[[CTR_OTHER1:.*]] = cir.load %{{.*}} : !cir.ptr<!cir.ptr<!rec_ContainsTrivial>> +// CIR-NEXT: %[[CTR_T1_SRC:.*]] = cir.get_member %[[CTR_OTHER1]][0] {name = "t1"} +// CIR-NEXT: cir.call @_ZN7TrivialaSERKS_(%[[CTR_T1_DST]], %[[CTR_T1_SRC]]) +// CIR-NEXT: %[[CTR_T2_DST:.*]] = cir.get_member %[[CTR_THIS]][1] {name = "t2"} +// CIR-NEXT: %[[CTR_OTHER2:.*]] = cir.load %{{.*}} : !cir.ptr<!cir.ptr<!rec_ContainsTrivial>> +// CIR-NEXT: %[[CTR_T2_SRC:.*]] = cir.get_member %[[CTR_OTHER2]][1] {name = "t2"} +// CIR-NEXT: cir.call @_ZN7TrivialaSERKS_(%[[CTR_T2_DST]], %[[CTR_T2_SRC]]) // CIR-NOT: cir.libc.memcpy // CIR: cir.return -ContainsTrivial &ContainsTrivial::operator=(const ContainsTrivial &) = default; // LLVM-LABEL: define {{.*}} @_ZN15ContainsTrivialaSERKS_( -// LLVM: call {{.*}} @_ZN7TrivialaSERKS_( -// LLVM: call {{.*}} @_ZN7TrivialaSERKS_( +// LLVM: [[CTR_THIS:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[CTR_T1_DST:%.*]] = getelementptr inbounds nuw %struct.ContainsTrivial, ptr [[CTR_THIS]], i32 0, i32 0 +// LLVM-NEXT: [[CTR_OTHER1:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[CTR_T1_SRC:%.*]] = getelementptr inbounds nuw %struct.ContainsTrivial, ptr [[CTR_OTHER1]], i32 0, i32 0 +// LLVM-NEXT: call {{.*}} @_ZN7TrivialaSERKS_(ptr {{.*}} [[CTR_T1_DST]], ptr {{.*}} [[CTR_T1_SRC]]) +// LLVM-NEXT: [[CTR_T2_DST:%.*]] = getelementptr inbounds nuw %struct.ContainsTrivial, ptr [[CTR_THIS]], i32 0, i32 1 +// LLVM-NEXT: [[CTR_OTHER2:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[CTR_T2_SRC:%.*]] = getelementptr inbounds nuw %struct.ContainsTrivial, ptr [[CTR_OTHER2]], i32 0, i32 1 +// LLVM-NEXT: call {{.*}} @_ZN7TrivialaSERKS_(ptr {{.*}} [[CTR_T2_DST]], ptr {{.*}} [[CTR_T2_SRC]]) // LLVM-NOT: call void @llvm.memcpy +// LLVM: ret ptr -// Classic codegen merges both fields into a single memcpy of the whole object. // OGCG-LABEL: define {{.*}} @_ZN15ContainsTrivialaSERKS_( -// OGCG-NOT: call {{.*}} @_ZN7TrivialaSERKS_( -// OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 80, i1 false) +// OGCG: [[CTR_THIS:%.*]] = load ptr, ptr %this.addr +// OGCG-NEXT: [[CTR_T1_DST:%.*]] = getelementptr inbounds nuw %struct.ContainsTrivial, ptr [[CTR_THIS]], i32 0, i32 0 +// OGCG-NEXT: [[CTR_OTHER:%.*]] = load ptr, ptr %.addr +// OGCG-NEXT: [[CTR_T1_SRC:%.*]] = getelementptr inbounds nuw %struct.ContainsTrivial, ptr [[CTR_OTHER]], i32 0, i32 0 +// OGCG-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[CTR_T1_DST]], ptr align 8 [[CTR_T1_SRC]], i64 80, i1 false) +// OGCG-NEXT: ret ptr [[CTR_THIS]] +ContainsTrivial &ContainsTrivial::operator=(const ContainsTrivial &) = default; struct ContainsTrivialArray { Trivial arr[2]; ContainsTrivialArray &operator=(const ContainsTrivialArray &); }; -// An array of trivial class types is copied with a memcpy. // CIR-LABEL: cir.func {{.*}} @_ZN20ContainsTrivialArrayaSERKS_( -// CIR: %[[SIZE_ARR:.*]] = cir.const #cir.int<80> : !u64i -// CIR: cir.libc.memcpy %[[SIZE_ARR]] bytes from +// CIR: %[[ARR_THIS:.*]] = cir.load deref %{{.*}} : !cir.ptr<!cir.ptr<!rec_ContainsTrivialArray>> +// CIR-NEXT: %[[ARR_DST:.*]] = cir.get_member %[[ARR_THIS]][0] {name = "arr"} +// CIR-NEXT: %[[ARR_DST_VOID:.*]] = cir.cast bitcast %[[ARR_DST]] : {{.*}} -> !cir.ptr<!void> +// CIR-NEXT: %[[ARR_OTHER:.*]] = cir.load %{{.*}} : !cir.ptr<!cir.ptr<!rec_ContainsTrivialArray>> +// CIR-NEXT: %[[ARR_SRC:.*]] = cir.get_member %[[ARR_OTHER]][0] {name = "arr"} +// CIR-NEXT: %[[ARR_SRC_VOID:.*]] = cir.cast bitcast %[[ARR_SRC]] : {{.*}} -> !cir.ptr<!void> +// CIR-NEXT: %[[ARR_SIZE:.*]] = cir.const #cir.int<80> : !u64i +// CIR-NEXT: cir.libc.memcpy %[[ARR_SIZE]] bytes from %[[ARR_SRC_VOID]] align(8) to %[[ARR_DST_VOID]] align(8) // CIR: cir.return -ContainsTrivialArray & -ContainsTrivialArray::operator=(const ContainsTrivialArray &) = default; // LLVM-LABEL: define {{.*}} @_ZN20ContainsTrivialArrayaSERKS_( -// LLVM: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 80, i1 false) +// LLVM: [[ARR_THIS:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[ARR_DST:%.*]] = getelementptr inbounds nuw %struct.ContainsTrivialArray, ptr [[ARR_THIS]], i32 0, i32 0 +// LLVM-NEXT: [[ARR_OTHER:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[ARR_SRC:%.*]] = getelementptr inbounds nuw %struct.ContainsTrivialArray, ptr [[ARR_OTHER]], i32 0, i32 0 +// LLVM-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[ARR_DST]], ptr align 8 [[ARR_SRC]], i64 80, i1 false) +// LLVM: ret ptr // OGCG-LABEL: define {{.*}} @_ZN20ContainsTrivialArrayaSERKS_( -// OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 80, i1 false) +// OGCG: [[ARR_THIS:%.*]] = load ptr, ptr %this.addr +// OGCG-NEXT: [[ARR_DST:%.*]] = getelementptr inbounds nuw %struct.ContainsTrivialArray, ptr [[ARR_THIS]], i32 0, i32 0 +// OGCG-NEXT: [[ARR_OTHER:%.*]] = load ptr, ptr %.addr +// OGCG-NEXT: [[ARR_SRC:%.*]] = getelementptr inbounds nuw %struct.ContainsTrivialArray, ptr [[ARR_OTHER]], i32 0, i32 0 +// OGCG-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[ARR_DST]], ptr align 8 [[ARR_SRC]], i64 80, i1 false) +// OGCG-NEXT: ret ptr [[ARR_THIS]] +ContainsTrivialArray & +ContainsTrivialArray::operator=(const ContainsTrivialArray &) = default; +// FIXME: CIR zeroes the temporary member by member (leaving its padding +// uninitialized) whereas OGCG memsets struct HasPadding { int a; long b[32]; int c; }; -// FIXME: CIR zeroes the temporary member by member (leaving its padding -// uninitialized) and the assignment operator copies 268 bytes (up to the end of -// the last field), not the 272 of the object, so the tail padding is not copied -// either. Update the CIR and LLVM checks when this is fixed. // CIR-LABEL: cir.func {{.*}} @_Z11resetPaddedP10HasPadding( -// CIR-NOT: cir.libc.memset %{{.*}} bytes -// CIR: cir.get_member %{{.*}}[0] {name = "a"} -// CIR: cir.libc.memset %{{.*}} bytes at -// CIR: cir.get_member %{{.*}}[2] {name = "c"} -// CIR: cir.call @_ZN10HasPaddingaSEOS_( +// CIR: cir.call @_ZN10HasPaddingaSEOS_( + // CIR-LABEL: cir.func {{.*}} @_ZN10HasPaddingaSEOS_( -// CIR: %[[SIZE_PADDED:.*]] = cir.const #cir.int<268> : !u64i -// CIR: cir.libc.memcpy %[[SIZE_PADDED]] bytes from +// CIR: %[[HP_THIS:.*]] = cir.load deref %{{.*}} : !cir.ptr<!cir.ptr<!rec_HasPadding>> +// CIR-NEXT: %[[HP_DST:.*]] = cir.get_member %[[HP_THIS]][0] {name = "a"} +// CIR-NEXT: %[[HP_OTHER:.*]] = cir.load align(8) %{{.*}} : !cir.ptr<!cir.ptr<!rec_HasPadding>> +// CIR-NEXT: %[[HP_SRC:.*]] = cir.get_member %[[HP_OTHER]][0] {name = "a"} +// CIR-NEXT: %[[HP_SIZE:.*]] = cir.const #cir.int<268> : !u64i +// CIR-NEXT: %[[HP_SRC_VOID:.*]] = cir.cast bitcast %[[HP_SRC]] : !cir.ptr<!s32i> -> !cir.ptr<!void> +// CIR-NEXT: %[[HP_DST_VOID:.*]] = cir.cast bitcast %[[HP_DST]] : !cir.ptr<!s32i> -> !cir.ptr<!void> +// CIR-NEXT: cir.libc.memcpy %[[HP_SIZE]] bytes from %[[HP_SRC_VOID]] align(8) to %[[HP_DST_VOID]] align(8) +// CIR-NEXT: cir.store %[[HP_THIS]], %{{.*}} +// CIR: cir.return // LLVM-LABEL: define {{.*}} @_Z11resetPaddedP10HasPadding( -// LLVM: call void @llvm.memset.p0.i64({{.*}}, i64 256, i1 false) -// LLVM: call {{.*}} @_ZN10HasPaddingaSEOS_( +// LLVM: call {{.*}} @_ZN10HasPaddingaSEOS_(ptr {{.*}} [[RP_P]], ptr {{.*}} [[RP_TMP]]) + // LLVM-LABEL: define {{.*}} @_ZN10HasPaddingaSEOS_( -// LLVM: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 268, i1 false) +// LLVM: [[HP_THIS:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[HP_DST:%.*]] = getelementptr inbounds nuw %struct.HasPadding, ptr [[HP_THIS]], i32 0, i32 0 +// LLVM-NEXT: [[HP_OTHER:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: [[HP_SRC:%.*]] = getelementptr inbounds nuw %struct.HasPadding, ptr [[HP_OTHER]], i32 0, i32 0 +// LLVM-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[HP_DST]], ptr align 8 [[HP_SRC]], i64 268, i1 false) +// LLVM: ret ptr // OGCG-LABEL: define {{.*}} @_Z11resetPaddedP10HasPadding( -// OGCG: call void @llvm.memset.p0.i64({{.*}}, i64 272, i1 false) -// OGCG: call void @llvm.memcpy.p0.p0.i64({{.*}}, i64 272, i1 false) +// OGCG: [[RP_TMP:%.*]] = alloca %struct.HasPadding, align 8 +// OGCG: call void @llvm.memset.p0.i64(ptr align 8 [[RP_TMP]], i8 0, i64 272, i1 false) +// OGCG: [[RP_P:%.*]] = load ptr, ptr %p.addr +// OGCG-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[RP_P]], ptr align 8 [[RP_TMP]], i64 272, i1 false) +// OGCG-NEXT: ret void +// OGCG-NOT: call {{.*}} @_ZN10HasPaddingaSEOS_( void resetPadded(HasPadding *p) { *p = {}; } >From a1cd493d098d9fd0d9081e5fe6512a984556337c Mon Sep 17 00:00:00 2001 From: Mariya Podchishchaeva <[email protected]> Date: Thu, 29 Oct 2026 09:50:26 -0500 Subject: [PATCH 09/11] Remove the comment --- clang/lib/CIR/CodeGen/CIRGenClass.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp index e19158126e0f25..d818f90791ad45 100644 --- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -1139,11 +1139,6 @@ void CIRGenFunction::emitImplicitAssignmentOperatorBody(FunctionArgList &args) { assert(!cir::MissingFeatures::incrementProfileCounter()); assert(!cir::MissingFeatures::runCleanupsScope()); - // 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 - // explicit. For now, we don't insert memcpy at all, though in some - // cases the AST contains a call to memcpy. AssignmentMemcpyizer AM(*this, assignOp, args); for (auto *i : rootCS->body()) AM.emitAssignment(i); >From 13ffb3fdd97687259dbe756e4ff17a2f0eef5480 Mon Sep 17 00:00:00 2001 From: Mariya Podchishchaeva <[email protected]> Date: Thu, 29 Oct 2026 10:39:23 -0500 Subject: [PATCH 10/11] Oops --- clang/test/CIR/CodeGen/assign-operator-memcpy.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp index c5ddcfffbd0d05..077a60ea74e0f1 100644 --- a/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp +++ b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp @@ -230,7 +230,9 @@ struct HasPadding { // CIR: cir.return // LLVM-LABEL: define {{.*}} @_Z11resetPaddedP10HasPadding( -// LLVM: call {{.*}} @_ZN10HasPaddingaSEOS_(ptr {{.*}} [[RP_P]], ptr {{.*}} [[RP_TMP]]) +// LLVM: [[RP_TMP:%.*]] = alloca %struct.HasPadding, align 8 +// LLVM: [[RP_P:%.*]] = load ptr, ptr {{.*}} +// LLVM-NEXT: call {{.*}} @_ZN10HasPaddingaSEOS_(ptr {{.*}} [[RP_P]], ptr {{.*}} [[RP_TMP]]) // LLVM-LABEL: define {{.*}} @_ZN10HasPaddingaSEOS_( // LLVM: [[HP_THIS:%.*]] = load ptr, ptr {{.*}} >From 72ecc38cc6c252621807f862e286c2553a6ea2a8 Mon Sep 17 00:00:00 2001 From: Mariya Podchishchaeva <[email protected]> Date: Fri, 30 Oct 2026 04:03:18 -0500 Subject: [PATCH 11/11] Hmm --- clang/lib/CIR/CodeGen/CIRGenClass.cpp | 5 +++-- clang/test/CIR/CodeGen/assign-operator-memcpy.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp index d818f90791ad45..a903d411c8b1a8 100644 --- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -142,8 +142,9 @@ class FieldMemcpyizer { CIRGenBuilderTy &builder = cgf.getBuilder(); mlir::Value sizeOp = builder.getConstInt(loc, cgf.sizeTy, size.getQuantity()); - builder.createMemCpy(loc, destPtr.withElementType(builder, cgf.voidTy), - srcPtr.withElementType(builder, cgf.voidTy), sizeOp); + Address destCast = destPtr.withElementType(builder, cgf.voidTy); + Address srcCast = srcPtr.withElementType(builder, cgf.voidTy); + builder.createMemCpy(loc, destCast, srcCast, sizeOp); } void addInitialField(FieldDecl *f) { diff --git a/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp index 077a60ea74e0f1..a356a3a72c3d46 100644 --- a/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp +++ b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp @@ -28,16 +28,16 @@ struct ContainsNonTrivial { // CIR-NEXT: %[[NT_OTHER_I:.*]] = cir.load align(8) %{{.*}} : !cir.ptr<!cir.ptr<!rec_ContainsNonTrivial>> // CIR-NEXT: %[[NT_I_SRC:.*]] = cir.get_member %[[NT_OTHER_I]][2] {name = "i"} // CIR-NEXT: %[[NT_SIZE_IJ:.*]] = cir.const #cir.int<12> : !u64i -// CIR-NEXT: %[[NT_I_SRC_VOID:.*]] = cir.cast bitcast %[[NT_I_SRC]] : !cir.ptr<!s32i> -> !cir.ptr<!void> // CIR-NEXT: %[[NT_I_DST_VOID:.*]] = cir.cast bitcast %[[NT_I_DST]] : !cir.ptr<!s32i> -> !cir.ptr<!void> +// CIR-NEXT: %[[NT_I_SRC_VOID:.*]] = cir.cast bitcast %[[NT_I_SRC]] : !cir.ptr<!s32i> -> !cir.ptr<!void> // CIR-NEXT: cir.libc.memcpy %[[NT_SIZE_IJ]] bytes from %[[NT_I_SRC_VOID]] align(4) to %[[NT_I_DST_VOID]] align(4) // CIR: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( // CIR-NEXT: %[[NT_K_DST:.*]] = cir.get_member %[[NT_THIS]][5] {name = "k"} // CIR-NEXT: %[[NT_OTHER_K:.*]] = cir.load align(8) %{{.*}} : !cir.ptr<!cir.ptr<!rec_ContainsNonTrivial>> // CIR-NEXT: %[[NT_K_SRC:.*]] = cir.get_member %[[NT_OTHER_K]][5] {name = "k"} // CIR-NEXT: %[[NT_SIZE_KLM:.*]] = cir.const #cir.int<2> : !u64i -// CIR-NEXT: %[[NT_K_SRC_VOID:.*]] = cir.cast bitcast %[[NT_K_SRC]] : !cir.ptr<!u16i> -> !cir.ptr<!void> // CIR-NEXT: %[[NT_K_DST_VOID:.*]] = cir.cast bitcast %[[NT_K_DST]] : !cir.ptr<!u16i> -> !cir.ptr<!void> +// CIR-NEXT: %[[NT_K_SRC_VOID:.*]] = cir.cast bitcast %[[NT_K_SRC]] : !cir.ptr<!u16i> -> !cir.ptr<!void> // CIR-NEXT: cir.libc.memcpy %[[NT_SIZE_KLM]] bytes from %[[NT_K_SRC_VOID]] align(1) to %[[NT_K_DST_VOID]] align(1) // CIR: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_( // CIR: cir.return @@ -114,8 +114,8 @@ void copyTrivial(Trivial &a, Trivial &b) { a = b; } // CIR-NEXT: %[[TR_OTHER:.*]] = cir.load align(8) %{{.*}} : !cir.ptr<!cir.ptr<!rec_Trivial>> // CIR-NEXT: %[[TR_SRC:.*]] = cir.get_member %[[TR_OTHER]][0] {name = "i"} // CIR-NEXT: %[[TR_SIZE:.*]] = cir.const #cir.int<36> : !u64i -// CIR-NEXT: %[[TR_SRC_VOID:.*]] = cir.cast bitcast %[[TR_SRC]] : !cir.ptr<!s32i> -> !cir.ptr<!void> // CIR-NEXT: %[[TR_DST_VOID:.*]] = cir.cast bitcast %[[TR_DST]] : !cir.ptr<!s32i> -> !cir.ptr<!void> +// CIR-NEXT: %[[TR_SRC_VOID:.*]] = cir.cast bitcast %[[TR_SRC]] : !cir.ptr<!s32i> -> !cir.ptr<!void> // CIR-NEXT: cir.libc.memcpy %[[TR_SIZE]] bytes from %[[TR_SRC_VOID]] align(8) to %[[TR_DST_VOID]] align(8) // CIR-NEXT: cir.store %[[TR_THIS]], %{{.*}} // CIR: cir.return @@ -223,8 +223,8 @@ struct HasPadding { // CIR-NEXT: %[[HP_OTHER:.*]] = cir.load align(8) %{{.*}} : !cir.ptr<!cir.ptr<!rec_HasPadding>> // CIR-NEXT: %[[HP_SRC:.*]] = cir.get_member %[[HP_OTHER]][0] {name = "a"} // CIR-NEXT: %[[HP_SIZE:.*]] = cir.const #cir.int<268> : !u64i -// CIR-NEXT: %[[HP_SRC_VOID:.*]] = cir.cast bitcast %[[HP_SRC]] : !cir.ptr<!s32i> -> !cir.ptr<!void> // CIR-NEXT: %[[HP_DST_VOID:.*]] = cir.cast bitcast %[[HP_DST]] : !cir.ptr<!s32i> -> !cir.ptr<!void> +// CIR-NEXT: %[[HP_SRC_VOID:.*]] = cir.cast bitcast %[[HP_SRC]] : !cir.ptr<!s32i> -> !cir.ptr<!void> // CIR-NEXT: cir.libc.memcpy %[[HP_SIZE]] bytes from %[[HP_SRC_VOID]] align(8) to %[[HP_DST_VOID]] align(8) // CIR-NEXT: cir.store %[[HP_THIS]], %{{.*}} // CIR: cir.return _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
