[clang] [CIR] Add CXX special member attribute to cir::FuncOp (PR #167975)
Hendrik Hübner via cfe-commits
cfe-commits at lists.llvm.org
Sun Nov 16 04:57:30 PST 2025
https://github.com/HendrikHuebner updated https://github.com/llvm/llvm-project/pull/167975
>From afa84ef160c6f3f58cd65eb08f8d883d336e3111 Mon Sep 17 00:00:00 2001
From: hhuebner <hendrik.huebner18 at gmail.com>
Date: Wed, 12 Nov 2025 21:20:30 +0100
Subject: [PATCH 1/4] 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 <hendrik.huebner18 at gmail.com>
Date: Sun, 16 Nov 2025 13:24:25 +0100
Subject: [PATCH 2/4] 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 <hendrik.huebner18 at gmail.com>
Date: Sun, 16 Nov 2025 13:47:45 +0100
Subject: [PATCH 3/4] 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 <hendrik.huebner18 at gmail.com>
Date: Sun, 16 Nov 2025 13:57:17 +0100
Subject: [PATCH 4/4] 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();
}
More information about the cfe-commits
mailing list