Skip to content

[CIR] Implement support for delete after new in a conditional branch#192544

Merged
andykaylor merged 5 commits into
llvm:mainfrom
andykaylor:cir-new-delete-conditional
Apr 20, 2026
Merged

[CIR] Implement support for delete after new in a conditional branch#192544
andykaylor merged 5 commits into
llvm:mainfrom
andykaylor:cir-new-delete-conditional

Conversation

@andykaylor
Copy link
Copy Markdown
Contributor

This implements handling for calling delete in an EH handler after a call to new when the new call appears inside a conditional operation, which requires the new result to be spilled inside the cleanup scope and reloaded after.

This implementation introduces the DominatingValue helper class, which is adapted from classic codegen, but only the parts of that class that are needed for the current change are implemented. This will likely be expanded in a future change as other uses are added.

Assisted-by: Cursor / claude-4.6-opus-high

This implements handling for calling delete in an EH handler after a
call to new when the new call appears inside a conditional operation,
which requires the new result to be spilled inside the cleanup scope
and reloaded after.

This implementation introduces the DominatingValue helper class, which
is adapted from classic codegen, but only the parts of that class that
are needed for the current change are implemented. This will likely
be expanded in a future change as other uses are added.

Assisted-by: Cursor / claude-4.6-opus-high
@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Apr 16, 2026
@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Apr 16, 2026

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clangir

Author: Andy Kaylor (andykaylor)

Changes

This implements handling for calling delete in an EH handler after a call to new when the new call appears inside a conditional operation, which requires the new result to be spilled inside the cleanup scope and reloaded after.

This implementation introduces the DominatingValue helper class, which is adapted from classic codegen, but only the parts of that class that are needed for the current change are implemented. This will likely be expanded in a future change as other uses are added.

Assisted-by: Cursor / claude-4.6-opus-high


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

5 Files Affected:

  • (modified) clang/lib/CIR/CodeGen/CIRGenCleanup.cpp (+83)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp (+41-15)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+38)
  • (modified) clang/lib/CIR/CodeGen/EHScopeStack.h (+12)
  • (modified) clang/test/CIR/CodeGen/new-delete.cpp (+165)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
index 55c27107e709d..816a39eb09060 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
@@ -86,6 +86,89 @@ Address CIRGenFunction::createCleanupActiveFlag() {
   return active;
 }
 
+void CIRGenFunction::initFullExprCleanup() {
+  initFullExprCleanupWithFlag(createCleanupActiveFlag());
+}
+
+void CIRGenFunction::initFullExprCleanupWithFlag(Address activeFlag) {
+  EHCleanupScope &cleanup = cast<EHCleanupScope>(*ehStack.begin());
+  assert(!cleanup.hasActiveFlag() && "cleanup already has active flag?");
+  cleanup.setActiveFlag(activeFlag);
+
+  if (cleanup.isNormalCleanup())
+    cleanup.setTestFlagInNormalCleanup();
+  if (cleanup.isEHCleanup())
+    cleanup.setTestFlagInEHCleanup();
+}
+
+//===----------------------------------------------------------------------===//
+// DominatingCIRValue / DominatingValue<RValue>
+//===----------------------------------------------------------------------===//
+
+bool DominatingCIRValue::needsSaving(mlir::Value value) {
+  if (!value)
+    return false;
+
+  // If it's a block argument, we don't need to save.
+  mlir::Operation *definingOp = value.getDefiningOp();
+  if (!definingOp)
+    return false;
+
+  // If the value is defined in the function entry block, it already dominates
+  // everything, so we don't need to save it.
+  mlir::Block *currBlock = definingOp->getBlock();
+  if (auto fnOp = definingOp->getParentOfType<cir::FuncOp>())
+    if (&fnOp.getBody().front() == currBlock)
+      return false;
+
+  return true;
+}
+
+DominatingCIRValue::saved_type DominatingCIRValue::save(CIRGenFunction &cgf,
+                                                        mlir::Value value) {
+  if (!needsSaving(value))
+    return saved_type(value, false);
+
+  // Otherwise, we need an alloca.
+  CharUnits align = CharUnits::fromQuantity(
+      cgf.cgm.getDataLayout().getABITypeAlign(value.getType()));
+  mlir::Location loc = value.getLoc();
+  Address alloca =
+      cgf.createTempAlloca(value.getType(), align, loc, "cond-cleanup.save");
+  cgf.getBuilder().createStore(loc, value, alloca);
+
+  return saved_type(alloca.emitRawPointer(), true);
+}
+
+mlir::Value DominatingCIRValue::restore(CIRGenFunction &cgf,
+                                        saved_type value) {
+  // If the value says it wasn't saved, trust that it's still dominating.
+  if (!value.getInt())
+    return value.getPointer();
+
+  // Otherwise, it should be an alloca instruction, as set up in save().
+  auto alloca = value.getPointer().getDefiningOp<cir::AllocaOp>();
+  return cgf.getBuilder().createAlignedLoad(
+      alloca.getLoc(), alloca.getAllocaType(), alloca,
+      llvm::MaybeAlign(alloca.getAlignment()));
+}
+
+DominatingValue<RValue>::saved_type
+DominatingValue<RValue>::saved_type::save(CIRGenFunction &cgf, RValue rv) {
+  if (rv.isScalar())
+    return saved_type(DominatingCIRValue::save(cgf, rv.getValue()));
+
+  if (rv.isAggregate())
+    cgf.cgm.errorNYI("DominatingValue<RValue>::save for aggregate type");
+  else
+    cgf.cgm.errorNYI("DominatingValue<RValue>::save for complex type");
+  return saved_type(DominatingCIRValue::saved_type());
+}
+
+RValue DominatingValue<RValue>::saved_type::restore(CIRGenFunction &cgf) {
+  return RValue::get(DominatingCIRValue::restore(cgf, val));
+}
+
 CIRGenFunction::FullExprCleanupScope::FullExprCleanupScope(CIRGenFunction &cgf,
                                                            const Expr *subExpr)
     : cgf(cgf), cleanups(cgf), scope(nullptr),
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 30a833564ec2f..e77c4294e1ba3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -798,12 +798,12 @@ class CallDeleteDuringNew final
 
   PlacementArg<Traits> *getPlacementArgs() { return getTrailingObjects(); }
 
