https://github.com/HendrikHuebner updated 
https://github.com/llvm/llvm-project/pull/167975

From afa84ef160c6f3f58cd65eb08f8d883d336e3111 Mon Sep 17 00:00:00 2001
From: hhuebner <[email protected]>
Date: Wed, 12 Nov 2025 21:20:30 +0100
Subject: [PATCH 1/6] Add Special member attribute

---
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  | 114 ++++++++++++++++++
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  27 ++++-
 clang/lib/CIR/CodeGen/CIRGenClass.cpp         |   3 +
 clang/lib/CIR/CodeGen/CIRGenFunction.cpp      |   8 +-
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        |  51 ++++++++
 clang/lib/CIR/CodeGen/CIRGenModule.h          |   4 +
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       |  58 +++++++++
 .../Dialect/Transforms/LoweringPrepare.cpp    |  28 +++++
 .../CIR/CodeGen/cxx-special-member-attr.cpp   |  59 +++++++++
 9 files changed, 349 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/cxx-special-member-attr.cpp

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 1e0fb038b19d8..07a5b1f3a06c8 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -822,6 +822,120 @@ def CIR_GlobalDtorAttr : CIR_GlobalCtorDtor<"Dtor", 
"dtor"> {
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// CXX SpecialMemberAttr
+//===----------------------------------------------------------------------===//
+
+def CIR_CtorKind : CIR_I32EnumAttr<"CtorKind", "CXX Constructor Kind", [
+  I32EnumAttrCase<"Custom", 0, "custom">,
+  I32EnumAttrCase<"Default", 1, "default">,
+  I32EnumAttrCase<"Copy", 2, "copy">,
+  I32EnumAttrCase<"Move", 3, "move">,
+]> {
+  let genSpecializedAttr = 0;
+}
+
+
+def CIR_CXXCtorAttr : CIR_Attr<"CXXCtor", "cxx_ctor"> {
+  let summary = "Marks a function as a C++ constructor";
+  let description = [{
+    This attribute identifies a C++ constructor and classifies its kind:
+
+    - `custom`: a user-defined constructor
+    - `default`: a default constructor
+    - `copy`: a copy constructor
+    - `move`: a move constructor
+
+    Example:
+    ```mlir
+    #cir.cxx_ctor<!rec_a, copy>
+    #cir.cxx_ctor<!rec_b, default, trivial>
+    ```
+  }];
+
+  let parameters = (ins
+    "mlir::Type":$type,
+    EnumParameter<CIR_CtorKind>:$ctor_kind,
+    DefaultValuedParameter<"bool", "false">:$is_trivial
+  );
+
+  let builders = [
+    AttrBuilderWithInferredContext<(ins "mlir::Type":$type,
+        CArg<"CtorKind", "cir::CtorKind::Custom">:$ctorKind,
+        CArg<"bool", "false">:$isTrivial), [{
+      return $_get(type.getContext(), type, ctorKind, isTrivial);
+    }]>,
+  ];
+
+  let assemblyFormat = [{
+    `<` $type `,` $ctor_kind (`,` `trivial` $is_trivial^)? `>`
+  }];
+}
+
+def CIR_CXXDtorAttr : CIR_Attr<"CXXDtor", "cxx_dtor"> {
+  let summary = "Marks a function as a CXX destructor";
+  let description = [{
+    This attribute identifies a C++ destructor.
+  }];
+
+  let parameters = (ins
+    "mlir::Type":$type,
+    DefaultValuedParameter<"bool", "false">:$is_trivial
+  );
+
+  let builders = [
+    AttrBuilderWithInferredContext<(ins "mlir::Type":$type,
+        CArg<"bool", "false">:$isTrivial), [{
+      return $_get(type.getContext(), type, isTrivial);
+    }]>
+  ];
+
+  let assemblyFormat = [{
+    `<` $type (`,` `trivial` $is_trivial^)? `>`
+  }];
+}
+
+def CIR_AssignKind : CIR_I32EnumAttr<"AssignKind", "CXX Assignment Operator 
Kind", [
+  I32EnumAttrCase<"Copy", 0, "copy">,
+  I32EnumAttrCase<"Move", 1, "move">,
+]> {
+  let genSpecializedAttr = 0;
+}
+
+def CIR_CXXAssignAttr : CIR_Attr<"CXXAssign", "cxx_assign"> {
+  let summary = "Marks a function as a CXX assignment operator";
+  let description = [{
+    This attribute identifies a C++ assignment operator and classifies its 
kind:
+
+    - `copy`: a copy assignment
+    - `move`: a move assignment
+  }];
+
+  let parameters = (ins
+    "mlir::Type":$type,
+    EnumParameter<CIR_AssignKind>:$assign_kind,
+    DefaultValuedParameter<"bool", "false">:$is_trivial
+  );
+
+  let builders = [
+    AttrBuilderWithInferredContext<(ins "mlir::Type":$type,
+        CArg<"AssignKind">:$assignKind,
+        CArg<"bool", "false">:$isTrivial), [{
+      return $_get(type.getContext(), type, assignKind, isTrivial);
+    }]>
+  ];
+
+  let assemblyFormat = [{
+    `<` $type `,` $assign_kind (`,` `trivial` $is_trivial^)? `>`
+  }];
+}
+
+def CIR_CXXSpecialMemberAttr : AnyAttrOf<[
+  CIR_CXXCtorAttr,
+  CIR_CXXDtorAttr,
+  CIR_CXXAssignAttr
+]>;
+
 
//===----------------------------------------------------------------------===//
 // BitfieldInfoAttr
 
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 2124b1dc62a81..163bd104ce04b 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2533,7 +2533,9 @@ def CIR_FuncOp : CIR_Op<"func", [
                        OptionalAttr<DictArrayAttr>:$res_attrs,
                        OptionalAttr<FlatSymbolRefAttr>:$aliasee,
                        CIR_OptionalPriorityAttr:$global_ctor_priority,
-                       CIR_OptionalPriorityAttr:$global_dtor_priority);
+                       CIR_OptionalPriorityAttr:$global_dtor_priority,
+                       
OptionalAttr<CIR_CXXSpecialMemberAttr>:$cxx_special_member
+   );
 
   let regions = (region AnyRegion:$body);
 
@@ -2572,7 +2574,28 @@ def CIR_FuncOp : CIR_Op<"func", [
     
//===------------------------------------------------------------------===//
 
     bool isDeclaration();
-  }];
+
+    
//===------------------------------------------------------------------===//
+    // C++ Special Member Functions
+    
//===------------------------------------------------------------------===//
+
+    /// Returns true if this function is a C++ special member function.
+    bool isCXXSpecialMemberFunction();
+
+    bool isCxxConstructor();
+
+    bool isCxxDestructor();
+
+    /// Returns true if this function is a copy or move assignment operator.
+    bool isCxxSpecialAssignment();
+
+    /// Returns the kind of constructor this function represents, if any.
+    std::optional<CtorKind> getCxxConstructorKind();
+
+    /// Returns the kind of assignment operator (move, copy) this function
+    /// represents, if any.
+    std::optional<AssignKind> getCxxSpecialAssignKind();
+}];
 
   let hasCustomAssemblyFormat = 1;
   let hasVerifier = 1;
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp 
b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index a8296782ebc40..7e6050012b09d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -18,6 +18,7 @@
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/RecordLayout.h"
 #include "clang/AST/Type.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
 #include "clang/CIR/MissingFeatures.h"
 
 using namespace clang;
@@ -786,6 +787,8 @@ void 
CIRGenFunction::emitImplicitAssignmentOperatorBody(FunctionArgList &args) {
          "Body of an implicit assignment operator should be compound stmt.");
   const auto *rootCS = cast<CompoundStmt>(rootS);
 
