llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Mariya Podchishchaeva (Fznamznon)

<details>
<summary>Changes</summary>

This adds a special class to to replace member initializers with memcpy when 
emitting assignment operator body. This is not entirely a cherry-pick but 
rather a port from the incubator because the current state of that class in the 
incubator is not buildable with LLVM main branch. This PR also ports relevant 
test cases from the incubator that were in 
clang/test/CIR/CodeGen/assign-operator.cpp in incubator's tree. However current 
LLVM main branch also has a test file with the same name but different test 
cases that are unaffected by AssignmentMemcpyizer, so I added ported cases to 
the new file assign-operator-memcpy.cpp . An additional test case `HasPadding` 
that originally motivated this work and that doesn't come from the incubator 
has been added to the new test file as well. There is no functional changes to 
the class, so a few TODOs are still present.

The relevant (non-refactoring) commits from the incubator that implement the 
class are:

https://github.com/llvm/clangir/commit/6cb05dcde4080a1bbe4c22e79015874e178f68fc 
, 
https://github.com/llvm/clangir/commit/b3dc28998f8f87ced528d4982bfe827fc9fd8dcc 
, 
https://github.com/llvm/clangir/commit/58b4687b64913be3762f5f0504634e0a75a87e61 

I used claude to port codestyle , port test cases and generate test checks.

---

Patch is 27.46 KiB, truncated to 20.00 KiB below, full version: 
https://github.com/llvm/llvm-project/pull/236803.diff


3 Files Affected:

- (modified) clang/include/clang/CIR/MissingFeatures.h (-1) 
- (modified) clang/lib/CIR/CodeGen/CIRGenClass.cpp (+270-11) 
- (added) clang/test/CIR/CodeGen/assign-operator-memcpy.cpp (+250) 


``````````diff
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 738f5464672435..d818f90791ad45 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -24,6 +24,272 @@
 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) {
+    // TODO(cir): isEmptyFieldForLayout is not handled here yet.
+    // Incubator has 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;
+    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)
+      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;
+};
+
+class AssignmentMemcpyizer : public FieldMemcpyizer {
+  // Returns the memcpyable field copied by the given statement, if one
+  // exists. Otherwise returns null.
+  FieldDecl *getMemcpyableField(Stmt *s) {
+    if (!assignmentsMemcpyable)
+      return nullptr;
+    if (auto *bo = dyn_cast<BinaryOperator>(s)) {
+      // Recognise trivial assignments.
+      if (bo->getOpcode() != BO_Assign)
+        return nullptr;
+      auto *me = dyn_cast<MemberExpr>(bo->getLHS());
+      if (!me)
+        return nullptr;
+      auto *field = dyn_cast<FieldDecl>(me->getMemberDecl());
+      if (!field || !isMemcpyableField(field))
+        return nullptr;
+      Stmt *rhs = bo->getRHS();
+      if (auto *ec = dyn_cast<ImplicitCastExpr>(rhs))
+        rhs = ec->getSubExpr();
+      if (!rhs)
+        return nullptr;
+      if (auto *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 (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.
+      auto *fd = dyn_cast<FunctionDecl>(ce->getCalleeDecl());
+      if (!fd || fd->getBuiltinID() != Builtin::BI__builtin_memcpy)
+        return nullptr;
+      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;
+      auto *me = dyn_cast<MemberExpr>(duo->getSubExpr());
+      if (!me)
+        return nullptr;
+      auto *field = dyn_cast<FieldDecl>(me->getMemberDecl());
+      if (!field || !isMemcpyableField(field))
+        return nullptr;
+      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;
+      auto *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())
+        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())
+          cgf.cgm.errorNYI(aggregatedStmts[0]->getSourceRange(),
+                           std::string("emitAggregatedStmts: ") +
+                               aggregatedStmts[0]->getStmtClassName());
+      }
+      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.
 ///
@@ -873,17 +1139,10 @@ 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.
-  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(
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..c5ddcfffbd0d05
--- /dev/null
+++ b/clang/test/CIR/CodeGen/assign-operator-memcpy.cpp
@@ -0,0 +1,250 @@
+// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -fclangir 
-emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -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
+
+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 &);
+};
+
+// CIR-LABEL: cir.func {{.*}} @_ZN18ContainsNonTrivialaSERKS_(
+// CIR:         %[[NT_THIS:.*]] = cir.load deref %{{.*}} : 
!cir.ptr<!cir.ptr<!rec_ContainsNonTrivial>>
+// CIR:         cir.call @_ZN21HasNonTrivialAssignOpaSERKS_(
+// 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-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
+
+// 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-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-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;
+  int *j;
+  double k;
+  int l[3];
+};
+
+// 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:         %[[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
+
+// LLVM-LABEL: define {{.*}} @_Z11copyTrivialR7TrivialS0_(
+// 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:         [[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.
+
+// CIR-LABEL: cir.func {{.*}} @_ZN7TrivialaSERKS_(
+// 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
+// L...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/236803
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to