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