+  cgm.setCXXSpecialMemberAttr(cast<cir::FuncOp>(curFn), assignOp);
+
   assert(!cir::MissingFeatures::incrementProfileCounter());
   assert(!cir::MissingFeatures::runCleanupsScope());
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp 
b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 866fda3166f41..be80df3091655 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -560,7 +560,7 @@ static void eraseEmptyAndUnusedBlocks(cir::FuncOp func) {
 
 cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
                                          cir::FuncType funcType) {
-  const auto funcDecl = cast<FunctionDecl>(gd.getDecl());
+  const auto *funcDecl = cast<FunctionDecl>(gd.getDecl());
   curGD = gd;
 
   if (funcDecl->isInlineBuiltinDeclaration()) {
@@ -630,6 +630,7 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl 
gd, cir::FuncOp fn,
   {
     LexicalScope lexScope(*this, fusedLoc, entryBB);
 
+    // Emit the standard function prologue.
     startFunction(gd, retTy, fn, funcType, args, loc, bodyRange.getBegin());
 
     // Save parameters for coroutine function.
@@ -656,6 +657,7 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl 
gd, cir::FuncOp fn,
       // copy-constructors.
       emitImplicitAssignmentOperatorBody(args);
     } else if (body) {
+      // Emit standard function body.
       if (mlir::failed(emitFunctionBody(body))) {
         return nullptr;
       }
@@ -683,6 +685,8 @@ void CIRGenFunction::emitConstructorBody(FunctionArgList 
&args) {
           ctorType == Ctor_Complete) &&
          "can only generate complete ctor for this ABI");
 
+  cgm.setCXXSpecialMemberAttr(cast<cir::FuncOp>(curFn), ctor);
+
   if (ctorType == Ctor_Complete && isConstructorDelegationValid(ctor) &&
       cgm.getTarget().getCXXABI().hasConstructorVariants()) {
     emitDelegateCXXConstructorCall(ctor, Ctor_Base, args, ctor->getEndLoc());
@@ -721,6 +725,8 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList 
&args) {
   const CXXDestructorDecl *dtor = cast<CXXDestructorDecl>(curGD.getDecl());
   CXXDtorType dtorType = curGD.getDtorType();
 
+  cgm.setCXXSpecialMemberAttr(cast<cir::FuncOp>(curFn), dtor);
+
   // For an abstract class, non-base destructors are never used (and can't
   // be emitted in general, because vbase dtors may not have been validated
   // by Sema), but the Itanium ABI doesn't make them optional and Clang may
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index c1f2581eb96e3..a94c40e7580ff 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -2211,6 +2211,9 @@ CIRGenModule::createCIRFunction(mlir::Location loc, 
StringRef name,
 
     assert(!cir::MissingFeatures::opFuncExtraAttrs());
 
+    // Mark C++ special member functions (Constructor, Destructor etc.)
+    setCXXSpecialMemberAttr(func, funcDecl);
+
     if (!cgf)
       theModule.push_back(func);
   }
@@ -2226,6 +2229,54 @@ CIRGenModule::createCIRBuiltinFunction(mlir::Location 
loc, StringRef name,
   return fnOp;
 }
 
+void CIRGenModule::setCXXSpecialMemberAttr(
+    cir::FuncOp funcOp, const clang::FunctionDecl *funcDecl) {
+  if (!funcDecl)
+    return;
+
+  if (const auto *dtor = dyn_cast<CXXDestructorDecl>(funcDecl)) {
+    auto cxxDtor = cir::CXXDtorAttr::get(
+        convertType(getASTContext().getCanonicalTagType(dtor->getParent())),
+        dtor->isTrivial());
+    funcOp.setCxxSpecialMemberAttr(cxxDtor);
+    return;
+  }
+
+  if (const auto *ctor = dyn_cast<CXXConstructorDecl>(funcDecl)) {
+    cir::CtorKind ctorKind = cir::CtorKind::Custom;
+    if (ctor->isDefaultConstructor())
+      ctorKind = cir::CtorKind::Default;
+    else if (ctor->isCopyConstructor())
+      ctorKind = cir::CtorKind::Copy;
+    else if (ctor->isMoveConstructor())
+      ctorKind = cir::CtorKind::Move;
+
+    auto cxxCtor = cir::CXXCtorAttr::get(
+        convertType(getASTContext().getCanonicalTagType(ctor->getParent())),
+        ctorKind, ctor->isTrivial());
+    funcOp.setCxxSpecialMemberAttr(cxxCtor);
+    return;
+  }
+
+  const auto *method = dyn_cast<CXXMethodDecl>(funcDecl);
+  if (method && (method->isCopyAssignmentOperator() ||
+                 method->isMoveAssignmentOperator())) {
+    cir::AssignKind assignKind;
+    if (method->isCopyAssignmentOperator())
+      assignKind = cir::AssignKind::Copy;
+    else if (method->isMoveAssignmentOperator())
+      assignKind = cir::AssignKind::Move;
+    else
+      llvm_unreachable("unexpected assignment operator kind");
+
+    auto cxxAssign = cir::CXXAssignAttr::get(
+        convertType(getASTContext().getCanonicalTagType(method->getParent())),
+        assignKind, method->isTrivial());
+    funcOp.setCxxSpecialMemberAttr(cxxAssign);
+    return;
+  }
+}
+
 cir::FuncOp CIRGenModule::createRuntimeFunction(cir::FuncType ty,
                                                 StringRef name, 
mlir::ArrayAttr,
                                                 [[maybe_unused]] bool isLocal,
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h 
b/clang/lib/CIR/CodeGen/CIRGenModule.h
index dc28d9e8e9d33..3ac88c674d66e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -497,6 +497,10 @@ class CIRGenModule : public CIRGenTypeCache {
                                        cir::FuncType ty,
                                        const clang::FunctionDecl *fd);
 
+  /// Mark the function as a special member (e.g. constructor, destructor)
+  void setCXXSpecialMemberAttr(cir::FuncOp funcOp,
+                               const clang::FunctionDecl *funcDecl);
+
   cir::FuncOp createRuntimeFunction(cir::FuncType ty, llvm::StringRef name,
                                     mlir::ArrayAttr = {}, bool isLocal = false,
                                     bool assumeConvergent = false);
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 9ac5efe0e41c7..e6d121ff9a10c 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1658,6 +1658,7 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, 
OperationState &state) {
   mlir::StringAttr visNameAttr = getSymVisibilityAttrName(state.name);
   mlir::StringAttr visibilityNameAttr = 
getGlobalVisibilityAttrName(state.name);
   mlir::StringAttr dsoLocalNameAttr = getDsoLocalAttrName(state.name);
+  mlir::StringAttr specialMemberAttr = getCxxSpecialMemberAttrName(state.name);
 
   if (::mlir::succeeded(parser.parseOptionalKeyword(builtinNameAttr.strref())))
     state.addAttribute(builtinNameAttr, parser.getBuilder().getUnitAttr());
@@ -1756,6 +1757,20 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, 
OperationState &state) {
     return success();
   };
 
+  // Parse CXXSpecialMember attribute
+  if (parser.parseOptionalKeyword("special_member").succeeded()) {
+    cir::CXXCtorAttr ctorAttr;
+    cir::CXXDtorAttr dtorAttr;
+    if (parser.parseLess().failed())
+      return failure();
+    if (parser.parseOptionalAttribute(ctorAttr).has_value())
+      state.addAttribute(specialMemberAttr, ctorAttr);
+    else if (parser.parseOptionalAttribute(dtorAttr).has_value())
+      state.addAttribute(specialMemberAttr, dtorAttr);
+    if (parser.parseGreater().failed())
+      return failure();
+  }
+
   if (parseGlobalDtorCtor("global_ctor", [&](std::optional<int> priority) {
         mlir::IntegerAttr globalCtorPriorityAttr =
             builder.getI32IntegerAttr(priority.value_or(65535));
@@ -1833,6 +1848,43 @@ bool cir::FuncOp::isDeclaration() {
   return false;
 }
 
+bool cir::FuncOp::isCXXSpecialMemberFunction() {
+  return getCxxSpecialMemberAttr() != nullptr;
+}
+
+bool cir::FuncOp::isCxxConstructor() {
+  auto attr = getCxxSpecialMemberAttr();
+  return attr && dyn_cast<CXXCtorAttr>(attr);
+}
+
+bool cir::FuncOp::isCxxDestructor() {
+  auto attr = getCxxSpecialMemberAttr();
+  return attr && dyn_cast<CXXDtorAttr>(attr);
+}
+
+bool cir::FuncOp::isCxxSpecialAssignment() {
+  auto attr = getCxxSpecialMemberAttr();
+  return attr && dyn_cast<CXXAssignAttr>(attr);
+}
+
+std::optional<CtorKind> cir::FuncOp::getCxxConstructorKind() {
+  auto attr = getCxxSpecialMemberAttr();
+  if (attr) {
+    if (auto ctor = dyn_cast<CXXCtorAttr>(attr))
+      return ctor.getCtorKind();
+  }
+  return std::nullopt;
+}
+
+std::optional<AssignKind> cir::FuncOp::getCxxSpecialAssignKind() {
+  auto attr = getCxxSpecialMemberAttr();
+  if (attr) {
+    if (auto assign = dyn_cast<CXXAssignAttr>(attr))
+      return assign.getAssignKind();
+  }
+  return std::nullopt;
+}
+
 mlir::Region *cir::FuncOp::getCallableRegion() {
   // TODO(CIR): This function will have special handling for aliases and a
   // check for an external function, once those features have been upstreamed.
@@ -1883,6 +1935,12 @@ void cir::FuncOp::print(OpAsmPrinter &p) {
     p << ")";
   }
 
+  if (auto specialMemberAttr = getCxxSpecialMember()) {
+    p << " special_member<";
+    p.printAttribute(*specialMemberAttr);
+    p << '>';
+  }
+
   if (auto globalCtorPriority = getGlobalCtorPriority()) {
     p << " global_ctor";
     if (globalCtorPriority.value() != 65535)
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp 
b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 29b1211d2c351..12e2bdfebbb80 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -8,10 +8,12 @@
 
 #include "LoweringPrepareCXXABI.h"
 #include "PassDetail.h"
+#include "mlir/IR/Attributes.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/Basic/Module.h"
 #include "clang/Basic/TargetInfo.h"
 #include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h"
+#include "clang/CIR/Dialect/IR/CIRAttrs.h"
 #include "clang/CIR/Dialect/IR/CIRDialect.h"
 #include "clang/CIR/Dialect/IR/CIROpsEnums.h"
 #include "clang/CIR/Dialect/Passes.h"
@@ -72,6 +74,7 @@ struct LoweringPreparePass
   void lowerDynamicCastOp(cir::DynamicCastOp op);
   void lowerArrayDtor(cir::ArrayDtor op);
   void lowerArrayCtor(cir::ArrayCtor op);
+  void lowerTrivialConstructorCall(cir::CallOp op);
 
   /// Build the function that initializes the specified global
   cir::FuncOp buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op);
@@ -984,6 +987,29 @@ void LoweringPreparePass::lowerArrayCtor(cir::ArrayCtor 
op) {
                              true);
 }
 
+void LoweringPreparePass::lowerTrivialConstructorCall(cir::CallOp op) {
+  FuncOp funcOp = getCalledFunction(op);
+  if (!funcOp)
+    return;
+
+  mlir::Attribute cxxSpecialMember = funcOp.getCxxSpecialMemberAttr();
+  if (!cxxSpecialMember)
+    return;
+
+  if (auto cxxCtor = dyn_cast<cir::CXXCtorAttr>(cxxSpecialMember)) {
+    if (cxxCtor.getCtorKind() == cir::CtorKind::Copy) {
+      // Replace the trivial copy constructor call with a `CopyOp`
+      CIRBaseBuilderTy builder(getContext());
+      auto operands = op.getOperands();
+      mlir::Value dest = operands[0];
+      mlir::Value src = operands[1];
+      builder.setInsertionPoint(op);
+      builder.createCopy(dest, src);
+      op.erase();
+    }
+  }
+}
+
 void LoweringPreparePass::runOnOp(mlir::Operation *op) {
   if (auto arrayCtor = dyn_cast<cir::ArrayCtor>(op)) {
     lowerArrayCtor(arrayCtor);
@@ -1006,6 +1032,8 @@ void LoweringPreparePass::runOnOp(mlir::Operation *op) {
       globalCtorList.emplace_back(fnOp.getName(), globalCtor.value());
     else if (auto globalDtor = fnOp.getGlobalDtorPriority())
       globalDtorList.emplace_back(fnOp.getName(), globalDtor.value());
+  } else if (auto callOp = dyn_cast<cir::CallOp>(op)) {
+    lowerTrivialConstructorCall(callOp);
   }
 }
 
diff --git a/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp 
b/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp
new file mode 100644
index 0000000000000..815ef2c2aaa25
--- /dev/null
+++ b/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp
@@ -0,0 +1,59 @@
+// RUN: %clang_cc1 -std=c++11 -triple aarch64-none-linux-android21 -fclangir 
-emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+
+struct Flub {
+  int a = 123;
+  // CIR: @_ZN4FlubC1ERKS_(%arg0: !cir.ptr<!rec_Flub> loc({{.*}}), %arg1: 
!cir.ptr<!rec_Flub> loc({{.*}})) special_member<#cir.cxx_ctor<!rec_Flub, copy, 
trivial true>>
+  // CIR: @_ZN4FlubC2EOS_(%arg0: !cir.ptr<!rec_Flub> loc({{.*}}), %arg1: 
!cir.ptr<!rec_Flub> loc({{.*}})) special_member<#cir.cxx_ctor<!rec_Flub, move, 
trivial true>
+  // CIR: @_ZN4FlubaSERKS_(%arg0: !cir.ptr<!rec_Flub> loc({{.*}}), %arg1: 
!cir.ptr<!rec_Flub> loc({{.*}})) -> !cir.ptr<!rec_Flub> 
special_member<#cir.cxx_assign<!rec_Flub, copy, trivial true>>
+  // CIR: @_ZN4FlubaSEOS_(%arg0: !cir.ptr<!rec_Flub> loc({{.*}}), %arg1: 
!cir.ptr<!rec_Flub> loc({{.*}})) -> !cir.ptr<!rec_Flub> 
special_member<#cir.cxx_assign<!rec_Flub, move, trivial true>>
+};
+
+struct Foo {
+  int a;
+
+  // CIR: @_ZN3FooC2Ev(%arg0: !cir.ptr<!rec_Foo> loc({{.*}})) 
special_member<#cir.cxx_ctor<!rec_Foo, default>>
+  Foo() : a(123) {}
+
+  // CIR: @_ZN3FooC2ERKS_(%arg0: !cir.ptr<!rec_Foo> loc({{.*}}), %arg1: 
!cir.ptr<!rec_Foo> loc({{.*}})) special_member<#cir.cxx_ctor<!rec_Foo, copy>>
+  Foo(const Foo &other) : a(other.a) {}
+
+  // CIR: @_ZN3FooC2EOS_(%arg0: !cir.ptr<!rec_Foo> loc({{.*}}), %arg1: 
!cir.ptr<!rec_Foo> loc({{.*}})) special_member<#cir.cxx_ctor<!rec_Foo, move>>
+  Foo(Foo &&other) noexcept : a(other.a) { other.a = 0; }
+
+  // CIR: @_ZN3FooaSERKS_(%arg0: !cir.ptr<!rec_Foo> loc({{.*}}), %arg1: 
!cir.ptr<!rec_Foo> loc({{.*}})) -> !cir.ptr<!rec_Foo> 
special_member<#cir.cxx_assign<!rec_Foo, copy>>
+  Foo &operator=(const Foo &other) {
+    if (this != &other) {
+      a = other.a;
+    }
+    return *this;
+  }
+
+  // CIR: @_ZN3FooaSEOS_(%arg0: !cir.ptr<!rec_Foo> loc({{.*}}), %arg1: 
!cir.ptr<!rec_Foo> loc({{.*}})) -> !cir.ptr<!rec_Foo> 
special_member<#cir.cxx_assign<!rec_Foo, move>>
+  Foo &operator=(Foo &&other) noexcept {
+    if (this != &other) {
+      a = other.a;
+      other.a = 0;
+    }
+    return *this;
+  }
+
+  // CIR: @_ZN3FooD1Ev(!cir.ptr<!rec_Foo>) 
special_member<#cir.cxx_dtor<!rec_Foo>>
+  ~Foo();
+};
+
+void trivial() {
+  Flub f1{};
+  Flub f2 = f1;
+  Flub f3 = static_cast<Flub&&>(f1);
+  f2 = f1;
+  f1 = static_cast<Flub&&>(f3);
+}
+
+void non_trivial() {
+  Foo f1{};
+  Foo f2 = f1;
+  Foo f3 = static_cast<Foo&&>(f1);
+  f2 = f1;
+  f1 = static_cast<Foo&&>(f3);
+}