+public:
   void setPlacementArg(unsigned i, RValueTy argValue, QualType argType) {
     assert(i < numPlacementArgs && "index out of range");
     getPlacementArgs()[i] = {argValue, argType};
   }
 
-public:
   static size_t getExtraSize(size_t numPlacementArgs) {
     return TrailingObj::template additionalSizeToAlloc<PlacementArg<Traits>>(
         numPlacementArgs);
@@ -813,18 +813,11 @@ class CallDeleteDuringNew final
                       const FunctionDecl *operatorDelete, ValueTy ptr,
                       ValueTy allocSize,
                       const ImplicitAllocationParameters &iap,
-                      CharUnits allocAlign, const CallArgList *newArgs,
-                      unsigned numNonPlacementArgs, CIRGenFunction *cgf,
-                      mlir::Location loc)
+                      CharUnits allocAlign)
       : numPlacementArgs(numPlacementArgs),
         passAlignmentToPlacementDelete(isAlignedAllocation(iap.PassAlignment)),
         operatorDelete(operatorDelete), ptr(ptr), allocSize(allocSize),
-        allocAlign(allocAlign) {
-    for (unsigned i = 0, n = numPlacementArgs; i != n; ++i) {
-      const CallArg &arg = (*newArgs)[i + numNonPlacementArgs];
-      setPlacementArg(i, arg.getRValue(*cgf, loc), arg.ty);
-    }
-  }
+        allocAlign(allocAlign) {}
 
   void emit(CIRGenFunction &cgf, Flags flags) override {
     const auto *fpt = operatorDelete->getType()->castAs<FunctionProtoType>();
@@ -901,17 +894,50 @@ static void enterNewDeleteCleanup(CIRGenFunction &cgf, const CXXNewExpr *e,
     typedef CallDeleteDuringNew<DirectCleanupTraits> DirectCleanup;
 
     assert(!cir::MissingFeatures::typeAwareAllocation());
-    cgf.ehStack.pushCleanupWithExtra<DirectCleanup>(
+    DirectCleanup *cleanup = cgf.ehStack.pushCleanupWithExtra<DirectCleanup>(
         EHCleanup, e->getNumPlacementArgs(), e->getOperatorDelete(),
         newPtr.getPointer(), allocSize, e->implicitAllocationParameters(),
-        allocAlign, &newArgs, numNonPlacementArgs, &cgf,
-        cgf.getLoc(e->getSourceRange()));
+        allocAlign);
+    for (unsigned i = 0, n = e->getNumPlacementArgs(); i != n; ++i) {
+      const CallArg &arg = newArgs[i + numNonPlacementArgs];
+      cleanup->setPlacementArg(i, arg.getRValue(cgf, cgf.getLoc(e->getSourceRange())),
+                               arg.ty);
+    }
 
     return;
   }
 
-  cgf.cgm.errorNYI(e->getSourceRange(),
-                   "enterNewDeleteCleanup: conditional branch");
+  // Otherwise, we need to save all this stuff.
+  DominatingValue<RValue>::saved_type savedNewPtr =
+      DominatingValue<RValue>::save(cgf, RValue::get(newPtr.getPointer()));
+  DominatingValue<RValue>::saved_type savedAllocSize =
+      DominatingValue<RValue>::save(cgf, RValue::get(allocSize));
+
+  struct ConditionalCleanupTraits {
+    typedef DominatingValue<RValue>::saved_type ValueTy;
+    typedef DominatingValue<RValue>::saved_type RValueTy;
+    static RValue get(CIRGenFunction &cgf, ValueTy v) {
+      return v.restore(cgf);
+    }
+  };
+  typedef CallDeleteDuringNew<ConditionalCleanupTraits> ConditionalCleanup;
+
+  assert(!cir::MissingFeatures::typeAwareAllocation());
+  ConditionalCleanup *cleanup =
+      cgf.ehStack.pushCleanupWithExtra<ConditionalCleanup>(
+          EHCleanup, e->getNumPlacementArgs(), e->getOperatorDelete(),
+          savedNewPtr, savedAllocSize, e->implicitAllocationParameters(),
+          allocAlign);
+  for (unsigned i = 0, n = e->getNumPlacementArgs(); i != n; ++i) {
+    const CallArg &arg = newArgs[i + numNonPlacementArgs];
+    cleanup->setPlacementArg(
+        i,
+        DominatingValue<RValue>::save(
+            cgf, arg.getRValue(cgf, cgf.getLoc(e->getSourceRange()))),
+        arg.ty);
+  }
+
+  cgf.initFullExprCleanup();
 }
 
 static void storeAnyExprIntoOneUnit(CIRGenFunction &cgf, const Expr *init,
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 7f91cba115f04..1905800554838 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1094,6 +1094,11 @@ class CIRGenFunction : public CIRGenTypeCache {
   /// true at the current insertion point (inside the conditional branch).
   Address createCleanupActiveFlag();
 
+  /// Set up the last cleanup that was pushed as a conditional
+  /// full-expression cleanup.
+  void initFullExprCleanup();
+  void initFullExprCleanupWithFlag(Address activeFlag);
+
   /// Promote a single pending cleanup entry onto the EH scope stack. If the
   /// entry has a valid activeFlag, the cleanup is configured as conditional.
   /// Defined in CIRGenDecl.cpp where the concrete cleanup types are visible.
@@ -2600,6 +2605,39 @@ class CIRGenFunction : public CIRGenTypeCache {
   };
 };
 
+/// Helper class with most of the code for saving a value for a
+/// conditional expression cleanup.
+struct DominatingCIRValue {
+  typedef llvm::PointerIntPair<mlir::Value, 1, bool> saved_type;
+
+  /// Answer whether the given value needs extra work to be saved.
+  static bool needsSaving(mlir::Value value);
+
+  static saved_type save(CIRGenFunction &cgf, mlir::Value value);
+  static mlir::Value restore(CIRGenFunction &cgf, saved_type value);
+};
+
+/// A specialization of DominatingValue for RValue.
+template <> struct DominatingValue<RValue> {
+  typedef RValue type;
+  class saved_type {
+    DominatingCIRValue::saved_type val;
+
+    saved_type(DominatingCIRValue::saved_type val) : val(val) {}
+
+  public:
+    static saved_type save(CIRGenFunction &cgf, RValue value);
+    RValue restore(CIRGenFunction &cgf);
+  };
+
+  static saved_type save(CIRGenFunction &cgf, type value) {
+    return saved_type::save(cgf, value);
+  }
+  static type restore(CIRGenFunction &cgf, saved_type value) {
+    return value.restore(cgf);
+  }
+};
+
 } // namespace clang::CIRGen
 
 #endif
