Author: Andy Kaylor Date: 2026-08-03T18:55:58Z New Revision: d5a6124259b55789bc49489632efa7c168a4f6cb
URL: https://github.com/llvm/llvm-project/commit/d5a6124259b55789bc49489632efa7c168a4f6cb DIFF: https://github.com/llvm/llvm-project/commit/d5a6124259b55789bc49489632efa7c168a4f6cb.diff LOG: [CIR] Fix cleanup of temporaries with null-checked new expr (#213376) We had a problem where a new expression that required both a null check of the allocated pointer and a cleanup of temporaries values created for the initializer was causing us to generate an unterminated cleanup scope. This was happening because we weren't pre-creating a cleanup scope around the entire expression for the temporary and ended up emitting it in an unexpected location. This change adds a check for the null-checked new expression in `ConditionalEvaluationFinder` and wraps the call to emit the initializer with `ConditionEvalulation::begin/endEvaluation` calls. Note that I have sunk the `begin/endEvaluation` into the `emitInit` lambda so that it can surround just the `emitNewInitializer` call and not the `enterNewDeleteCleanup` call, which creates a local cleanup scope for the case where the allocation succeeded but the constructor throws an exception. Classic codegen calls `begin/endEvaluation` at a higher level and ends up using an active flag for the allocation delete cleanup even though the flag is always true when that cleanup is reached. Assisted-by: Cursor / various models Added: Modified: clang/lib/CIR/CodeGen/CIRGenCleanup.cpp clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp clang/test/CIR/CodeGen/new-null.cpp Removed: ################################################################################ diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp index 104a49c8bce26..07cbe34409ea4 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp @@ -26,11 +26,8 @@ using namespace clang; using namespace clang::CIRGen; namespace { -/// Return true if the expression tree contains an AbstractConditionalOperator -/// (ternary ?:), which is the only construct whose CIR codegen calls -/// ConditionalEvaluation::beginEvaluation() and thus causes cleanups to be -/// deferred via pushFullExprCleanup. Logical &&/|| do NOT call -/// beginEvaluation(); their branch-local cleanups are handled by LexicalScope. +/// Return true if the expression tree contains a construct that causes cleanups +/// to be deferred via pushFullExprCleanup. class ConditionalEvaluationFinder : public RecursiveASTVisitor<ConditionalEvaluationFinder> { bool foundConditional = false; @@ -43,6 +40,17 @@ class ConditionalEvaluationFinder return false; } + bool VisitCXXNewExpr(CXXNewExpr *e) { + // If the new expression has an initializer, the initializer may contain a + // a temporary expression that requires deferred cleanup. If we're emitting + // a null check, we need to make this cleanup conditional. + if (e->hasInitializer() && e->shouldNullCheckAllocation()) { + foundConditional = true; + return false; + } + return true; + } + // Don't cross evaluation-context boundaries. bool TraverseLambdaExpr(LambdaExpr *) { return true; } bool TraverseBlockExpr(BlockExpr *) { return true; } diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp index b46fb8a93da42..43c210b44f011 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp @@ -1695,9 +1695,15 @@ mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) { // Lambda that emits the init sequence: cleanup setup, cookie init, // bitcast + initializer, and cleanup deactivation. + // + // \p conditional is non-null when this new-expression is null-checked. + // ConditionalEvaluation is activated only around emitNewInitializer so that + // temporaries created there get conditional cleanups, while the operator + // delete cleanup (which is entered and left entirely inside the null-check + // branch) stays on the cheaper unconditional path. Address result = Address::invalid(); Address resultPtr = Address::invalid(); - auto emitInit = [&]() { + auto emitInit = [&](ConditionalEvaluation *conditional) { EHScopeStack::stable_iterator operatorDeleteCleanup; mlir::Operation *cleanupDominator = nullptr; if (useNewDeleteCleanup) { @@ -1739,8 +1745,12 @@ mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) { assert(!cir::MissingFeatures::sanitizers()); + if (conditional) + conditional->beginEvaluation(); emitNewInitializer(*this, e, allocType, elementTy, result, numElements, allocSizeWithoutCookie); + if (conditional) + conditional->endEvaluation(); // Deactivate the 'operator delete' cleanup if we finished // initialization. @@ -1755,17 +1765,23 @@ mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) { cir::IfOp nullCheckOp; if (nullCheck) { + // The initializer is only run if the allocation succeeds, so any + // temporaries created while emitting the initializer must be cleaned up + // conditionally (with an active flag) after the branch. The enclosing + // FullExprCleanupScope detects this via ConditionalEvaluationFinder and + // provides the cleanup region for the deferred destructors. + ConditionalEvaluation eval(*this); mlir::Value isNotNull = builder.createPtrIsNotNull(allocation.getPointer()); nullCheckOp = cir::IfOp::create(builder, getLoc(e->getSourceRange()), isNotNull, /*withElseRegion=*/false, /*thenBuilder=*/ [&](mlir::OpBuilder &, mlir::Location loc) { - emitInit(); + emitInit(&eval); builder.createYield(loc); }); } else { - emitInit(); + emitInit(/*conditional=*/nullptr); } mlir::Value resultValue = result.getPointer(); diff --git a/clang/test/CIR/CodeGen/new-null.cpp b/clang/test/CIR/CodeGen/new-null.cpp index 1aa085e6d95d0..298ab033c149e 100644 --- a/clang/test/CIR/CodeGen/new-null.cpp +++ b/clang/test/CIR/CodeGen/new-null.cpp @@ -114,3 +114,266 @@ int *test_nothrow_new_init() { // OGCG: br label %[[CONT]] // OGCG: [[CONT]]: // OGCG: phi ptr + +struct T { + T(); + ~T(); + operator int(); +}; + +T makeT(); + +struct U { + U(int); + ~U(); +}; + +// nothrow new with a temporary in the initializer: the temporary's dtor cleanup +// must be conditional because the initializer only runs when allocation succeeds. +// The operator delete cleanup stays unconditional: it is entered entirely inside +// the null-check branch. +U *test_nothrow_new_temp() { + return new (nothrow) U(makeT()); +} + +// CHECK: cir.func {{.*}} @_Z21test_nothrow_new_tempv() +// CHECK: %[[TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_T> +// CHECK: %[[TMP_ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CHECK: cir.cleanup.scope { +// CHECK: %[[ALLOC:.*]] = cir.call @_ZnwmRKSt9nothrow_t({{.*}}) nothrow +// CHECK: %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void> +// CHECK: %[[IS_NOT_NULL:.*]] = cir.cmp ne %[[ALLOC]], %[[NULL]] : !cir.ptr<!void> +// CHECK: %[[FALSE:.*]] = cir.const #false +// CHECK: cir.store %[[FALSE]], %[[TMP_ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CHECK: cir.if %[[IS_NOT_NULL]] { +// CHECK: cir.cleanup.scope { +// CHECK: %[[MAKE_T:.*]] = cir.call @_Z5makeTv() : () -> !rec_T +// CHECK: cir.store{{.*}} %[[MAKE_T]], %[[TMP]] : !rec_T, !cir.ptr<!rec_T> +// CHECK: %[[TRUE:.*]] = cir.const #true +// CHECK: cir.store %[[TRUE]], %[[TMP_ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CHECK: %[[CONV:.*]] = cir.call @_ZN1TcviEv(%[[TMP]]) +// CHECK: cir.call @_ZN1UC1Ei({{.*}}, %[[CONV]]) +// CHECK: } cleanup eh { +// CHECK: cir.call @_ZdlPvRKSt9nothrow_t(%[[ALLOC]], {{.*}}) nothrow +// CHECK: } loc({{.*}}) +// CHECK: } loc({{.*}}) +// CHECK: %[[LOADED:.*]] = cir.load{{.*}} : !cir.ptr<!cir.ptr<!rec_U>>, !cir.ptr<!rec_U> +// CHECK: %[[NULL_U:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!rec_U> +// CHECK: cir.select if %[[IS_NOT_NULL]] then %[[LOADED]] else %[[NULL_U]] +// CHECK: } cleanup all { +// CHECK: %[[TMP_IS_ACTIVE:.*]] = cir.load{{.*}} %[[TMP_ACTIVE]] : !cir.ptr<!cir.bool>, !cir.bool +// CHECK: cir.if %[[TMP_IS_ACTIVE]] { +// CHECK: cir.call @_ZN1TD1Ev(%[[TMP]]) nothrow +// CHECK: } +// CHECK: } + +// LLVM: define {{.*}} ptr @_Z21test_nothrow_new_tempv() {{.*}}personality ptr @__gxx_personality_v0 +// LLVM: %[[TMP:.*]] = alloca %struct.T +// LLVM: %[[ALLOC:.*]] = call {{.*}} ptr @_ZnwmRKSt9nothrow_t(i64 noundef 1, {{.*}}) +// LLVM: %[[CMP:.*]] = icmp ne ptr %[[ALLOC]], null +// LLVM: store i8 0, ptr %[[TMP_ACTIVE:.*]] +// LLVM: br i1 %[[CMP]], label %[[NOT_NULL:.*]], label %[[CONT:.*]] +// LLVM: [[NOT_NULL]]: +// LLVM: %[[MAKE_T:.*]] = invoke %struct.T @_Z5makeTv() +// LLVM: to label %[[INVOKE_CONT:.*]] unwind label %[[LPAD:.*]] +// LLVM: [[INVOKE_CONT]]: +// LLVM: store {{.*}} %[[MAKE_T]], ptr %[[TMP]] +// LLVM: store i8 1, ptr %[[TMP_ACTIVE]] +// LLVM: invoke {{.*}} @_ZN1TcviEv(ptr {{.*}} %[[TMP]]) +// LLVM: invoke void @_ZN1UC1Ei(ptr {{.*}} %[[ALLOC]], i32 {{.*}}) +// LLVM: [[LPAD]]: +// LLVM: landingpad { ptr, i32 } +// LLVM: cleanup +// LLVM: call void @_ZdlPvRKSt9nothrow_t({{.*}} %[[ALLOC]], {{.*}}) +// LLVM: [[CONT]]: +// LLVM: select i1 %[[CMP]], ptr {{.*}}, ptr null +// LLVM: %[[TMP_I8:.*]] = load i8, ptr %[[TMP_ACTIVE]] +// LLVM: %[[TMP_IS_ACTIVE:.*]] = trunc i8 %[[TMP_I8]] to i1 +// LLVM: br i1 %[[TMP_IS_ACTIVE]], label %[[DO_TMP_DTOR:.*]], label %[[SKIP_TMP_DTOR:.*]] +// LLVM: [[DO_TMP_DTOR]]: +// LLVM: call void @_ZN1TD1Ev(ptr {{.*}} %[[TMP]]) + +// OGCG: define {{.*}} ptr @_Z21test_nothrow_new_tempv() {{.*}}personality ptr @__gxx_personality_v0 +// OGCG: entry: +// OGCG: %[[TMP:.*]] = alloca %struct.T +// OGCG: %[[ALLOC:.*]] = call {{.*}} ptr @_ZnwmRKSt9nothrow_t(i64 noundef 1, {{.*}}) +// OGCG: %[[IS_NULL:.*]] = icmp eq ptr %[[ALLOC]], null +// OGCG: store i1 false, ptr %[[DELETE_ACTIVE:.*]] +// OGCG: store i1 false, ptr %[[TMP_ACTIVE:.*]] +// OGCG: br i1 %[[IS_NULL]], label %[[CONT:.*]], label %[[NOT_NULL:.*]] +// OGCG: [[NOT_NULL]]: +// OGCG: store i1 true, ptr %[[DELETE_ACTIVE]] +// OGCG: invoke void @_Z5makeTv(ptr {{.*}} %[[TMP]]) +// OGCG: to label %[[INVOKE_CONT:.*]] unwind label %[[LPAD:.*]] +// OGCG: [[INVOKE_CONT]]: +// OGCG: store i1 true, ptr %[[TMP_ACTIVE]] +// OGCG: invoke {{.*}} @_ZN1TcviEv(ptr {{.*}} %[[TMP]]) +// OGCG: invoke void @_ZN1UC1Ei(ptr {{.*}} %[[ALLOC]], i32 {{.*}}) +// OGCG: store i1 false, ptr %[[DELETE_ACTIVE]] +// OGCG: br label %[[CONT]] +// OGCG: [[CONT]]: +// OGCG: phi ptr +// OGCG: %[[TMP_IS_ACTIVE:.*]] = load i1, ptr %[[TMP_ACTIVE]] +// OGCG: br i1 %[[TMP_IS_ACTIVE]], label %[[DO_TMP_DTOR:.*]], label %[[SKIP_TMP_DTOR:.*]] +// OGCG: [[DO_TMP_DTOR]]: +// OGCG: call void @_ZN1TD1Ev(ptr {{.*}} %[[TMP]]) +// OGCG: [[LPAD]]: +// OGCG: landingpad { ptr, i32 } +// OGCG: cleanup +// OGCG: %[[DEL_ACTIVE:.*]] = load i1, ptr %[[DELETE_ACTIVE]] +// OGCG: br i1 %[[DEL_ACTIVE]], label %[[DO_DELETE:.*]], label %[[SKIP_DELETE:.*]] +// OGCG: [[DO_DELETE]]: +// OGCG: call void @_ZdlPvRKSt9nothrow_t({{.*}}) + +struct InnerT { + InnerT(); + ~InnerT(); + operator int(); +}; + +struct OuterT { + OuterT(int); + ~OuterT(); + operator int(); +}; + +InnerT makeInnerT(); +OuterT makeOuterT(int); + +// Nested temporaries in a nothrow-new initializer: each temporary gets its own +// conditional cleanup flag, destroyed in reverse construction order. +U *test_nothrow_new_nested_temps() { + return new (nothrow) U(makeOuterT(makeInnerT())); +} + +// CHECK: cir.func {{.*}} @_Z29test_nothrow_new_nested_tempsv() +// CHECK: %[[OUTER_TMP:.*]] = cir.alloca "ref.tmp0" {{.*}} : !cir.ptr<!rec_OuterT> +// CHECK: %[[INNER_TMP:.*]] = cir.alloca "ref.tmp1" {{.*}} : !cir.ptr<!rec_InnerT> +// CHECK: %[[INNER_ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CHECK: %[[OUTER_ACTIVE:.*]] = cir.alloca "cleanup.cond" {{.*}} : !cir.ptr<!cir.bool> +// CHECK: cir.cleanup.scope { +// CHECK: %[[ALLOC:.*]] = cir.call @_ZnwmRKSt9nothrow_t({{.*}}) nothrow +// CHECK: %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void> +// CHECK: %[[IS_NOT_NULL:.*]] = cir.cmp ne %[[ALLOC]], %[[NULL]] : !cir.ptr<!void> +// CHECK: %[[FALSE:.*]] = cir.const #false +// CHECK: cir.store %[[FALSE]], %[[INNER_ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CHECK: %[[FALSE2:.*]] = cir.const #false +// CHECK: cir.store %[[FALSE2]], %[[OUTER_ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CHECK: cir.if %[[IS_NOT_NULL]] { +// CHECK: cir.cleanup.scope { +// CHECK: %[[MAKE_INNER:.*]] = cir.call @_Z10makeInnerTv() : () -> !rec_InnerT +// CHECK: cir.store{{.*}} %[[MAKE_INNER]], %[[INNER_TMP]] : !rec_InnerT, !cir.ptr<!rec_InnerT> +// CHECK: %[[TRUE:.*]] = cir.const #true +// CHECK: cir.store %[[TRUE]], %[[INNER_ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CHECK: %[[INNER_CONV:.*]] = cir.call @_ZN6InnerTcviEv(%[[INNER_TMP]]) +// CHECK: %[[MAKE_OUTER:.*]] = cir.call @_Z10makeOuterTi(%[[INNER_CONV]]) : (!s32i {{.*}}) -> !rec_OuterT +// CHECK: cir.store{{.*}} %[[MAKE_OUTER]], %[[OUTER_TMP]] : !rec_OuterT, !cir.ptr<!rec_OuterT> +// CHECK: %[[TRUE2:.*]] = cir.const #true +// CHECK: cir.store %[[TRUE2]], %[[OUTER_ACTIVE]] : !cir.bool, !cir.ptr<!cir.bool> +// CHECK: %[[OUTER_CONV:.*]] = cir.call @_ZN6OuterTcviEv(%[[OUTER_TMP]]) +// CHECK: cir.call @_ZN1UC1Ei({{.*}}, %[[OUTER_CONV]]) +// CHECK: } cleanup eh { +// CHECK: cir.call @_ZdlPvRKSt9nothrow_t(%[[ALLOC]], {{.*}}) nothrow +// CHECK: } loc({{.*}}) +// CHECK: } loc({{.*}}) +// CHECK: %[[LOADED:.*]] = cir.load{{.*}} : !cir.ptr<!cir.ptr<!rec_U>>, !cir.ptr<!rec_U> +// CHECK: %[[NULL_U:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!rec_U> +// CHECK: cir.select if %[[IS_NOT_NULL]] then %[[LOADED]] else %[[NULL_U]] +// CHECK: } cleanup all { +// CHECK: %[[OUTER_IS_ACTIVE:.*]] = cir.load{{.*}} %[[OUTER_ACTIVE]] : !cir.ptr<!cir.bool>, !cir.bool +// CHECK: cir.if %[[OUTER_IS_ACTIVE]] { +// CHECK: cir.call @_ZN6OuterTD1Ev(%[[OUTER_TMP]]) nothrow +// CHECK: } +// CHECK: %[[INNER_IS_ACTIVE:.*]] = cir.load{{.*}} %[[INNER_ACTIVE]] : !cir.ptr<!cir.bool>, !cir.bool +// CHECK: cir.if %[[INNER_IS_ACTIVE]] { +// CHECK: cir.call @_ZN6InnerTD1Ev(%[[INNER_TMP]]) nothrow +// CHECK: } +// CHECK: } + +// LLVM: define {{.*}} ptr @_Z29test_nothrow_new_nested_tempsv() {{.*}}personality ptr @__gxx_personality_v0 +// LLVM: %[[OUTER_TMP:.*]] = alloca %struct.OuterT +// LLVM: %[[INNER_TMP:.*]] = alloca %struct.InnerT +// LLVM: %[[ALLOC:.*]] = call {{.*}} ptr @_ZnwmRKSt9nothrow_t(i64 noundef 1, {{.*}}) +// LLVM: %[[CMP:.*]] = icmp ne ptr %[[ALLOC]], null +// LLVM: store i8 0, ptr %[[INNER_ACTIVE:.*]] +// LLVM: store i8 0, ptr %[[OUTER_ACTIVE:.*]] +// LLVM: br i1 %[[CMP]], label %[[NOT_NULL:.*]], label %[[CONT:.*]] +// LLVM: [[NOT_NULL]]: +// LLVM: %[[MAKE_INNER:.*]] = invoke %struct.InnerT @_Z10makeInnerTv() +// LLVM: to label %[[INNER_CONT:.*]] unwind label %[[LPAD:.*]] +// LLVM: [[INNER_CONT]]: +// LLVM: store {{.*}} %[[MAKE_INNER]], ptr %[[INNER_TMP]] +// LLVM: store i8 1, ptr %[[INNER_ACTIVE]] +// LLVM: %[[INNER_CONV:.*]] = invoke {{.*}} @_ZN6InnerTcviEv(ptr {{.*}} %[[INNER_TMP]]) +// LLVM: to label %[[INNER_CONV_CONT:.*]] unwind label %[[LPAD]] +// LLVM: [[INNER_CONV_CONT]]: +// LLVM: %[[MAKE_OUTER:.*]] = invoke %struct.OuterT @_Z10makeOuterTi(i32 {{.*}} %[[INNER_CONV]]) +// LLVM: to label %[[OUTER_CONT:.*]] unwind label %[[LPAD]] +// LLVM: [[OUTER_CONT]]: +// LLVM: store {{.*}} %[[MAKE_OUTER]], ptr %[[OUTER_TMP]] +// LLVM: store i8 1, ptr %[[OUTER_ACTIVE]] +// LLVM: invoke {{.*}} @_ZN6OuterTcviEv(ptr {{.*}} %[[OUTER_TMP]]) +// LLVM: invoke void @_ZN1UC1Ei(ptr {{.*}} %[[ALLOC]], i32 {{.*}}) +// LLVM: [[LPAD]]: +// LLVM: landingpad { ptr, i32 } +// LLVM: cleanup +// LLVM: call void @_ZdlPvRKSt9nothrow_t({{.*}} %[[ALLOC]], {{.*}}) +// LLVM: [[CONT]]: +// LLVM: select i1 %[[CMP]], ptr {{.*}}, ptr null +// LLVM: %[[OUTER_I8:.*]] = load i8, ptr %[[OUTER_ACTIVE]] +// LLVM: %[[OUTER_IS_ACTIVE:.*]] = trunc i8 %[[OUTER_I8]] to i1 +// LLVM: br i1 %[[OUTER_IS_ACTIVE]], label %[[DO_OUTER_DTOR:.*]], label %[[SKIP_OUTER_DTOR:.*]] +// LLVM: [[DO_OUTER_DTOR]]: +// LLVM: call void @_ZN6OuterTD1Ev(ptr {{.*}} %[[OUTER_TMP]]) +// LLVM: [[SKIP_OUTER_DTOR]]: +// LLVM: %[[INNER_I8:.*]] = load i8, ptr %[[INNER_ACTIVE]] +// LLVM: %[[INNER_IS_ACTIVE:.*]] = trunc i8 %[[INNER_I8]] to i1 +// LLVM: br i1 %[[INNER_IS_ACTIVE]], label %[[DO_INNER_DTOR:.*]], label %[[SKIP_INNER_DTOR:.*]] +// LLVM: [[DO_INNER_DTOR]]: +// LLVM: call void @_ZN6InnerTD1Ev(ptr {{.*}} %[[INNER_TMP]]) + +// OGCG: define {{.*}} ptr @_Z29test_nothrow_new_nested_tempsv() {{.*}}personality ptr @__gxx_personality_v0 +// OGCG: entry: +// OGCG: %[[OUTER_TMP:.*]] = alloca %struct.OuterT +// OGCG: %[[INNER_TMP:.*]] = alloca %struct.InnerT +// OGCG: %[[ALLOC:.*]] = call {{.*}} ptr @_ZnwmRKSt9nothrow_t(i64 noundef 1, {{.*}}) +// OGCG: %[[IS_NULL:.*]] = icmp eq ptr %[[ALLOC]], null +// OGCG: store i1 false, ptr %[[DELETE_ACTIVE:.*]] +// OGCG: store i1 false, ptr %[[INNER_ACTIVE:.*]] +// OGCG: store i1 false, ptr %[[OUTER_ACTIVE:.*]] +// OGCG: br i1 %[[IS_NULL]], label %[[CONT:.*]], label %[[NOT_NULL:.*]] +// OGCG: [[NOT_NULL]]: +// OGCG: store i1 true, ptr %[[DELETE_ACTIVE]] +// OGCG: invoke void @_Z10makeInnerTv(ptr {{.*}} %[[INNER_TMP]]) +// OGCG: to label %[[INNER_CONT:.*]] unwind label %[[LPAD:.*]] +// OGCG: [[INNER_CONT]]: +// OGCG: store i1 true, ptr %[[INNER_ACTIVE]] +// OGCG: %[[INNER_CONV:.*]] = invoke {{.*}} @_ZN6InnerTcviEv(ptr {{.*}} %[[INNER_TMP]]) +// OGCG: to label %[[INNER_CONV_CONT:.*]] unwind label %[[LPAD_INNER:.*]] +// OGCG: [[INNER_CONV_CONT]]: +// OGCG: invoke void @_Z10makeOuterTi(ptr {{.*}} %[[OUTER_TMP]], i32 {{.*}} %[[INNER_CONV]]) +// OGCG: to label %[[OUTER_CONT:.*]] unwind label %[[LPAD_INNER]] +// OGCG: [[OUTER_CONT]]: +// OGCG: store i1 true, ptr %[[OUTER_ACTIVE]] +// OGCG: invoke {{.*}} @_ZN6OuterTcviEv(ptr {{.*}} %[[OUTER_TMP]]) +// OGCG: invoke void @_ZN1UC1Ei(ptr {{.*}} %[[ALLOC]], i32 {{.*}}) +// OGCG: store i1 false, ptr %[[DELETE_ACTIVE]] +// OGCG: br label %[[CONT]] +// OGCG: [[CONT]]: +// OGCG: phi ptr +// OGCG: %[[OUTER_IS_ACTIVE:.*]] = load i1, ptr %[[OUTER_ACTIVE]] +// OGCG: br i1 %[[OUTER_IS_ACTIVE]], label %[[DO_OUTER_DTOR:.*]], label %[[SKIP_OUTER_DTOR:.*]] +// OGCG: [[DO_OUTER_DTOR]]: +// OGCG: call void @_ZN6OuterTD1Ev(ptr {{.*}} %[[OUTER_TMP]]) +// OGCG: [[SKIP_OUTER_DTOR]]: +// OGCG: %[[INNER_IS_ACTIVE:.*]] = load i1, ptr %[[INNER_ACTIVE]] +// OGCG: br i1 %[[INNER_IS_ACTIVE]], label %[[DO_INNER_DTOR:.*]], label %[[SKIP_INNER_DTOR:.*]] +// OGCG: [[DO_INNER_DTOR]]: +// OGCG: call void @_ZN6InnerTD1Ev(ptr {{.*}} %[[INNER_TMP]]) +// OGCG: [[LPAD]]: +// OGCG: landingpad { ptr, i32 } +// OGCG: cleanup +// OGCG: %[[DEL_ACTIVE:.*]] = load i1, ptr %[[DELETE_ACTIVE]] +// OGCG: br i1 %[[DEL_ACTIVE]], label %[[DO_DELETE:.*]], label %[[SKIP_DELETE:.*]] +// OGCG: [[DO_DELETE]]: +// OGCG: call void @_ZdlPvRKSt9nothrow_t({{.*}}) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