From 026c8c339f6e27a6cfe4312633ea30698b9d2a83 Mon Sep 17 00:00:00 2001
From: hhuebner <[email protected]>
Date: Sun, 16 Nov 2025 13:24:25 +0100
Subject: [PATCH 2/6] Feedback

---
 clang/lib/CIR/CodeGen/CIRGenModule.cpp | 38 ++++++++++++++------------
 1 file changed, 21 insertions(+), 17 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index a94c40e7580ff..3b9c5cfbb0243 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -2229,6 +2229,24 @@ CIRGenModule::createCIRBuiltinFunction(mlir::Location 
loc, StringRef name,
   return fnOp;
 }
 
+static cir::CtorKind getCtorKindFromDecl(const CXXConstructorDecl *ctor) {
+  if (ctor->isDefaultConstructor())
+    return cir::CtorKind::Default;
+  if (ctor->isCopyConstructor())
+    return cir::CtorKind::Copy;
+  if (ctor->isMoveConstructor())
+    return cir::CtorKind::Move;
+  return cir::CtorKind::Custom;
+}
+
+static cir::AssignKind getAssignKindFromDecl(const CXXMethodDecl *method) {
+  if (method->isCopyAssignmentOperator())
+    return cir::AssignKind::Copy;
+  if (method->isMoveAssignmentOperator())
+    return cir::AssignKind::Move;
+  llvm_unreachable("not a copy or move assignment operator");
+}
+
 void CIRGenModule::setCXXSpecialMemberAttr(
     cir::FuncOp funcOp, const clang::FunctionDecl *funcDecl) {
   if (!funcDecl)
@@ -2243,17 +2261,10 @@ void CIRGenModule::setCXXSpecialMemberAttr(
   }
 
   if (const auto *ctor = dyn_cast<CXXConstructorDecl>(funcDecl)) {
-    cir::CtorKind ctorKind = cir::CtorKind::Custom;
-    if (ctor->isDefaultConstructor())
-      ctorKind = cir::CtorKind::Default;
-    else if (ctor->isCopyConstructor())
-      ctorKind = cir::CtorKind::Copy;
-    else if (ctor->isMoveConstructor())
-      ctorKind = cir::CtorKind::Move;
-
+    cir::CtorKind kind = getCtorKindFromDecl(ctor);
     auto cxxCtor = cir::CXXCtorAttr::get(
         convertType(getASTContext().getCanonicalTagType(ctor->getParent())),
-        ctorKind, ctor->isTrivial());
+        kind, ctor->isTrivial());
     funcOp.setCxxSpecialMemberAttr(cxxCtor);
     return;
   }