diff --git a/clang/lib/CIR/CodeGen/EHScopeStack.h b/clang/lib/CIR/CodeGen/EHScopeStack.h
index 308d98f108101..9e7a917d1d6cf 100644
--- a/clang/lib/CIR/CodeGen/EHScopeStack.h
+++ b/clang/lib/CIR/CodeGen/EHScopeStack.h
@@ -25,6 +25,18 @@ namespace clang::CIRGen {
 
 class CIRGenFunction;
 
+template <class T> struct InvariantValue {
+  using type = T;
+  using saved_type = T;
+  static bool needsSaving(type value) { return false; }
+  static saved_type save(CIRGenFunction &cgf, type value) { return value; }
+  static type restore(CIRGenFunction &cgf, saved_type value) { return value; }
+};
+
+/// A metaprogramming class for ensuring that a value will dominate an
+/// arbitrary position in a function.
+template <class T> struct DominatingValue : InvariantValue<T> {};
+
 enum CleanupKind : unsigned {
   /// Denotes a cleanup that should run when a scope is exited using exceptional
   /// control flow (a throw statement leading to stack unwinding, ).
diff --git a/clang/test/CIR/CodeGen/new-delete.cpp b/clang/test/CIR/CodeGen/new-delete.cpp
index d0c8c7d851c70..71e3407001ddb 100644
--- a/clang/test/CIR/CodeGen/new-delete.cpp
+++ b/clang/test/CIR/CodeGen/new-delete.cpp
@@ -247,6 +247,171 @@ B *c() {
 // OGCG:   %[[EXN_INSERT_2:.*]] = insertvalue { ptr, i32 } %[[EXN_INSERT]], i32 %[[EHSELECTOR]], 1
 // OGCG:   resume { ptr, i32 } %[[EXN_INSERT_2]]
 
+struct C {
+  C();
+  ~C();
+};
+
+C *test_new_delete_conditional(bool cond) {
+  return cond ? new C : 0;
+}
+
+// CIR-LABEL: @_Z27test_new_delete_conditionalb
+// CIR:   %[[CLEANUP_COND:.*]] = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["cleanup.cond"]
+// CIR:   %[[FALSE:.*]] = cir.const #false
+// CIR:   cir.store %[[FALSE]], %[[CLEANUP_COND]]
+// CIR:   %[[TERN_RESULT:.*]] = cir.ternary
+// CIR:     %[[PTR_SAVE:.*]] = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["cond-cleanup.save"]
+// CIR:     %[[SIZE_SAVE:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["cond-cleanup.save"]
+// CIR:     %[[NEW_RESULT:.*]] = cir.alloca !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>, ["__new_result"]
+// CIR:     %[[ALLOC_SIZE:.*]] = cir.const #cir.int<1> : !u64i
+// CIR:     %[[NEW_PTR:.*]] = cir.call @_Znwm(%[[ALLOC_SIZE]])
+// CIR:     cir.store {{.*}}%[[NEW_PTR]], %[[PTR_SAVE]]
+// CIR:     cir.store {{.*}}%[[ALLOC_SIZE]], %[[SIZE_SAVE]]
+// CIR:     cir.cleanup.scope {
+// CIR:       %[[TRUE:.*]] = cir.const #true
+// CIR:       cir.store %[[TRUE]], %[[CLEANUP_COND]]
+// CIR:       %[[NEW_AS_C:.*]] = cir.cast bitcast %[[NEW_PTR]] : !cir.ptr<!void> -> !cir.ptr<!rec_C>
+// CIR:       cir.store{{.*}} %[[NEW_AS_C]], %[[NEW_RESULT]]
+// CIR:       cir.call @_ZN1CC1Ev(%[[NEW_AS_C]])
+// CIR:       cir.yield
+// CIR:     } cleanup eh {
+// CIR:       %[[FLAG:.*]] = cir.load {{.*}} %[[CLEANUP_COND]]
+// CIR:       cir.if %[[FLAG]] {
+// CIR:         %[[RESTORED_PTR:.*]] = cir.load {{.*}} %[[PTR_SAVE]]
+// CIR:         cir.call @_ZdlPv(%[[RESTORED_PTR]])
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     }
+// CIR:     %[[TRUE_RESULT:.*]] = cir.load{{.*}} %[[NEW_RESULT]]
+// CIR:     cir.yield %[[TRUE_RESULT]] : !cir.ptr<!rec_C>
+// CIR:   }, false {
+// CIR:     %[[FALSE_RESULT:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!rec_C>
+// CIR:     cir.yield %[[FALSE_RESULT]] : !cir.ptr<!rec_C>
+// CIR:   }) : (!cir.bool) -> !cir.ptr<!rec_C>
+
+// LLVM-LABEL: @_Z27test_new_delete_conditionalb
+// LLVM:   store i8 0, ptr %[[CLEANUP_FLAG:.*]], align 1
+// LLVM:   br i1 {{.*}}, label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]]
+// LLVM: [[TRUE_BB]]:
+// LLVM:   %[[NEWP:.*]] = call ptr @_Znwm(i64 1) #[[ATTR_BUILTIN_NEW]]
+// LLVM:   store ptr %[[NEWP]], ptr %[[SAVE_PTR:.*]], align 8
+// LLVM:   store i8 1, ptr %[[CLEANUP_FLAG]], align 1
+// LLVM:   invoke void @_ZN1CC1Ev(ptr {{.*}}%[[NEWP]])
+// LLVM:     to label %{{.*}} unwind label %[[LPAD:.*]]
+// LLVM: [[LPAD]]:
+// LLVM:   landingpad { ptr, i32 }
+// LLVM-NEXT:   cleanup
+// LLVM:   %[[IS_ACTIVE:.*]] = trunc i8 %{{.*}} to i1
+// LLVM:   br i1 %[[IS_ACTIVE]], label %[[DO_DEL:.*]], label %[[SKIP_DEL:.*]]
+// LLVM: [[DO_DEL]]:
+// LLVM:   %[[LOAD_PTR:.*]] = load ptr, ptr %[[SAVE_PTR]], align 8
+// LLVM:   call void @_ZdlPv(ptr %[[LOAD_PTR]]) #[[ATTR_BUILTIN_DEL]]
+// LLVM:   resume
+
+// OGCG-LABEL: @_Z27test_new_delete_conditionalb
+// OGCG:   store i1 false, ptr %[[OG_FLAG:.*]], align 1
+// OGCG:   br i1 {{.*}}, label %[[OG_TRUE:.*]], label %[[OG_FALSE:.*]]
+// OGCG: [[OG_TRUE]]:
+// OGCG:   %[[OG_NEWP:.*]] = call noalias nonnull ptr @_Znwm(i64 1) #[[OGCG_ATTR_BUILTIN_NEW]]
+// OGCG:   store ptr %[[OG_NEWP]], ptr %[[OG_SAVE:.*]], align 8
+// OGCG:   store i1 true, ptr %[[OG_FLAG]], align 1
+// OGCG:   invoke void @_ZN1CC1Ev(ptr {{.*}}%[[OG_NEWP]])
+// OGCG:     to label %{{.*}} unwind label %[[OG_LPAD:.*]]
+// OGCG: [[OG_LPAD]]:
+// OGCG:   landingpad { ptr, i32 }
+// OGCG-NEXT:   cleanup
+// OGCG:   %[[OG_ACTIVE:.*]] = load i1, ptr %[[OG_FLAG]], align 1
+// OGCG:   br i1 %[[OG_ACTIVE]], label %[[OG_DO_DEL:.*]], label %[[OG_SKIP_DEL:.*]]
+// OGCG: [[OG_DO_DEL]]:
+// OGCG:   %[[OG_LOAD:.*]] = load ptr, ptr %[[OG_SAVE]], align 8
+// OGCG:   call void @_ZdlPv(ptr %[[OG_LOAD]]) #[[OGCG_ATTR_BUILTIN_DEL]]
+// OGCG:   resume
+
+void *operator new(unsigned long, int);
+void operator delete(void *, int);
+
+C *test_new_delete_conditional_with_placement(bool cond, int tag) {
+  return cond ? new (tag) C : 0;
+}
+
+// CIR-LABEL: @_Z42test_new_delete_conditional_with_placementbi
+// CIR:   %[[CLEANUP_COND:.*]] = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["cleanup.cond"]
+// CIR:   %[[TERN_RESULT:.*]] = cir.ternary
+// CIR:     %[[PTR_SAVE:.*]] = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["cond-cleanup.save"]
+// CIR:     %[[SIZE_SAVE:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["cond-cleanup.save"]
+// CIR:     %[[TAG_SAVE:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["cond-cleanup.save"]
+// CIR:     %[[NEW_RESULT:.*]] = cir.alloca !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>, ["__new_result"]
+// CIR:     %[[ONE:.*]] = cir.const #cir.int<1> : !u64i
+// CIR:     %[[TAG_VAL:.*]] = cir.load{{.*}}
+// CIR:     %[[NEW_PTR:.*]] = cir.call @_Znwmi(%[[ONE]], %[[TAG_VAL]])
+// CIR:     cir.store{{.*}} %[[NEW_PTR]], %[[PTR_SAVE]]
+// CIR:     cir.store{{.*}} %[[ONE]], %[[SIZE_SAVE]]
+// CIR:     cir.cleanup.scope {
+// CIR:       cir.store{{.*}} %[[TAG_VAL]], %[[TAG_SAVE]]
+// CIR:       %[[TRUE:.*]] = cir.const #true
+// CIR:       cir.store %[[TRUE]], %[[CLEANUP_COND]]
+// CIR:       %[[NEW_AS_C:.*]] = cir.cast bitcast %[[NEW_PTR]] : !cir.ptr<!void> -> !cir.ptr<!rec_C>
+// CIR:       cir.store{{.*}} %[[NEW_AS_C]], %[[NEW_RESULT]]
+// CIR:       cir.call @_ZN1CC1Ev(%[[NEW_AS_C]])
+// CIR:     } cleanup eh {
+// CIR:       %[[FLAG:.*]] = cir.load{{.*}} %[[CLEANUP_COND]]
+// CIR:       cir.if %[[FLAG]] {
+// CIR:         %[[RESTORED_PTR:.*]] = cir.load {{.*}} %[[PTR_SAVE]]
+// CIR:         %[[RESTORED_TAG:.*]] = cir.load {{.*}} %[[TAG_SAVE]]
+// CIR:         cir.call @_ZdlPvi(%[[RESTORED_PTR]], %[[RESTORED_TAG]])
+// CIR:       }
+// CIR:       cir.yield
+// CIR:     }
+// CIR:     %[[TRUE_RESULT:.*]] = cir.load{{.*}} %[[NEW_RESULT]]
+// CIR:     cir.yield %[[TRUE_RESULT]] : !cir.ptr<!rec_C>
+// CIR:   }, false {
+// CIR:     %[[FALSE_RESULT:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!rec_C>
+// CIR:     cir.yield %[[FALSE_RESULT]] : !cir.ptr<!rec_C>
+// CIR:   }) : (!cir.bool) -> !cir.ptr<!rec_C>
+
+// LLVM-LABEL: @_Z42test_new_delete_conditional_with_placementbi
+// LLVM:   store i8 0, ptr %[[PL_FLAG:.*]], align 1
+// LLVM:   br i1 {{.*}}, label %[[PL_TRUE:.*]], label %[[PL_FALSE:.*]]
+// LLVM: [[PL_TRUE]]:
+// LLVM:   %[[PL_TAG:.*]] = load i32, ptr %{{.*}}, align 4
+// LLVM:   %[[PL_NEWP:.*]] = call ptr @_Znwmi(i64 1, i32 %[[PL_TAG]])
+// LLVM:   store ptr %[[PL_NEWP]], ptr %[[PL_SAVE_PTR:.*]], align 8
+// LLVM:   store i32 %[[PL_TAG]], ptr %[[PL_SAVE_TAG:.*]], align 4
+// LLVM:   store i8 1, ptr %[[PL_FLAG]], align 1
+// LLVM:   invoke void @_ZN1CC1Ev(ptr {{.*}}%[[PL_NEWP]])
+// LLVM:     to label %{{.*}} unwind label %[[PL_LPAD:.*]]
+// LLVM: [[PL_LPAD]]:
+// LLVM:   landingpad { ptr, i32 }
+// LLVM-NEXT:   cleanup
+// LLVM:   %[[PL_ACTIVE:.*]] = trunc i8 %{{.*}} to i1
+// LLVM:   br i1 %[[PL_ACTIVE]], label %[[PL_DO_DEL:.*]], label %[[PL_SKIP_DEL:.*]]
+// LLVM: [[PL_DO_DEL]]:
+// LLVM:   %[[PL_LOAD_PTR:.*]] = load ptr, ptr %[[PL_SAVE_PTR]], align 8
+// LLVM:   %[[PL_LOAD_TAG:.*]] = load i32, ptr %[[PL_SAVE_TAG]], align 4
+// LLVM:   invoke void @_ZdlPvi(ptr %[[PL_LOAD_PTR]], i32 %[[PL_LOAD_TAG]])
+
+// OGCG-LABEL: @_Z42test_new_delete_conditional_with_placementbi
+// OGCG:   store i1 false, ptr %[[OGP_FLAG:.*]], align 1
+// OGCG:   br i1 {{.*}}, label %[[OGP_TRUE:.*]], label %[[OGP_FALSE:.*]]
+// OGCG: [[OGP_TRUE]]:
+// OGCG:   %[[OGP_TAG:.*]] = load i32, ptr %{{.*}}, align 4
+// OGCG:   %[[OGP_NEWP:.*]] = call ptr @_Znwmi(i64 1, i32 %[[OGP_TAG]])
+// OGCG:   store ptr %[[OGP_NEWP]], ptr %[[OGP_SAVE_PTR:.*]], align 8
+// OGCG:   store i32 %[[OGP_TAG]], ptr %[[OGP_SAVE_TAG:.*]], align 4
+// OGCG:   store i1 true, ptr %[[OGP_FLAG]], align 1
+// OGCG:   invoke void @_ZN1CC1Ev(ptr {{.*}}%[[OGP_NEWP]])
+// OGCG:     to label %{{.*}} unwind label %[[OGP_LPAD:.*]]
+// OGCG: [[OGP_LPAD]]:
+// OGCG:   landingpad { ptr, i32 }
+// OGCG-NEXT:   cleanup
+// OGCG:   %[[OGP_ACTIVE:.*]] = load i1, ptr %[[OGP_FLAG]], align 1
+// OGCG:   br i1 %[[OGP_ACTIVE]], label %[[OGP_DO_DEL:.*]], label %[[OGP_SKIP_DEL:.*]]
+// OGCG: [[OGP_DO_DEL]]:
+// OGCG:   %[[OGP_LOAD_PTR:.*]] = load ptr, ptr %[[OGP_SAVE_PTR]], align 8
+// OGCG:   %[[OGP_LOAD_TAG:.*]] = load i32, ptr %[[OGP_SAVE_TAG]], align 4
+// OGCG:   invoke void @_ZdlPvi(ptr %[[OGP_LOAD_PTR]], i32 %[[OGP_LOAD_TAG]])
+
 // LLVM-DAG: attributes #[[ATTR_BUILTIN_NEW]] = {{{.*}}builtin{{.*}}}
 // LLVM-DAG: attributes #[[ATTR_BUILTIN_DEL]] = {{{.*}}builtin{{.*}}}
 // OGCG-DAG: attributes #[[OGCG_ATTR_BUILTIN_NEW]] = {{{.*}}builtin{{.*}}}

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 16, 2026

✅ With the latest revision this PR passed the C/C++ code formatter.

Comment thread clang/lib/CIR/CodeGen/EHScopeStack.h Outdated

template <class T> struct InvariantValue {
using type = T;
using saved_type = T;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

silly question: why two aliases here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This class, as well as DominatingValue and DominatingCIRValue are basically copy and paste from classic codegen, and I have to admit that I only have a rough understanding of how they work. As I understand it this is a trivial fallback implementation that intentionally does nothing. It basically is establishing a pattern for what the derived classes do. As far as I can tell there is no real reason for type and saved_type to be different here other than that they will be different in some derived classes and having them different here (arguably) promotes readability.

I don't think this PR actually reaches any of these fallback implementations, but they will be needed later.

The basic idea is that we have some value at one point in the CIR and we need to reference it at some later point in the IR. The InvariantValue class is saying that it doesn't change and so nothing needs to be done to make it available at the later point. But for DominatingValue we may need to save it to a temporary location and reload it, which is what save and restore will do there.

The classic codegen implementation of pushFullExprCleanup uses the DominatingValue class to "save" all of the packed arguments, so it ends up getting used for things like QualType that fall back on the InvariantValue implementation. I'm not sure we're going to need that for CIR, so maybe I should strip in down a bit more for now?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would suggest simplifying, we can add the complexity later if we need it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Aggree with @erichkeane, also snake_case triggers me here

Comment thread clang/lib/CIR/CodeGen/EHScopeStack.h Outdated
using saved_type = T;
static bool needsSaving(type value) { return false; }
static saved_type save(CIRGenFunction &cgf, type value) { return value; }
static type restore(CIRGenFunction &cgf, saved_type value) { return value; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What do save and restore do differently?

Copy link
Copy Markdown
Contributor

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

Would love hte simplification of save/restore if it works well enough, either way LGTM.

Comment thread clang/lib/CIR/CodeGen/CIRGenCleanup.cpp Outdated
// If it's a block argument, we don't need to save.
mlir::Operation *definingOp = value.getDefiningOp();
if (!definingOp)
return false;
Copy link
Copy Markdown
Contributor

@xlauko xlauko Apr 17, 2026

Choose a reason for hiding this comment

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

This is not correct no? a block arg of a non-entry block does not dominate an arbitrary later insertion point.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're right, of course. This is what the incubator did, and I hadn't questioned it.

Comment thread clang/lib/CIR/CodeGen/CIRGenCleanup.cpp Outdated
Comment on lines +117 to +122
// If the value is defined in the function entry block, it already dominates
// everything, so we don't need to save it.
mlir::Block *currBlock = definingOp->getBlock();
if (auto fnOp = definingOp->getParentOfType<cir::FuncOp>())
if (&fnOp.getBody().front() == currBlock)
return false;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This on the other hand is too conservative and does not eliminate values like:

cir.func {
  cir.scope {
   %value // currBlock != fnOp.getBody().front()
  }
  // nothing more here
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I stripped out some things from the incubator implementation here because I didn't think I could hit them with the one case I'm implementing in this PR. I'm starting to think maybe this whole DominatingValue mechanism is more complex and potentially error prone than we need right now.

Comment thread clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp Outdated
allocAlign, &newArgs, numNonPlacementArgs, &cgf,
cgf.getLoc(e->getSourceRange()));
allocAlign);
for (unsigned i = 0, n = e->getNumPlacementArgs(); i != n; ++i) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

llvm::seq does not apply here?

Comment thread clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp Outdated
EHCleanup, e->getNumPlacementArgs(), e->getOperatorDelete(),
savedNewPtr, savedAllocSize, e->implicitAllocationParameters(),
allocAlign);
for (unsigned i = 0, n = e->getNumPlacementArgs(); i != n; ++i) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

llvm::seq

Comment thread clang/lib/CIR/CodeGen/CIRGenFunction.h Outdated

public:
static saved_type save(CIRGenFunction &cgf, RValue value);
RValue restore(CIRGenFunction &cgf);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shouldn't this be static?

Comment thread clang/lib/CIR/CodeGen/EHScopeStack.h Outdated

template <class T> struct InvariantValue {
using type = T;
using saved_type = T;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Aggree with @erichkeane, also snake_case triggers me here

cgf.cgm.errorNYI(e->getSourceRange(),
"enterNewDeleteCleanup: conditional branch");
// Otherwise, we need to save all this stuff.
auto saveValue = [&](mlir::Value value) -> mlir::Value {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a much simpler implementation. It doesn't attempt to look for cases where the values don't need to be saved, but the optimizer should handle that. In particular, you'll see in the CIR checks for the test that we spill the size even when it's a constant, but that gets resolved during lowering to LLVM IR.

@andykaylor andykaylor requested review from erichkeane and xlauko April 20, 2026 16:23
Comment thread clang/lib/CIR/CodeGen/CIRGenCleanup.cpp Outdated
cleanup.setActiveFlag(activeFlag);

if (cleanup.isNormalCleanup())
cleanup.setTestFlagInNormalCleanup();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It might be cleaner/nicer to have these two functions take a value, so that this becomes:

cleanup.setTestFlagInNormalCleanup(cleanump.isNormalCleanup()? Though it IS a little weird to me that the ctor of EHCleanupScope can't do this? Should it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The logic for setting these flags is not very closely coupled with the creation of the cleanup. It would take a lot of restructuring to set it in the ctor. On the other hand, making the setTestFlag... functions take an argument is a simple change. I'll do that.

@github-actions
Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 88883 tests passed
  • 827 tests skipped
  • 3 tests failed

Failed Tests

(click on a test name to see its output)

Clang

Clang.CodeGenHLSL/resources/resources-in-structs-array.hlsl
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang -cc1 -internal-isystem /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lib/clang/23/include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl | llvm-cxxfilt | /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang -cc1 -internal-isystem /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lib/clang/23/include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl
# note: command had no output on stdout or stderr
# executed command: llvm-cxxfilt
# note: command had no output on stdout or stderr
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl
# .---command stderr------------
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl:80:16: error: CHECK-NEXT: is not on the line after the previous match
# | // CHECK-NEXT: %[[TMP:.*]] = alloca %"class.hlsl::RWStructuredBuffer", align 4
# |                ^
# | <stdin>:244:2: note: 'next' match was here
# |  %tmp = alloca %"class.hlsl::RWStructuredBuffer", align 4
# |  ^
# | <stdin>:242:7: note: previous match ended here
# | entry:
# |       ^
# | <stdin>:243:1: note: non-matching line after previous match is here
# |  %0 = call token @llvm.experimental.convergence.entry()
# | ^
# | 
# | Input file: <stdin>
# | Check file: /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |          .
# |          .
# |          .
# |        239:  
# |        240: ; Function Attrs: alwaysinline convergent mustprogress norecurse nounwind 
# |        241: define internal void @main()() #2 { 
# |        242: entry: 
# |        243:  %0 = call token @llvm.experimental.convergence.entry() 
# |        244:  %tmp = alloca %"class.hlsl::RWStructuredBuffer", align 4 
# | next:80      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  error: match on wrong line
# |        245:  %call1 = call noundef nonnull align 4 dereferenceable(4) ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @arrayOfA.1.Buf, i32 noundef 0) #5 [ "convergencectrl"(token %0) ] 
# |        246:  store float 1.000000e+00, ptr %call1, align 4 
# |        247:  %call2 = call noundef nonnull align 4 dereferenceable(4) ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @gArray.1.multiArray.1.0.Buf, i32 noundef 0) #5 [ "convergencectrl"(token %0) ] 
# |        248:  store float 2.000000e+00, ptr %call2, align 4 
# |        249:  %call3 = call noundef nonnull align 4 dereferenceable(4) ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @gArray.0.multiArray.0.1.Buf, i32 noundef 0) #5 [ "convergencectrl"(token %0) ] 
# |          .
# |          .
# |          .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

Clang.CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang -cc1 -internal-isystem /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lib/clang/23/include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl | llvm-cxxfilt | /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang -cc1 -internal-isystem /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lib/clang/23/include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
# note: command had no output on stdout or stderr
# executed command: llvm-cxxfilt
# note: command had no output on stdout or stderr
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
# .---command stderr------------
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl:96:16: error: CHECK-NEXT: is not on the line after the previous match
# | // CHECK-NEXT: %i = alloca i32
# |                ^
# | <stdin>:273:2: note: 'next' match was here
# |  %i = alloca i32, align 4
# |  ^
# | <stdin>:271:7: note: previous match ended here
# | entry:
# |       ^
# | <stdin>:272:1: note: non-matching line after previous match is here
# |  %0 = call token @llvm.experimental.convergence.entry()
# | ^
# | 
# | Input file: <stdin>
# | Check file: /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |          .
# |          .
# |          .
# |        268:  
# |        269: ; Function Attrs: alwaysinline convergent mustprogress norecurse nounwind 
# |        270: define internal void @main()() #2 { 
# |        271: entry: 
# |        272:  %0 = call token @llvm.experimental.convergence.entry() 
# |        273:  %i = alloca i32, align 4 
# | next:96      !~~~~~~~~~~~~~~           error: match on wrong line
# |        274:  %tmp = alloca %"class.hlsl::StructuredBuffer.0", align 4 
# |        275:  %a = alloca float, align 4 
# |        276:  %call1 = call noundef nonnull align 4 dereferenceable(4) ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @"_ZL8c.A::Buf", i32 noundef 0) #5 [ "convergencectrl"(token %0) ] 
# |        277:  store float 0x3FF3AE1480000000, ptr %call1, align 4 
# |        278:  %call2 = call noundef nonnull align 4 dereferenceable(4) ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @c.Buf2, i32 noundef 0) #5 [ "convergencectrl"(token %0) ] 
# |          .
# |          .
# |          .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

Clang.CodeGenHLSL/resources/resources-in-structs.hlsl
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang -cc1 -internal-isystem /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lib/clang/23/include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl | llvm-cxxfilt | /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang -cc1 -internal-isystem /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lib/clang/23/include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl
# note: command had no output on stdout or stderr
# executed command: llvm-cxxfilt
# note: command had no output on stdout or stderr
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl
# .---command stderr------------
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl:44:16: error: CHECK-NEXT: is not on the line after the previous match
# | // CHECK-NEXT: call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)
# |                ^
# | <stdin>:26:2: note: 'next' match was here
# |  call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)(ptr dead_on_unwind writable sret(%"class.hlsl::RWBuffer") align 4 @a.Buf, i32 noundef 0, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @.str) #5 [ "convergencectrl"(token %0) ]
# |  ^
# | <stdin>:24:7: note: previous match ended here
# | entry:
# |       ^
# | <stdin>:25:1: note: non-matching line after previous match is here
# |  %0 = call token @llvm.experimental.convergence.entry()
# | ^
# | 
# | Input file: <stdin>
# | Check file: /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |          .
# |          .
# |          .
# |         21:  
# |         22: ; Function Attrs: alwaysinline convergent nounwind 
# |         23: define internal void @__cxx_global_var_init() #0 { 
# |         24: entry: 
# |         25:  %0 = call token @llvm.experimental.convergence.entry() 
# |         26:  call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)(ptr dead_on_unwind writable sret(%"class.hlsl::RWBuffer") align 4 @a.Buf, i32 noundef 0, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @.str) #5 [ "convergencectrl"(token %0) ] 
# | next:44      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                                                                                                                                                                                              error: match on wrong line
# |         27:  ret void 
# |         28: } 
# |         29:  
# |         30: ; Function Attrs: convergent nocallback nofree nosync nounwind willreturn memory(none) 
# |         31: declare token @llvm.experimental.convergence.entry() #1 
# |          .
# |          .
# |          .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

@github-actions
Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 53670 tests passed
  • 1774 tests skipped
  • 3 tests failed

Failed Tests

(click on a test name to see its output)

Clang

Clang.CodeGenHLSL/resources/resources-in-structs-array.hlsl
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
c:\_work\llvm-project\llvm-project\build\bin\clang.exe -cc1 -internal-isystem C:\_work\llvm-project\llvm-project\build\lib\clang\23\include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs-array.hlsl | llvm-cxxfilt | c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs-array.hlsl
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\clang.exe' -cc1 -internal-isystem 'C:\_work\llvm-project\llvm-project\build\lib\clang\23\include' -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - 'C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs-array.hlsl'
# note: command had no output on stdout or stderr
# executed command: llvm-cxxfilt
# note: command had no output on stdout or stderr
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe' 'C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs-array.hlsl'
# .---command stderr------------
# | C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs-array.hlsl:80:16: error: CHECK-NEXT: is not on the line after the previous match
# | // CHECK-NEXT: %[[TMP:.*]] = alloca %"class.hlsl::RWStructuredBuffer", align 4
# |                ^
# | <stdin>:244:2: note: 'next' match was here
# |  %tmp = alloca %"class.hlsl::RWStructuredBuffer", align 4
# |  ^
# | <stdin>:242:7: note: previous match ended here
# | entry:
# |       ^
# | <stdin>:243:1: note: non-matching line after previous match is here
# |  %0 = call token @llvm.experimental.convergence.entry()
# | ^
# | 
# | Input file: <stdin>
# | Check file: C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs-array.hlsl
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |          .
# |          .
# |          .
# |        239:  
# |        240: ; Function Attrs: alwaysinline convergent mustprogress norecurse nounwind 
# |        241: define internal void @main()() #2 { 
# |        242: entry: 
# |        243:  %0 = call token @llvm.experimental.convergence.entry() 
# |        244:  %tmp = alloca %"class.hlsl::RWStructuredBuffer", align 4 
# | next:80      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  error: match on wrong line
# |        245:  %call1 = call noundef nonnull align 4 dereferenceable(4) ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @arrayOfA.1.Buf, i32 noundef 0) #5 [ "convergencectrl"(token %0) ] 
# |        246:  store float 1.000000e+00, ptr %call1, align 4 
# |        247:  %call2 = call noundef nonnull align 4 dereferenceable(4) ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @gArray.1.multiArray.1.0.Buf, i32 noundef 0) #5 [ "convergencectrl"(token %0) ] 
# |        248:  store float 2.000000e+00, ptr %call2, align 4 
# |        249:  %call3 = call noundef nonnull align 4 dereferenceable(4) ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @gArray.0.multiArray.0.1.Buf, i32 noundef 0) #5 [ "convergencectrl"(token %0) ] 
# |          .
# |          .
# |          .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

Clang.CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
c:\_work\llvm-project\llvm-project\build\bin\clang.exe -cc1 -internal-isystem C:\_work\llvm-project\llvm-project\build\lib\clang\23\include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs-inheritance.hlsl | llvm-cxxfilt | c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs-inheritance.hlsl
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\clang.exe' -cc1 -internal-isystem 'C:\_work\llvm-project\llvm-project\build\lib\clang\23\include' -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - 'C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs-inheritance.hlsl'
# note: command had no output on stdout or stderr
# executed command: llvm-cxxfilt
# note: command had no output on stdout or stderr
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe' 'C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs-inheritance.hlsl'
# .---command stderr------------
# | C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs-inheritance.hlsl:96:16: error: CHECK-NEXT: is not on the line after the previous match
# | // CHECK-NEXT: %i = alloca i32
# |                ^
# | <stdin>:273:2: note: 'next' match was here
# |  %i = alloca i32, align 4
# |  ^
# | <stdin>:271:7: note: previous match ended here
# | entry:
# |       ^
# | <stdin>:272:1: note: non-matching line after previous match is here
# |  %0 = call token @llvm.experimental.convergence.entry()
# | ^
# | 
# | Input file: <stdin>
# | Check file: C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs-inheritance.hlsl
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |          .
# |          .
# |          .
# |        268:  
# |        269: ; Function Attrs: alwaysinline convergent mustprogress norecurse nounwind 
# |        270: define internal void @main()() #2 { 
# |        271: entry: 
# |        272:  %0 = call token @llvm.experimental.convergence.entry() 
# |        273:  %i = alloca i32, align 4 
# | next:96      !~~~~~~~~~~~~~~           error: match on wrong line
# |        274:  %tmp = alloca %"class.hlsl::StructuredBuffer.0", align 4 
# |        275:  %a = alloca float, align 4 
# |        276:  %call1 = call noundef nonnull align 4 dereferenceable(4) ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @"_ZL8c.A::Buf", i32 noundef 0) #5 [ "convergencectrl"(token %0) ] 
# |        277:  store float 0x3FF3AE1480000000, ptr %call1, align 4 
# |        278:  %call2 = call noundef nonnull align 4 dereferenceable(4) ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @c.Buf2, i32 noundef 0) #5 [ "convergencectrl"(token %0) ] 
# |          .
# |          .
# |          .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

Clang.CodeGenHLSL/resources/resources-in-structs.hlsl
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
c:\_work\llvm-project\llvm-project\build\bin\clang.exe -cc1 -internal-isystem C:\_work\llvm-project\llvm-project\build\lib\clang\23\include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs.hlsl | llvm-cxxfilt | c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs.hlsl
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\clang.exe' -cc1 -internal-isystem 'C:\_work\llvm-project\llvm-project\build\lib\clang\23\include' -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - 'C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs.hlsl'
# note: command had no output on stdout or stderr
# executed command: llvm-cxxfilt
# note: command had no output on stdout or stderr
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe' 'C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs.hlsl'
# .---command stderr------------
# | C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs.hlsl:44:16: error: CHECK-NEXT: is not on the line after the previous match
# | // CHECK-NEXT: call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)
# |                ^
# | <stdin>:26:2: note: 'next' match was here
# |  call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)(ptr dead_on_unwind writable sret(%"class.hlsl::RWBuffer") align 4 @a.Buf, i32 noundef 0, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @.str) #5 [ "convergencectrl"(token %0) ]
# |  ^
# | <stdin>:24:7: note: previous match ended here
# | entry:
# |       ^
# | <stdin>:25:1: note: non-matching line after previous match is here
# |  %0 = call token @llvm.experimental.convergence.entry()
# | ^
# | 
# | Input file: <stdin>
# | Check file: C:\_work\llvm-project\llvm-project\clang\test\CodeGenHLSL\resources\resources-in-structs.hlsl
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |          .
# |          .
# |          .
# |         21:  
# |         22: ; Function Attrs: alwaysinline convergent nounwind 
# |         23: define internal void @__cxx_global_var_init() #0 { 
# |         24: entry: 
# |         25:  %0 = call token @llvm.experimental.convergence.entry() 
# |         26:  call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)(ptr dead_on_unwind writable sret(%"class.hlsl::RWBuffer") align 4 @a.Buf, i32 noundef 0, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @.str) #5 [ "convergencectrl"(token %0) ] 
# | next:44      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                                                                                                                                                                                              error: match on wrong line
# |         27:  ret void 
# |         28: } 
# |         29:  
# |         30: ; Function Attrs: convergent nocallback nofree nosync nounwind willreturn memory(none) 
# |         31: declare token @llvm.experimental.convergence.entry() #1 
# |          .
# |          .
# |          .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

@andykaylor andykaylor merged commit 72861ae into llvm:main Apr 20, 2026
9 of 10 checks passed
@andykaylor andykaylor deleted the cir-new-delete-conditional branch April 20, 2026 19:46
s-perron pushed a commit to s-perron/llvm-project that referenced this pull request Apr 21, 2026
…lvm#192544)

This implements handling for calling delete in an EH handler after a
call to new when the new call appears inside a conditional operation,
which requires the new result to be spilled inside the cleanup scope and
reloaded after.

This implementation introduces the DominatingValue helper class, which
is adapted from classic codegen, but only the parts of that class that
are needed for the current change are implemented. This will likely be
expanded in a future change as other uses are added.

Assisted-by: Cursor / claude-4.6-opus-high
KHicketts pushed a commit to KHicketts/llvm-project that referenced this pull request Apr 30, 2026
…lvm#192544)

This implements handling for calling delete in an EH handler after a
call to new when the new call appears inside a conditional operation,
which requires the new result to be spilled inside the cleanup scope and
reloaded after.

This implementation introduces the DominatingValue helper class, which
is adapted from classic codegen, but only the parts of that class that
are needed for the current change are implemented. This will likely be
expanded in a future change as other uses are added.

Assisted-by: Cursor / claude-4.6-opus-high
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.

4 participants