https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/151600
>From d90efb878b2e27779099e21e63a105d14f99ba62 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <akay...@nvidia.com> Date: Thu, 31 Jul 2025 14:36:54 -0700 Subject: [PATCH 1/2] [CIR] Handle expression with cleanups This adds code to handle expressions with cleanup, including materializing a temporary object for the expression. --- clang/include/clang/CIR/MissingFeatures.h | 1 + clang/lib/CIR/CodeGen/Address.h | 7 + clang/lib/CIR/CodeGen/CIRGenDecl.cpp | 37 +++--- clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 148 +++++++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 24 ++++ clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 6 + clang/lib/CIR/CodeGen/CIRGenFunction.h | 21 +++ clang/test/CIR/CodeGen/cleanup.cpp | 14 ++ 8 files changed, 242 insertions(+), 16 deletions(-) diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index ad3329d3be32e..32a53a1208092 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -222,6 +222,7 @@ struct MissingFeatures { static bool lowerAggregateLoadStore() { return false; } static bool lowerModeOptLevel() { return false; } static bool maybeHandleStaticInExternC() { return false; } + static bool mergeAllConstants() { return false; } static bool metaDataNode() { return false; } static bool moduleNameHash() { return false; } static bool msabi() { return false; } diff --git a/clang/lib/CIR/CodeGen/Address.h b/clang/lib/CIR/CodeGen/Address.h index 6f76c3ebb1c5e..72c1d524bf804 100644 --- a/clang/lib/CIR/CodeGen/Address.h +++ b/clang/lib/CIR/CodeGen/Address.h @@ -101,6 +101,13 @@ class Address { } clang::CharUnits getAlignment() const { return alignment; } + + /// Get the operation which defines this address. + mlir::Operation *getDefiningOp() const { + if (!isValid()) + return nullptr; + return getPointer().getDefiningOp(); + } }; } // namespace clang::CIRGen diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp index 6527fb5697f7c..cf03a427c47b3 100644 --- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp @@ -649,6 +649,27 @@ void CIRGenFunction::emitNullabilityCheck(LValue lhs, mlir::Value rhs, assert(!cir::MissingFeatures::sanitizers()); } +namespace { +struct DestroyObject final : EHScopeStack::Cleanup { + DestroyObject(Address addr, QualType type, + CIRGenFunction::Destroyer *destroyer) + : addr(addr), type(type), destroyer(destroyer) {} + + Address addr; + QualType type; + CIRGenFunction::Destroyer *destroyer; + + void emit(CIRGenFunction &cgf) override { + cgf.emitDestroy(addr, type, destroyer); + } +}; +} // namespace + +void CIRGenFunction::pushDestroy(CleanupKind cleanupKind, Address addr, + QualType type, Destroyer *destroyer) { + pushFullExprCleanup<DestroyObject>(cleanupKind, addr, type, destroyer); +} + /// Destroys all the elements of the given array, beginning from last to first. /// The array cannot be zero-length. /// @@ -736,22 +757,6 @@ CIRGenFunction::getDestroyer(QualType::DestructionKind kind) { llvm_unreachable("Unknown DestructionKind"); } -namespace { -struct DestroyObject final : EHScopeStack::Cleanup { - DestroyObject(Address addr, QualType type, - CIRGenFunction::Destroyer *destroyer) - : addr(addr), type(type), destroyer(destroyer) {} - - Address addr; - QualType type; - CIRGenFunction::Destroyer *destroyer; - - void emit(CIRGenFunction &cgf) override { - cgf.emitDestroy(addr, type, destroyer); - } -}; -} // namespace - /// Enter a destroy cleanup for the given local variable. void CIRGenFunction::emitAutoVarTypeCleanup( const CIRGenFunction::AutoVarEmission &emission, diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index c18498f80e99f..16c5b6cc9bad0 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -1096,6 +1096,154 @@ void CIRGenFunction::emitAnyExprToMem(const Expr *e, Address location, llvm_unreachable("bad evaluation kind"); } +static Address createReferenceTemporary(CIRGenFunction &cgf, + const MaterializeTemporaryExpr *m, + const Expr *inner) { + // TODO(cir): cgf.getTargetHooks(); + switch (m->getStorageDuration()) { + case SD_FullExpression: + case SD_Automatic: { + QualType ty = inner->getType(); + + assert(!cir::MissingFeatures::mergeAllConstants()); + + // The temporary memory should be created in the same scope as the extending + // declaration of the temporary materialization expression. + cir::AllocaOp extDeclAlloca; + if (const clang::ValueDecl *extDecl = m->getExtendingDecl()) { + auto extDeclAddrIter = cgf.localDeclMap.find(extDecl); + if (extDeclAddrIter != cgf.localDeclMap.end()) { + extDeclAlloca = mlir::dyn_cast_if_present<cir::AllocaOp>( + extDeclAddrIter->second.getDefiningOp()); + } + } + mlir::OpBuilder::InsertPoint ip; + if (extDeclAlloca) + ip = {extDeclAlloca->getBlock(), extDeclAlloca->getIterator()}; + return cgf.createMemTemp(ty, cgf.getLoc(m->getSourceRange()), + cgf.getCounterRefTmpAsString(), /*alloca=*/nullptr, + ip); + } + case SD_Thread: + case SD_Static: { + cgf.cgm.errorNYI( + m->getSourceRange(), + "createReferenceTemporary: static/thread storage duration"); + return Address::invalid(); + } + + case SD_Dynamic: + llvm_unreachable("temporary can't have dynamic storage duration"); + } + llvm_unreachable("unknown storage duration"); +} + +static void pushTemporaryCleanup(CIRGenFunction &cgf, + const MaterializeTemporaryExpr *m, + const Expr *e, Address referenceTemporary) { + // Objective-C++ ARC: + // If we are binding a reference to a temporary that has ownership, we + // need to perform retain/release operations on the temporary. + // + // FIXME(ogcg): This should be looking at e, not m. + if (m->getType().getObjCLifetime()) { + cgf.cgm.errorNYI(e->getSourceRange(), "pushTemporaryCleanup: ObjCLifetime"); + return; + } + + clang::CXXDestructorDecl *referenceTemporaryDtor = nullptr; + if (const clang::RecordType *rt = e->getType() + ->getBaseElementTypeUnsafe() + ->getAs<clang::RecordType>()) { + // Get the destructor for the reference temporary. + auto *classDecl = cast<clang::CXXRecordDecl>(rt->getDecl()); + if (!classDecl->hasTrivialDestructor()) + referenceTemporaryDtor = classDecl->getDestructor(); + } + + if (!referenceTemporaryDtor) + return; + + // Call the destructor for the temporary. + switch (m->getStorageDuration()) { + case SD_Static: + case SD_Thread: + cgf.cgm.errorNYI(e->getSourceRange(), + "pushTemporaryCleanup: static/thread storage duration"); + return; + + case SD_FullExpression: + cgf.pushDestroy(NormalAndEHCleanup, referenceTemporary, e->getType(), + CIRGenFunction::destroyCXXObject); + break; + + case SD_Automatic: + cgf.cgm.errorNYI(e->getSourceRange(), + "pushTemporaryCleanup: automatic storage duration"); + break; + + case SD_Dynamic: + llvm_unreachable("temporary cannot have dynamic storage duration"); + } +} + +LValue CIRGenFunction::emitMaterializeTemporaryExpr( + const MaterializeTemporaryExpr *m) { + const Expr *e = m->getSubExpr(); + + assert((!m->getExtendingDecl() || !isa<VarDecl>(m->getExtendingDecl()) || + !cast<VarDecl>(m->getExtendingDecl())->isARCPseudoStrong()) && + "Reference should never be pseudo-strong!"); + + // FIXME: ideally this would use emitAnyExprToMem, however, we cannot do so + // as that will cause the lifetime adjustment to be lost for ARC + auto ownership = m->getType().getObjCLifetime(); + if (ownership != Qualifiers::OCL_None && + ownership != Qualifiers::OCL_ExplicitNone) { + cgm.errorNYI(e->getSourceRange(), + "emitMaterializeTemporaryExpr: ObjCLifetime"); + return {}; + } + + SmallVector<const Expr *, 2> commaLHSs; + SmallVector<SubobjectAdjustment, 2> adjustments; + e = e->skipRValueSubobjectAdjustments(commaLHSs, adjustments); + + for (const Expr *ignored : commaLHSs) + emitIgnoredExpr(ignored); + + if (isa<OpaqueValueExpr>(e)) { + cgm.errorNYI(e->getSourceRange(), + "emitMaterializeTemporaryExpr: OpaqueValueExpr"); + return {}; + } + + // Create and initialize the reference temporary. + Address object = createReferenceTemporary(*this, m, e); + + if (auto var = + mlir::dyn_cast<cir::GlobalOp>(object.getPointer().getDefiningOp())) { + // TODO(cir): add something akin to stripPointerCasts() to ptr above + cgm.errorNYI(e->getSourceRange(), "emitMaterializeTemporaryExpr: GlobalOp"); + return {}; + } else { + assert(!cir::MissingFeatures::emitLifetimeMarkers()); + emitAnyExprToMem(e, object, Qualifiers(), /*isInitializer=*/true); + } + pushTemporaryCleanup(*this, m, e, object); + + // Perform derived-to-base casts and/or field accesses, to get from the + // temporary object we created (and, potentially, for which we extended + // the lifetime) to the subobject we're binding the reference to. + if (!adjustments.empty()) { + cgm.errorNYI(e->getSourceRange(), + "emitMaterializeTemporaryExpr: Adjustments"); + return {}; + } + + return makeAddrLValue(object, m->getType(), AlignmentSource::Decl); +} + LValue CIRGenFunction::emitCompoundLiteralLValue(const CompoundLiteralExpr *e) { if (e->isFileScope()) { cgm.errorNYI(e->getSourceRange(), "emitCompoundLiteralLValue: FileScope"); diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 2523b0ff33787..d20c15d02b31a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -626,6 +626,7 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { mlir::Value VisitCXXThisExpr(CXXThisExpr *te) { return cgf.loadCXXThis(); } + mlir::Value VisitExprWithCleanups(ExprWithCleanups *e); mlir::Value VisitCXXNewExpr(const CXXNewExpr *e) { return cgf.emitCXXNewExpr(e); } @@ -1217,6 +1218,29 @@ mlir::Value ScalarExprEmitter::emitCompoundAssign( return emitLoadOfLValue(lhs, e->getExprLoc()); } +mlir::Value ScalarExprEmitter::VisitExprWithCleanups(ExprWithCleanups *e) { + mlir::Location scopeLoc = cgf.getLoc(e->getSourceRange()); + mlir::OpBuilder &builder = cgf.builder; + + auto scope = cir::ScopeOp::create( + builder, scopeLoc, + /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Type &yieldTy, mlir::Location loc) { + CIRGenFunction::LexicalScope lexScope{cgf, loc, + builder.getInsertionBlock()}; + mlir::Value scopeYieldVal = Visit(e->getSubExpr()); + if (scopeYieldVal) { + // Defend against dominance problems caused by jumps out of expression + // evaluation through the shared cleanup block. + lexScope.forceCleanup(); + cir::YieldOp::create(builder, loc, scopeYieldVal); + yieldTy = scopeYieldVal.getType(); + } + }); + + return scope.getNumResults() > 0 ? scope->getResult(0) : nullptr; +} + } // namespace LValue diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index c65d0254bf8e6..7b2efb7a1b423 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -801,6 +801,8 @@ LValue CIRGenFunction::emitLValue(const Expr *e) { case Expr::CXXDynamicCastExprClass: case Expr::ImplicitCastExprClass: return emitCastLValue(cast<CastExpr>(e)); + case Expr::MaterializeTemporaryExprClass: + return emitMaterializeTemporaryExpr(cast<MaterializeTemporaryExpr>(e)); } } @@ -811,6 +813,10 @@ static std::string getVersionedTmpName(llvm::StringRef name, unsigned cnt) { return std::string(out.str()); } +std::string CIRGenFunction::getCounterRefTmpAsString() { + return getVersionedTmpName("ref.tmp", counterRefTmp++); +} + std::string CIRGenFunction::getCounterAggTmpAsString() { return getVersionedTmpName("agg.tmp", counterAggTmp++); } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 603f75078c519..a7880ae6dad40 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -325,7 +325,9 @@ class CIRGenFunction : public CIRGenTypeCache { }; /// Hold counters for incrementally naming temporaries + unsigned counterRefTmp = 0; unsigned counterAggTmp = 0; + std::string getCounterRefTmpAsString(); std::string getCounterAggTmpAsString(); /// Helpers to convert Clang's SourceLocation to a MLIR Location. @@ -604,6 +606,19 @@ class CIRGenFunction : public CIRGenTypeCache { void popCleanupBlocks(size_t oldCleanupStackDepth); void popCleanupBlock(); + /// Push a cleanup to be run at the end of the current full-expression. Safe + /// against the possibility that we're currently inside a + /// conditionally-evaluated expression. + template <class T, class... As> + void pushFullExprCleanup(CleanupKind kind, As... a) { + // If we're not in a conditional branch, or if none of the + // arguments requires saving, then use the unconditional cleanup. + if (!isInConditionalBranch()) + return ehStack.pushCleanup<T>(kind, a...); + + cgm.errorNYI("pushFullExprCleanup in conditional branch"); + } + /// Enters a new scope for capturing cleanups, all of which /// will be executed once the scope is exited. class RunCleanupsScope { @@ -619,6 +634,7 @@ class CIRGenFunction : public CIRGenTypeCache { protected: CIRGenFunction &cgf; + public: /// Enter a new cleanup scope. explicit RunCleanupsScope(CIRGenFunction &cgf) : performCleanup(true), cgf(cgf) { @@ -801,6 +817,9 @@ class CIRGenFunction : public CIRGenTypeCache { static Destroyer destroyCXXObject; + void pushDestroy(CleanupKind kind, Address addr, QualType type, + Destroyer *destroyer); + Destroyer *getDestroyer(clang::QualType::DestructionKind kind); /// ---------------------- @@ -1136,6 +1155,8 @@ class CIRGenFunction : public CIRGenTypeCache { const clang::FieldDecl *field, llvm::StringRef fieldName); + LValue emitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *e); + LValue emitMemberExpr(const MemberExpr *e); /// Given an expression with a pointer type, emit the value and compute our diff --git a/clang/test/CIR/CodeGen/cleanup.cpp b/clang/test/CIR/CodeGen/cleanup.cpp index 4196151352b79..0400d4b275fbb 100644 --- a/clang/test/CIR/CodeGen/cleanup.cpp +++ b/clang/test/CIR/CodeGen/cleanup.cpp @@ -81,3 +81,17 @@ void test_cleanup_nested() { // CHECK: } // CHECK: cir.call @_ZN5StrukD1Ev(%[[OUTER]]) nothrow : (!cir.ptr<!rec_Struk>) -> () // CHECK: cir.return + +void use_ref(const Struk &); + +void test_expr_with_cleanup() { + use_ref(Struk{}); +} + +// CHECK: cir.func{{.*}} @_Z22test_expr_with_cleanupv() +// CHECK: cir.scope { +// CHECK: %[[S:.*]] = cir.alloca !rec_Struk, !cir.ptr<!rec_Struk> +// CHECK: cir.call @_Z7use_refRK5Struk(%[[S]]) +// CHECK: cir.call @_ZN5StrukD1Ev(%[[S]]) nothrow : (!cir.ptr<!rec_Struk>) -> () +// CHECK: } +// CHECK: cir.return >From ba18556975e78d18036247c10b76036a4320cab0 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <akay...@nvidia.com> Date: Fri, 1 Aug 2025 10:09:47 -0700 Subject: [PATCH 2/2] Address review feedback --- clang/lib/CIR/CodeGen/Address.h | 4 ++++ clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 15 ++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/clang/lib/CIR/CodeGen/Address.h b/clang/lib/CIR/CodeGen/Address.h index 72c1d524bf804..6c927e9eda9cc 100644 --- a/clang/lib/CIR/CodeGen/Address.h +++ b/clang/lib/CIR/CodeGen/Address.h @@ -108,6 +108,10 @@ class Address { return nullptr; return getPointer().getDefiningOp(); } + + template <typename OpTy> OpTy getDefiningOp() const { + return mlir::dyn_cast_or_null<OpTy>(getDefiningOp()); + } }; } // namespace clang::CIRGen diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index 16c5b6cc9bad0..cc00e6917897e 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -1110,12 +1110,10 @@ static Address createReferenceTemporary(CIRGenFunction &cgf, // The temporary memory should be created in the same scope as the extending // declaration of the temporary materialization expression. cir::AllocaOp extDeclAlloca; - if (const clang::ValueDecl *extDecl = m->getExtendingDecl()) { + if (const ValueDecl *extDecl = m->getExtendingDecl()) { auto extDeclAddrIter = cgf.localDeclMap.find(extDecl); - if (extDeclAddrIter != cgf.localDeclMap.end()) { - extDeclAlloca = mlir::dyn_cast_if_present<cir::AllocaOp>( - extDeclAddrIter->second.getDefiningOp()); - } + if (extDeclAddrIter != cgf.localDeclMap.end()) + extDeclAlloca = extDeclAddrIter->second.getDefiningOp<cir::AllocaOp>(); } mlir::OpBuilder::InsertPoint ip; if (extDeclAlloca) @@ -1151,12 +1149,12 @@ static void pushTemporaryCleanup(CIRGenFunction &cgf, return; } - clang::CXXDestructorDecl *referenceTemporaryDtor = nullptr; + CXXDestructorDecl *referenceTemporaryDtor = nullptr; if (const clang::RecordType *rt = e->getType() ->getBaseElementTypeUnsafe() ->getAs<clang::RecordType>()) { // Get the destructor for the reference temporary. - auto *classDecl = cast<clang::CXXRecordDecl>(rt->getDecl()); + auto *classDecl = cast<CXXRecordDecl>(rt->getDecl()); if (!classDecl->hasTrivialDestructor()) referenceTemporaryDtor = classDecl->getDestructor(); } @@ -1221,8 +1219,7 @@ LValue CIRGenFunction::emitMaterializeTemporaryExpr( // Create and initialize the reference temporary. Address object = createReferenceTemporary(*this, m, e); - if (auto var = - mlir::dyn_cast<cir::GlobalOp>(object.getPointer().getDefiningOp())) { + if (auto var = object.getPointer().getDefiningOp<cir::GlobalOp>()) { // TODO(cir): add something akin to stripPointerCasts() to ptr above cgm.errorNYI(e->getSourceRange(), "emitMaterializeTemporaryExpr: GlobalOp"); return {}; _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits