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

Reply via email to