Skip to content

Conversation

@andykaylor
Copy link
Contributor

This adds code to handle expressions with cleanup, including materializing a temporary object for the expression.

This adds code to handle expressions with cleanup, including materializing
a temporary object for the expression.
@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Jul 31, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 31, 2025

@llvm/pr-subscribers-clang

Author: Andy Kaylor (andykaylor)

Changes

This adds code to handle expressions with cleanup, including materializing a temporary object for the expression.


Full diff: https://github.com/llvm/llvm-project/pull/151600.diff

8 Files Affected:

  • (modified) clang/include/clang/CIR/MissingFeatures.h (+1)
  • (modified) clang/lib/CIR/CodeGen/Address.h (+7)
  • (modified) clang/lib/CIR/CodeGen/CIRGenDecl.cpp (+21-16)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+148)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+24)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.cpp (+6)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+21)
  • (modified) clang/test/CIR/CodeGen/cleanup.cpp (+14)
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

@llvmbot
Copy link
Member

llvmbot commented Jul 31, 2025

@llvm/pr-subscribers-clangir

Author: Andy Kaylor (andykaylor)

Changes

This adds code to handle expressions with cleanup, including materializing a temporary object for the expression.


Full diff: https://github.com/llvm/llvm-project/pull/151600.diff

8 Files Affected:

  • (modified) clang/include/clang/CIR/MissingFeatures.h (+1)
  • (modified) clang/lib/CIR/CodeGen/Address.h (+7)
  • (modified) clang/lib/CIR/CodeGen/CIRGenDecl.cpp (+21-16)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+148)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+24)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.cpp (+6)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+21)
  • (modified) clang/test/CIR/CodeGen/cleanup.cpp (+14)
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

Copy link
Contributor

@xlauko xlauko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm with minor nits

Comment on lines 1116 to 1117
extDeclAlloca = mlir::dyn_cast_if_present<cir::AllocaOp>(
extDeclAddrIter->second.getDefiningOp());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
extDeclAlloca = mlir::dyn_cast_if_present<cir::AllocaOp>(
extDeclAddrIter->second.getDefiningOp());
extDeclAlloca = extDeclAddrIter->second.getDefiningOp<cir::AllocaOp>();

Comment on lines 1155 to 1159
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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 (const RecordType *rt = e->getType()
->getBaseElementTypeUnsafe()
->getAs<clang::RecordType>()) {
// Get the destructor for the reference temporary.
auto *classDecl = cast<CXXRecordDecl>(rt->getDecl());

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, these are necessary because we have a cir::RecordType.

Comment on lines 1224 to 1225
if (auto var =
mlir::dyn_cast<cir::GlobalOp>(object.getPointer().getDefiningOp())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after #151217 is merged:

Suggested change
if (auto var =
mlir::dyn_cast<cir::GlobalOp>(object.getPointer().getDefiningOp())) {
if (auto var = object.getDefiningOp<cir::GlobalOp>()) {

@andykaylor andykaylor merged commit a304e09 into llvm:main Aug 1, 2025
9 checks passed
@andykaylor andykaylor deleted the cir-expr-cleanups branch August 1, 2025 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants