From d497eb9f6cc4ca211acd70a7d571f448e2a897a4 Mon Sep 17 00:00:00 2001 From: Shoaib Meenai Date: Mon, 2 Dec 2024 17:34:08 -0800 Subject: [PATCH 1/3] Update [ghstack-poisoned] --- clang/include/clang/CIR/MissingFeatures.h | 1 + clang/lib/CIR/CodeGen/CIRGenClass.cpp | 59 +++++++++++--- clang/test/CIR/CodeGen/assign-operator.cpp | 92 ++++++++++++++++++++++ 3 files changed, 139 insertions(+), 13 deletions(-) diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 886fc2426048..c248f17e0263 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -174,6 +174,7 @@ struct MissingFeatures { // ABIInfo queries. static bool useTargetLoweringABIInfo() { return false; } + static bool isEmptyFieldForLayout() { return false; } // Misc static bool cacheRecordLayouts() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp index 7e62a9fcaf10..4389bbad0f1c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -98,15 +98,13 @@ class CopyingValueRepresentation { class FieldMemcpyizer { public: - FieldMemcpyizer(CIRGenFunction &CGF, const CXXRecordDecl *ClassDecl, + FieldMemcpyizer(CIRGenFunction &CGF, const CXXMethodDecl *MethodDecl, const VarDecl *SrcRec) - : CGF(CGF), ClassDecl(ClassDecl), - // SrcRec(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) { - (void)SrcRec; - } + LastFieldOffset(0), LastAddedFieldIndex(0) {} bool isMemcpyableField(FieldDecl *F) const { // Never memcpy fields when we are adding poised paddings. @@ -115,11 +113,11 @@ class FieldMemcpyizer { 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) @@ -148,18 +146,54 @@ class FieldMemcpyizer { return; } - llvm_unreachable("NYI"); + 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().getTypeDeclType(ClassDecl); + 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) { - llvm_unreachable("NYI"); + mlir::Location loc = CGF.getLoc(MethodDecl->getLocation()); + cir::ConstantOp sizeOp = + CGF.getBuilder().getConstInt(loc, CGF.SizeTy, Size.getQuantity()); + mlir::Value dest = + CGF.getBuilder().createBitcast(DestPtr.getPointer(), CGF.VoidPtrTy); + mlir::Value src = + CGF.getBuilder().createBitcast(SrcPtr.getPointer(), CGF.VoidPtrTy); + CGF.getBuilder().createMemCpy(loc, dest, src, sizeOp); } void addInitialField(FieldDecl *F) { @@ -192,7 +226,7 @@ class FieldMemcpyizer { } } - // const VarDecl *SrcRec; + const VarDecl *SrcRec; const ASTRecordLayout &RecLayout; FieldDecl *FirstField; FieldDecl *LastField; @@ -307,8 +341,7 @@ class ConstructorMemcpyizer : public FieldMemcpyizer { public: ConstructorMemcpyizer(CIRGenFunction &CGF, const CXXConstructorDecl *CD, FunctionArgList &Args) - : FieldMemcpyizer(CGF, CD->getParent(), - getTrivialCopySource(CGF, CD, Args)), + : FieldMemcpyizer(CGF, CD, getTrivialCopySource(CGF, CD, Args)), ConstructorDecl(CD), MemcpyableCtor(CD->isDefaulted() && CD->isCopyOrMoveConstructor() && CGF.getLangOpts().getGC() == LangOptions::NonGC), @@ -446,7 +479,7 @@ class AssignmentMemcpyizer : public FieldMemcpyizer { public: AssignmentMemcpyizer(CIRGenFunction &CGF, const CXXMethodDecl *AD, FunctionArgList &Args) - : FieldMemcpyizer(CGF, AD->getParent(), Args[Args.size() - 1]), + : FieldMemcpyizer(CGF, AD, Args[Args.size() - 1]), AssignmentsMemcpyable(CGF.getLangOpts().getGC() == LangOptions::NonGC) { assert(Args.size() == 2); } diff --git a/clang/test/CIR/CodeGen/assign-operator.cpp b/clang/test/CIR/CodeGen/assign-operator.cpp index 63fc25c5817f..55118f222c7f 100644 --- a/clang/test/CIR/CodeGen/assign-operator.cpp +++ b/clang/test/CIR/CodeGen/assign-operator.cpp @@ -99,3 +99,95 @@ int main() { // CHECK: %2 = cir.load %0 : !cir.ptr, !s32i // CHECK: cir.return %2 : !s32i // CHECK: } + +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 &); +}; + +// CHECK-LABEL: cir.func @_ZN18ContainsNonTrivialaSERKS_( +// CHECK-NEXT: %[[#THIS:]] = cir.alloca !cir.ptr +// CHECK-NEXT: %[[#OTHER:]] = cir.alloca !cir.ptr +// CHECK-NEXT: %[[#RETVAL:]] = cir.alloca !cir.ptr +// CHECK-NEXT: cir.store %arg0, %[[#THIS]] +// CHECK-NEXT: cir.store %arg1, %[[#OTHER]] +// CHECK-NEXT: %[[#THIS_LOAD:]] = cir.load deref %[[#THIS]] +// CHECK-NEXT: %[[#THIS_START:]] = cir.get_member %[[#THIS_LOAD]][0] {name = "start"} +// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]] +// CHECK-NEXT: %[[#OTHER_START:]] = cir.get_member %[[#OTHER_LOAD]][0] {name = "start"} +// CHECK-NEXT: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_(%[[#THIS_START]], %[[#OTHER_START]]) +// CHECK-NEXT: %[[#THIS_I:]] = cir.get_member %[[#THIS_LOAD]][2] {name = "i"} +// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]] +// CHECK-NEXT: %[[#OTHER_I:]] = cir.get_member %[[#OTHER_LOAD]][2] {name = "i"} +// CHECK-NEXT: %[[#MEMCPY_SIZE:]] = cir.const #cir.int<12> : !u64i +// CHECK-NEXT: %[[#THIS_I_CAST:]] = cir.cast(bitcast, %[[#THIS_I]] : !cir.ptr), !cir.ptr +// CHECK-NEXT: %[[#OTHER_I_CAST:]] = cir.cast(bitcast, %[[#OTHER_I]] : !cir.ptr), !cir.ptr +// CHECK-NEXT: cir.libc.memcpy %[[#MEMCPY_SIZE]] bytes from %[[#OTHER_I_CAST]] to %[[#THIS_I_CAST]] +// CHECK-NEXT: %[[#THIS_MIDDLE:]] = cir.get_member %[[#THIS_LOAD]][4] {name = "middle"} +// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]] +// CHECK-NEXT: %[[#OTHER_MIDDLE:]] = cir.get_member %[[#OTHER_LOAD]][4] {name = "middle"} +// CHECK-NEXT: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_(%[[#THIS_MIDDLE]], %[[#OTHER_MIDDLE]]) +// CHECK-NEXT: %[[#THIS_K:]] = cir.get_member %[[#THIS_LOAD]][5] {name = "k"} +// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]] +// CHECK-NEXT: %[[#OTHER_K:]] = cir.get_member %[[#OTHER_LOAD]][5] {name = "k"} +// CHECK-NEXT: %[[#MEMCPY_SIZE:]] = cir.const #cir.int<2> : !u64i +// CHECK-NEXT: %[[#THIS_K_CAST:]] = cir.cast(bitcast, %[[#THIS_K]] : !cir.ptr), !cir.ptr +// CHECK-NEXT: %[[#OTHER_K_CAST:]] = cir.cast(bitcast, %[[#OTHER_K]] : !cir.ptr), !cir.ptr +// CHECK-NEXT: cir.libc.memcpy %[[#MEMCPY_SIZE]] bytes from %[[#OTHER_K_CAST]] to %[[#THIS_K_CAST]] +// CHECK-NEXT: %[[#THIS_END:]] = cir.get_member %[[#THIS_LOAD]][6] {name = "end"} +// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]] +// CHECK-NEXT: %[[#OTHER_END:]] = cir.get_member %[[#OTHER_LOAD]][6] {name = "end"} +// CHECK-NEXT: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_(%[[#THIS_END]], %[[#OTHER_END]]) +// CHECK-NEXT: cir.store %[[#THIS_LOAD]], %[[#RETVAL]] +// CHECK-NEXT: %[[#RETVAL_LOAD:]] = cir.load %[[#RETVAL]] +// CHECK-NEXT: cir.return %[[#RETVAL_LOAD]] +// CHECK-NEXT: } +ContainsNonTrivial & +ContainsNonTrivial::operator=(const ContainsNonTrivial &) = default; + +struct Trivial { + int i; + int *j; + double k; + int l[3]; +}; + +// CHECK-LABEL: cir.func linkonce_odr @_ZN7TrivialaSERKS_( +// CHECK-NEXT: %[[#THIS:]] = cir.alloca !cir.ptr +// CHECK-NEXT: %[[#OTHER:]] = cir.alloca !cir.ptr +// CHECK-NEXT: %[[#RETVAL:]] = cir.alloca !cir.ptr +// CHECK-NEXT: cir.store %arg0, %[[#THIS]] +// CHECK-NEXT: cir.store %arg1, %[[#OTHER]] +// CHECK-NEXT: %[[#THIS_LOAD:]] = cir.load deref %[[#THIS]] +// CHECK-NEXT: %[[#THIS_I:]] = cir.get_member %[[#THIS_LOAD]][0] {name = "i"} +// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]] +// CHECK-NEXT: %[[#OTHER_I:]] = cir.get_member %[[#OTHER_LOAD]][0] {name = "i"} +// Note that tail padding bytes are not included. +// CHECK-NEXT: %[[#MEMCPY_SIZE:]] = cir.const #cir.int<36> : !u64i +// CHECK-NEXT: %[[#THIS_I_CAST:]] = cir.cast(bitcast, %[[#THIS_I]] : !cir.ptr), !cir.ptr +// CHECK-NEXT: %[[#OTHER_I_CAST:]] = cir.cast(bitcast, %[[#OTHER_I]] : !cir.ptr), !cir.ptr +// CHECK-NEXT: cir.libc.memcpy %[[#MEMCPY_SIZE]] bytes from %[[#OTHER_I_CAST]] to %[[#THIS_I_CAST]] +// CHECK-NEXT: cir.store %[[#THIS_LOAD]], %[[#RETVAL]] +// CHECK-NEXT: cir.br ^bb1 +// CHECK-NEXT: ^bb1: +// CHECK-NEXT: %[[#RETVAL_LOAD:]] = cir.load %[[#RETVAL]] +// CHECK-NEXT: cir.return %[[#RETVAL_LOAD]] +// CHECK-NEXT: } + +// We should explicitly call operator= even for trivial types. +// CHECK-LABEL: cir.func @_Z11copyTrivialR7TrivialS0_( +// CHECK: cir.call @_ZN7TrivialaSERKS_( +void copyTrivial(Trivial &a, Trivial &b) { + a = b; +} From aff8c6666491a48e217afcc2222f9251d530e155 Mon Sep 17 00:00:00 2001 From: Shoaib Meenai Date: Mon, 2 Dec 2024 17:34:11 -0800 Subject: [PATCH 2/3] Update [ghstack-poisoned] --- clang/lib/CIR/CodeGen/CIRGenClass.cpp | 17 +++--------- clang/test/CIR/CodeGen/assign-operator.cpp | 30 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp index 4389bbad0f1c..652c4553ca37 100644 --- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -429,20 +429,11 @@ class AssignmentMemcpyizer : public FieldMemcpyizer { } return nullptr; } else if (CXXMemberCallExpr *MCE = dyn_cast(S)) { - CXXMethodDecl *MD = dyn_cast(MCE->getCalleeDecl()); - if (!(MD && isMemcpyEquivalentSpecialMember(MD))) - return nullptr; - MemberExpr *IOA = dyn_cast(MCE->getImplicitObjectArgument()); - if (!IOA) - return nullptr; - FieldDecl *Field = dyn_cast(IOA->getMemberDecl()); - if (!Field || !isMemcpyableField(Field)) - return nullptr; - MemberExpr *Arg0 = dyn_cast(MCE->getArg(0)); - if (!Arg0 || Field != dyn_cast(Arg0->getMemberDecl())) - return nullptr; - return Field; + // We want to represent all calls explicitly for analysis purposes. + return nullptr; } else if (CallExpr *CE = dyn_cast(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(CE->getCalleeDecl()); if (!FD || FD->getBuiltinID() != Builtin::BI__builtin_memcpy) return nullptr; diff --git a/clang/test/CIR/CodeGen/assign-operator.cpp b/clang/test/CIR/CodeGen/assign-operator.cpp index 55118f222c7f..e814a649c728 100644 --- a/clang/test/CIR/CodeGen/assign-operator.cpp +++ b/clang/test/CIR/CodeGen/assign-operator.cpp @@ -191,3 +191,33 @@ struct Trivial { void copyTrivial(Trivial &a, Trivial &b) { a = b; } + +struct ContainsTrivial { + Trivial t1; + Trivial t2; + ContainsTrivial &operator=(const ContainsTrivial &); +}; + +// We should explicitly call operator= even for trivial types. +// CHECK-LABEL: cir.func @_ZN15ContainsTrivialaSERKS_( +// CHECK: cir.call @_ZN7TrivialaSERKS_( +// CHECK: cir.call @_ZN7TrivialaSERKS_( +ContainsTrivial &ContainsTrivial::operator=(const ContainsTrivial &) = default; + +struct ContainsTrivialArray { + Trivial arr[2]; + ContainsTrivialArray &operator=(const ContainsTrivialArray &); +}; + +// We should be calling operator= here but don't currently. +// CHECK-LABEL: cir.func @_ZN20ContainsTrivialArrayaSERKS_( +// CHECK: %[[#THIS_LOAD:]] = cir.load deref %[[#]] +// CHECK-NEXT: %[[#THIS_ARR:]] = cir.get_member %[[#THIS_LOAD]][0] {name = "arr"} +// CHECK-NEXT: %[[#THIS_ARR_CAST:]] = cir.cast(bitcast, %[[#THIS_ARR]] : !cir.ptr>), !cir.ptr +// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#]] +// CHECK-NEXT: %[[#OTHER_ARR:]] = cir.get_member %[[#OTHER_LOAD]][0] {name = "arr"} +// CHECK-NEXT: %[[#OTHER_ARR_CAST:]] = cir.cast(bitcast, %[[#OTHER_ARR]] : !cir.ptr>), !cir.ptr +// CHECK-NEXT: %[[#MEMCPY_SIZE:]] = cir.const #cir.int<80> : !u64i +// CHECK-NEXT: cir.libc.memcpy %[[#MEMCPY_SIZE]] bytes from %[[#OTHER_ARR_CAST]] to %[[#THIS_ARR_CAST]] +ContainsTrivialArray & +ContainsTrivialArray::operator=(const ContainsTrivialArray &) = default; From 3f857ee58b04f31943df8fdd7661515666d28220 Mon Sep 17 00:00:00 2001 From: Shoaib Meenai Date: Mon, 2 Dec 2024 17:34:13 -0800 Subject: [PATCH 3/3] Update [ghstack-poisoned] --- clang/include/clang/CIR/MissingFeatures.h | 1 - clang/lib/CIR/CodeGen/CIRGenClass.cpp | 44 ++++++++++------- clang/test/CIR/CodeGen/copy-constructor.cpp | 52 +++++++++++++++++++++ 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index c248f17e0263..57471c4dc9bb 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -141,7 +141,6 @@ struct MissingFeatures { static bool shouldSplitConstantStore() { return false; } static bool shouldCreateMemCpyFromGlobal() { return false; } static bool shouldReverseUnaryCondOnBoolExpr() { return false; } - static bool fieldMemcpyizerBuildMemcpy() { return false; } static bool isTrivialCtorOrDtor() { return false; } static bool isMemcpyEquivalentSpecialMember() { return false; } static bool constructABIArgDirectExtend() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp index 652c4553ca37..8d60d75747bf 100644 --- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -333,9 +333,23 @@ class ConstructorMemcpyizer : public FieldMemcpyizer { bool isMemberInitMemcpyable(CXXCtorInitializer *MemberInit) const { if (!MemcpyableCtor) return false; + FieldDecl *field = MemberInit->getMember(); + assert(field && "No field for member init."); + QualType fieldType = field->getType(); + CXXConstructExpr *ce = dyn_cast(MemberInit->getInit()); + + // Bail out on any members of record type (unlike CodeGen, which emits a + // memcpy for trivially-copyable record types). + if (ce || (fieldType->isArrayType() && + CGF.getContext().getBaseElementType(fieldType)->isRecordType())) + return false; - assert(!cir::MissingFeatures::fieldMemcpyizerBuildMemcpy()); - return false; + // Bail out on volatile fields. + if (!isMemcpyableField(field)) + return false; + + // Otherwise we're good. + return true; } public: @@ -363,7 +377,10 @@ class ConstructorMemcpyizer : public FieldMemcpyizer { // This memcpy is too small to be worthwhile. Fall back on default // codegen. if (!AggregatedInits.empty()) { - llvm_unreachable("NYI"); + CopyingValueRepresentation cvr(CGF); + emitMemberInitializer(CGF, ConstructorDecl->getParent(), + AggregatedInits[0], ConstructorDecl, Args); + AggregatedInits.clear(); } reset(); return; @@ -375,21 +392,14 @@ class ConstructorMemcpyizer : public FieldMemcpyizer { } void pushEHDestructors() { - Address ThisPtr = CGF.LoadCXXThisAddress(); - QualType RecordTy = CGF.getContext().getTypeDeclType(ClassDecl); - LValue LHS = CGF.makeAddrLValue(ThisPtr, RecordTy); - (void)LHS; - - for (unsigned i = 0; i < AggregatedInits.size(); ++i) { - CXXCtorInitializer *MemberInit = AggregatedInits[i]; - QualType FieldType = MemberInit->getAnyMember()->getType(); - QualType::DestructionKind dtorKind = FieldType.isDestructedType(); - if (!CGF.needsEHCleanup(dtorKind)) - continue; - LValue FieldLHS = LHS; - emitLValueForAnyFieldInitialization(CGF, MemberInit, FieldLHS); - CGF.pushEHDestroy(dtorKind, FieldLHS.getAddress(), FieldType); +#ifndef NDEBUG + for (CXXCtorInitializer *memberInit : AggregatedInits) { + QualType fieldType = memberInit->getAnyMember()->getType(); + QualType::DestructionKind dtorKind = fieldType.isDestructedType(); + assert(!CGF.needsEHCleanup(dtorKind) && + "Non-record types shouldn't need EH cleanup"); } +#endif } void finish() { emitAggregatedInits(); } diff --git a/clang/test/CIR/CodeGen/copy-constructor.cpp b/clang/test/CIR/CodeGen/copy-constructor.cpp index 92e0887b02ef..8c6475d642e1 100644 --- a/clang/test/CIR/CodeGen/copy-constructor.cpp +++ b/clang/test/CIR/CodeGen/copy-constructor.cpp @@ -33,3 +33,55 @@ struct HasScalarArrayMember { // LLVM-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr %[[#THIS_ARR]], ptr %[[#OTHER_ARR]], i32 16, i1 false) // LLVM-NEXT: ret void HasScalarArrayMember::HasScalarArrayMember(const HasScalarArrayMember &) = default; + +struct Trivial { int *i; }; +struct ManyMembers { + int i; + int j; + Trivial k; + int l[1]; + int m[2]; + Trivial n; + int &o; + int *p; +}; + +// CIR-LABEL: cir.func linkonce_odr @_ZN11ManyMembersC2ERKS_( +// CIR: %[[#THIS_LOAD:]] = cir.load %[[#]] +// CIR-NEXT: %[[#THIS_I:]] = cir.get_member %[[#THIS_LOAD]][0] {name = "i"} +// CIR-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER:]] +// CIR-NEXT: %[[#OTHER_I:]] = cir.get_member %[[#OTHER_LOAD]][0] {name = "i"} +// CIR-NEXT: %[[#MEMCPY_SIZE:]] = cir.const #cir.int<8> +// CIR-NEXT: %[[#THIS_I_CAST:]] = cir.cast(bitcast, %[[#THIS_I]] : !cir.ptr), !cir.ptr +// CIR-NEXT: %[[#OTHER_I_CAST:]] = cir.cast(bitcast, %[[#OTHER_I]] : !cir.ptr), !cir.ptr +// CIR-NEXT: cir.libc.memcpy %[[#MEMCPY_SIZE]] bytes from %[[#OTHER_I_CAST]] to %[[#THIS_I_CAST]] +// CIR-NEXT: %[[#THIS_K:]] = cir.get_member %[[#THIS_LOAD]][2] {name = "k"} +// CIR-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]] +// CIR-NEXT: %[[#OTHER_K:]] = cir.get_member %[[#OTHER_LOAD]][2] {name = "k"} +// CIR-NEXT: cir.call @_ZN7TrivialC1ERKS_(%[[#THIS_K]], %[[#OTHER_K]]) +// CIR-NEXT: %[[#THIS_L:]] = cir.get_member %[[#THIS_LOAD]][3] {name = "l"} +// CIR-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]] +// CIR-NEXT: %[[#OTHER_L:]] = cir.get_member %[[#OTHER_LOAD]][3] {name = "l"} +// CIR-NEXT: %[[#MEMCPY_SIZE:]] = cir.const #cir.int<12> +// CIR-NEXT: %[[#THIS_L_CAST:]] = cir.cast(bitcast, %[[#THIS_L]] : !cir.ptr>), !cir.ptr +// CIR-NEXT: %[[#OTHER_L_CAST:]] = cir.cast(bitcast, %[[#OTHER_L]] : !cir.ptr>), !cir.ptr +// CIR-NEXT: cir.libc.memcpy %[[#MEMCPY_SIZE]] bytes from %[[#OTHER_L_CAST]] to %[[#THIS_L_CAST]] +// CIR-NEXT: %[[#THIS_N:]] = cir.get_member %[[#THIS_LOAD]][5] {name = "n"} +// CIR-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]] +// CIR-NEXT: %[[#OTHER_N:]] = cir.get_member %[[#OTHER_LOAD]][5] {name = "n"} +// CIR-NEXT: cir.call @_ZN7TrivialC1ERKS_(%[[#THIS_N]], %[[#OTHER_N]]) +// CIR-NEXT: %[[#THIS_O:]] = cir.get_member %[[#THIS_LOAD]][6] {name = "o"} +// CIR-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]] +// CIR-NEXT: %[[#OTHER_O:]] = cir.get_member %[[#OTHER_LOAD]][6] {name = "o"} +// CIR-NEXT: %[[#MEMCPY_SIZE:]] = cir.const #cir.int<16> +// CIR-NEXT: %[[#THIS_O_CAST:]] = cir.cast(bitcast, %[[#THIS_O]] : !cir.ptr>), !cir.ptr +// CIR-NEXT: %[[#OTHER_O_CAST:]] = cir.cast(bitcast, %[[#OTHER_O]] : !cir.ptr>), !cir.ptr +// CIR-NEXT: cir.libc.memcpy %[[#MEMCPY_SIZE]] bytes from %[[#OTHER_O_CAST]] to %[[#THIS_O_CAST]] +// CIR-NEXT: cir.return +// CIR-NEXT: } + +// CIR-LABEL: cir.func @_Z6doCopyR11ManyMembers( +// CIR: cir.call @_ZN11ManyMembersC1ERKS_( +ManyMembers doCopy(ManyMembers &src) { + return src; +}