@@ -2261,14 +2272,7 @@ void CIRGenModule::setCXXSpecialMemberAttr(
   const auto *method = dyn_cast<CXXMethodDecl>(funcDecl);
   if (method && (method->isCopyAssignmentOperator() ||
                  method->isMoveAssignmentOperator())) {
-    cir::AssignKind assignKind;
-    if (method->isCopyAssignmentOperator())
-      assignKind = cir::AssignKind::Copy;
-    else if (method->isMoveAssignmentOperator())
-      assignKind = cir::AssignKind::Move;
-    else
-      llvm_unreachable("unexpected assignment operator kind");
-
+    cir::AssignKind assignKind = getAssignKindFromDecl(method);
     auto cxxAssign = cir::CXXAssignAttr::get(
         convertType(getASTContext().getCanonicalTagType(method->getParent())),
         assignKind, method->isTrivial());

From 571eb6a9b69f41dc8ca0dc3dc6a713b13cdf52d8 Mon Sep 17 00:00:00 2001
From: hhuebner <[email protected]>
Date: Sun, 16 Nov 2025 13:47:45 +0100
Subject: [PATCH 3/6] Add cir parsing test

---
 .../Dialect/Transforms/LoweringPrepare.cpp    | 26 --------------
 clang/test/CIR/IR/func.cir                    | 34 +++++++++++++++++++
 2 files changed, 34 insertions(+), 26 deletions(-)

diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp 
b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 12e2bdfebbb80..96a03ec3b8e9f 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -74,7 +74,6 @@ struct LoweringPreparePass
   void lowerDynamicCastOp(cir::DynamicCastOp op);
   void lowerArrayDtor(cir::ArrayDtor op);
   void lowerArrayCtor(cir::ArrayCtor op);
-  void lowerTrivialConstructorCall(cir::CallOp op);
 
   /// Build the function that initializes the specified global
   cir::FuncOp buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op);
@@ -987,29 +986,6 @@ void LoweringPreparePass::lowerArrayCtor(cir::ArrayCtor 
op) {
                              true);
 }
 
-void LoweringPreparePass::lowerTrivialConstructorCall(cir::CallOp op) {
-  FuncOp funcOp = getCalledFunction(op);
-  if (!funcOp)
-    return;
-
-  mlir::Attribute cxxSpecialMember = funcOp.getCxxSpecialMemberAttr();
-  if (!cxxSpecialMember)
-    return;
-
-  if (auto cxxCtor = dyn_cast<cir::CXXCtorAttr>(cxxSpecialMember)) {
-    if (cxxCtor.getCtorKind() == cir::CtorKind::Copy) {
-      // Replace the trivial copy constructor call with a `CopyOp`
-      CIRBaseBuilderTy builder(getContext());
-      auto operands = op.getOperands();
-      mlir::Value dest = operands[0];
-      mlir::Value src = operands[1];
-      builder.setInsertionPoint(op);
-      builder.createCopy(dest, src);
-      op.erase();
-    }
-  }
-}
-
 void LoweringPreparePass::runOnOp(mlir::Operation *op) {
   if (auto arrayCtor = dyn_cast<cir::ArrayCtor>(op)) {
     lowerArrayCtor(arrayCtor);
@@ -1032,8 +1008,6 @@ void LoweringPreparePass::runOnOp(mlir::Operation *op) {
       globalCtorList.emplace_back(fnOp.getName(), globalCtor.value());
     else if (auto globalDtor = fnOp.getGlobalDtorPriority())
       globalDtorList.emplace_back(fnOp.getName(), globalDtor.value());
-  } else if (auto callOp = dyn_cast<cir::CallOp>(op)) {
-    lowerTrivialConstructorCall(callOp);
   }
 }
 
diff --git a/clang/test/CIR/IR/func.cir b/clang/test/CIR/IR/func.cir
index 6e91898a3b452..1f25e98a1eee0 100644
--- a/clang/test/CIR/IR/func.cir
+++ b/clang/test/CIR/IR/func.cir
@@ -143,3 +143,37 @@ cir.func @global_dtor_with_priority() global_dtor(201) {
 // CHECK: }
 
 }
+
+!rec_Foo = !cir.record<struct "Foo" {!s32i}>
+
+cir.func @Foo_default() special_member<#cir.cxx_ctor<!rec_Foo, default>> {
+  cir.return
+}
+
+// CHECK: cir.func @Foo_default() special_member<#cir.cxx_ctor<!rec_Foo, 
default>> {
+// CHECK:   cir.return
+// CHECK: }
+
+cir.func @Foo_trivial_copy() special_member<#cir.cxx_ctor<!rec_Foo, copy, 
trivial true>> {
+  cir.return
+}
+
+// CHECK: cir.func @Foo_trivial_copy() special_member<#cir.cxx_ctor<!rec_Foo, 
copy, trivial true>> {
+// CHECK:   cir.return
+// CHECK: }
+
+cir.func @Foo_destructor() special_member<#cir.cxx_dtor<!rec_Foo>> {
+  cir.return
+}
+
+// CHECK: cir.func @Foo_destructor() special_member<#cir.cxx_dtor<!rec_Foo>> {
+// CHECK:   cir.return
+// CHECK: }
+
+cir.func @Foo_move_assign() special_member<#cir.cxx_assign<!rec_Foo, move>> {
+  cir.return
+}
+
+// CHECK: cir.func @Foo_move_assign() special_member<#cir.cxx_assign<!rec_Foo, 
move>> {
+// CHECK:   cir.return
+// CHECK: }

From 33178d400b4365972d35c3fd244bc8268fd02032 Mon Sep 17 00:00:00 2001
From: hhuebner <[email protected]>
Date: Sun, 16 Nov 2025 13:57:17 +0100
Subject: [PATCH 4/6] assign parser

---
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index e6d121ff9a10c..a56980b93c128 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -12,6 +12,7 @@
 
 #include "clang/CIR/Dialect/IR/CIRDialect.h"
 
+#include "clang/CIR/Dialect/IR/CIRAttrs.h"
 #include "clang/CIR/Dialect/IR/CIROpsEnums.h"
 #include "clang/CIR/Dialect/IR/CIRTypes.h"
 
@@ -1761,12 +1762,15 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, 
OperationState &state) {
   if (parser.parseOptionalKeyword("special_member").succeeded()) {
     cir::CXXCtorAttr ctorAttr;
     cir::CXXDtorAttr dtorAttr;
+    cir::CXXAssignAttr assignAttr;
     if (parser.parseLess().failed())
       return failure();
     if (parser.parseOptionalAttribute(ctorAttr).has_value())
       state.addAttribute(specialMemberAttr, ctorAttr);
     else if (parser.parseOptionalAttribute(dtorAttr).has_value())
       state.addAttribute(specialMemberAttr, dtorAttr);
+    else if (parser.parseOptionalAttribute(assignAttr).has_value())
+      state.addAttribute(specialMemberAttr, assignAttr);
     if (parser.parseGreater().failed())
       return failure();
   }

From 78ee0b97578885fb26740473b73346d3f60ab59b Mon Sep 17 00:00:00 2001
From: hhuebner <[email protected]>
Date: Sun, 16 Nov 2025 14:59:34 +0100
Subject: [PATCH 5/6] trivial member

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td |  7 ++++++-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp      | 18 ++++++++++++++++--
 2 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 163bd104ce04b..815af9bca217e 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2595,6 +2595,11 @@ def CIR_FuncOp : CIR_Op<"func", [
     /// Returns the kind of assignment operator (move, copy) this function
     /// represents, if any.
     std::optional<AssignKind> getCxxSpecialAssignKind();
+
+    /// Returns true if the function is a trivial C++ member functions such as
+    /// trivial default constructor, copy/move constructor, copy/move 
assignment,
+    /// or destructor.
+    bool isCxxTrivialMemberFunction();
 }];
 
   let hasCustomAssemblyFormat = 1;
@@ -4173,7 +4178,7 @@ def CIR_ObjSizeOp : CIR_Op<"objsize", [Pure]> {
     When the `min` attribute is present, the operation returns the minimum
     guaranteed accessible size. When absent (max mode), it returns the maximum
     possible object size. Corresponds to `llvm.objectsize`'s `min` argument.
-    
+
     The `dynamic` attribute determines if the value should be evaluated at
     runtime. Corresponds to `llvm.objectsize`'s `dynamic` argument.
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index a56980b93c128..26a7a6f2831dd 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -16,6 +16,7 @@
 #include "clang/CIR/Dialect/IR/CIROpsEnums.h"
 #include "clang/CIR/Dialect/IR/CIRTypes.h"
 
+#include "mlir/IR/Attributes.h"
 #include "mlir/IR/DialectImplementation.h"
 #include "mlir/Interfaces/ControlFlowInterfaces.h"
 #include "mlir/Interfaces/FunctionImplementation.h"
@@ -1872,7 +1873,7 @@ bool cir::FuncOp::isCxxSpecialAssignment() {
 }
 
 std::optional<CtorKind> cir::FuncOp::getCxxConstructorKind() {
-  auto attr = getCxxSpecialMemberAttr();
+  mlir::Attribute attr = getCxxSpecialMemberAttr();
   if (attr) {
     if (auto ctor = dyn_cast<CXXCtorAttr>(attr))
       return ctor.getCtorKind();
@@ -1881,7 +1882,7 @@ std::optional<CtorKind> 
cir::FuncOp::getCxxConstructorKind() {
 }
 
 std::optional<AssignKind> cir::FuncOp::getCxxSpecialAssignKind() {
-  auto attr = getCxxSpecialMemberAttr();
+  mlir::Attribute attr = getCxxSpecialMemberAttr();
   if (attr) {
     if (auto assign = dyn_cast<CXXAssignAttr>(attr))
       return assign.getAssignKind();
@@ -1889,6 +1890,19 @@ std::optional<AssignKind> 
cir::FuncOp::getCxxSpecialAssignKind() {
   return std::nullopt;
 }
 
+bool cir::FuncOp::isCxxTrivialMemberFunction() {
+  mlir::Attribute attr = getCxxSpecialMemberAttr();
+  if (attr) {
+    if (auto ctor = dyn_cast<CXXCtorAttr>(attr))
+      return ctor.getIsTrivial();
+    if (auto dtor = dyn_cast<CXXDtorAttr>(attr))
+      return dtor.getIsTrivial();
+    if (auto assign = dyn_cast<CXXAssignAttr>(attr))
+      return assign.getIsTrivial();
+  }
+  return false;
+}
+
 mlir::Region *cir::FuncOp::getCallableRegion() {
   // TODO(CIR): This function will have special handling for aliases and a
   // check for an external function, once those features have been upstreamed.

From 13edd0d861f33f449c724c574cc79bcfd1a0d7c3 Mon Sep 17 00:00:00 2001
From: hhuebner <[email protected]>
Date: Wed, 19 Nov 2025 01:16:17 +0100
Subject: [PATCH 6/6] feedback

---
 clang/include/clang/CIR/Dialect/IR/CIRAttrs.td | 1 -
 clang/include/clang/CIR/Dialect/IR/CIROps.td   | 1 -
 2 files changed, 2 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 07a5b1f3a06c8..81d7fe52f8291 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -835,7 +835,6 @@ def CIR_CtorKind : CIR_I32EnumAttr<"CtorKind", "CXX 
Constructor Kind", [
   let genSpecializedAttr = 0;
 }
 
-
 def CIR_CXXCtorAttr : CIR_Attr<"CXXCtor", "cxx_ctor"> {
   let summary = "Marks a function as a C++ constructor";
   let description = [{
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 815af9bca217e..1ee52782fa99b 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2583,7 +2583,6 @@ def CIR_FuncOp : CIR_Op<"func", [
     bool isCXXSpecialMemberFunction();
 
     bool isCxxConstructor();
-
     bool isCxxDestructor();
 
     /// Returns true if this function is a copy or move assignment operator.

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

Reply via email to