blob: 27ed405a0fe47ac482450b80813930c946d91546 [file] [edit]
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -mconstructor-aliases -emit-cir %s -o %t.cir
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -mconstructor-aliases -emit-llvm %s -o %t-cir.ll
// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -mconstructor-aliases -emit-llvm %s -o %t.ll
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
// Minimal in-source declarations of the standard-library bits needed for a
// destroying operator delete so this test does not depend on a system header.
namespace std {
struct destroying_delete_t {
explicit destroying_delete_t() = default;
};
inline constexpr destroying_delete_t destroying_delete{};
} // namespace std
// A destroying operator delete: its second parameter is
// std::destroying_delete_t. Such an operator delete takes full responsibility
// for both destruction and deallocation of the object, so the 'delete'
// expression must not emit a separate destructor call or cleanup.
struct S {
void operator delete(S *, std::destroying_delete_t);
~S();
};
void test_destroying_delete(S *s) {
delete s;
}
// S::operator delete(S *, std::destroying_delete_t)
// CIR: cir.func private @_ZN1SdlEPS_St19destroying_delete_t(!cir.ptr<!rec_S> {llvm.noundef}, !rec_std3A3Adestroying_delete_t)
// LLVM: declare void @_ZN1SdlEPS_St19destroying_delete_t(ptr noundef, %"struct.std::destroying_delete_t")
// The destroying operator delete takes over the entire delete operation:
// no destructor call and no delete-cleanup are emitted in the caller; the
// operator delete is invoked inside the standard null-check if-region and is
// responsible for destroying the object itself.
// CIR: cir.func {{.*}} @_Z22test_destroying_deleteP1S(%[[ARG:.*]]: !cir.ptr<!rec_S> {{.*}})
// CIR: %[[S_ADDR:.*]] = cir.alloca "s" {{.*}} init : !cir.ptr<!cir.ptr<!rec_S>>
// CIR: %[[TAG_ADDR:.*]] = cir.alloca "destroying.delete.tag" {{.*}} : !cir.ptr<!rec_std3A3Adestroying_delete_t{{.*}}>
// CIR: cir.store %[[ARG]], %[[S_ADDR]]
// CIR: %[[S:.*]] = cir.load{{.*}} %[[S_ADDR]]
// CIR: %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!rec_S>
// CIR: %[[NOT_NULL:.*]] = cir.cmp ne %[[S]], %[[NULL]] : !cir.ptr<!rec_S>
// CIR: cir.if %[[NOT_NULL]] {
// CIR: %[[TAG:.*]] = cir.load{{.*}} %[[TAG_ADDR]]
// CIR: cir.call @_ZN1SdlEPS_St19destroying_delete_t(%[[S]], %[[TAG]]){{.*}} : (!cir.ptr<!rec_S> {{.*}}, !rec_std3A3Adestroying_delete_t) -> ()
// CIR-NOT: cir.call @_ZN1SD{{[12]}}Ev
// CIR: }
// CIR: cir.return
// LLVM: define {{.*}} void @_Z22test_destroying_deleteP1S(ptr {{.*}} %[[ARG:.*]])
// LLVM: %[[S_ADDR:.*]] = alloca ptr
// LLVM: %[[TAG_ADDR:.*]] = alloca %"struct.std::destroying_delete_t"
// LLVM: store ptr %[[ARG]], ptr %[[S_ADDR]]
// LLVM: %[[S:.*]] = load ptr, ptr %[[S_ADDR]]
// LLVM: %[[NOT_NULL:.*]] = icmp ne ptr %[[S]], null
// LLVM: br i1 %[[NOT_NULL]], label %[[NOTNULL:.*]], label %[[END:.*]]
// LLVM: [[NOTNULL]]:
// LLVM: %[[TAG:.*]] = load %"struct.std::destroying_delete_t", ptr %[[TAG_ADDR]]
// LLVM: call void @_ZN1SdlEPS_St19destroying_delete_t(ptr noundef %[[S]], %"struct.std::destroying_delete_t" %[[TAG]])
// LLVM-NOT: call void @_ZN1SD{{[12]}}Ev
// LLVM: [[END]]:
// LLVM: ret void
// Classic codegen elides empty-class parameters at the ABI level, so the
// destroying_delete_t tag disappears from both the declaration and the call.
// Either way, no destructor call is emitted from the caller.
// OGCG: define {{.*}} void @_Z22test_destroying_deleteP1S(ptr {{.*}} %[[ARG:.*]])
// OGCG: %[[S_ADDR:.*]] = alloca ptr
// OGCG: store ptr %[[ARG]], ptr %[[S_ADDR]]
// OGCG: %[[S:.*]] = load ptr, ptr %[[S_ADDR]]
// OGCG: %[[ISNULL:.*]] = icmp eq ptr %[[S]], null
// OGCG: br i1 %[[ISNULL]], label %[[END:.*]], label %[[NOTNULL:.*]]
// OGCG: [[NOTNULL]]:
// OGCG: call void @_ZN1SdlEPS_St19destroying_delete_t(ptr noundef %[[S]])
// OGCG-NOT: call void @_ZN1SD{{[12]}}Ev
// OGCG: [[END]]:
// OGCG: ret void
// OGCG: declare void @_ZN1SdlEPS_St19destroying_delete_t(ptr noundef)
// A class with a virtual destructor and a destroying operator delete.
// Per the Itanium C++ ABI, the call is dispatched through the vtable's
// deleting-destructor slot (entry index 1); that function is responsible
// for running the destructor chain and then invoking the class-level
// destroying operator delete. The caller therefore does not call the
// destructor or the operator delete directly.
struct V {
virtual ~V();
void operator delete(V *, std::destroying_delete_t);
};
void test_virtual_destroying_delete(V *v) {
delete v;
}
// CIR: cir.func {{.*}} @_Z30test_virtual_destroying_deleteP1V(%[[ARG:.*]]: !cir.ptr<!rec_V> {{.*}})
// CIR: %[[V_ADDR:.*]] = cir.alloca "v" {{.*}} init : !cir.ptr<!cir.ptr<!rec_V>>
// CIR: cir.store %[[ARG]], %[[V_ADDR]]
// CIR: %[[V:.*]] = cir.load{{.*}} %[[V_ADDR]]
// CIR: %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!rec_V>
// CIR: %[[NOT_NULL:.*]] = cir.cmp ne %[[V]], %[[NULL]] : !cir.ptr<!rec_V>
// CIR: cir.if %[[NOT_NULL]] {
// CIR: %[[VPTR_ADDR:.*]] = cir.vtable.get_vptr %[[V]] : !cir.ptr<!rec_V> -> !cir.ptr<!cir.vptr>
// CIR: %[[VPTR:.*]] = cir.load{{.*}} %[[VPTR_ADDR]] : !cir.ptr<!cir.vptr>, !cir.vptr
// CIR: %[[SLOT_ADDR:.*]] = cir.vtable.get_virtual_fn_addr %[[VPTR]][1]
// CIR: %[[SLOT:.*]] = cir.load{{.*}} %[[SLOT_ADDR]]
// CIR: cir.call %[[SLOT]](%[[V]]){{.*}} : (!cir.ptr<!cir.func<(!cir.ptr<!rec_V>)>>, !cir.ptr<!rec_V> {{.*}}) -> ()
// CIR-NOT: cir.call @_ZN1VdlEPS_St19destroying_delete_t
// CIR-NOT: cir.call @_ZN1VD{{[12]}}Ev
// CIR: }
// CIR: cir.return
// LLVM: define {{.*}} void @_Z30test_virtual_destroying_deleteP1V(ptr {{.*}} %[[ARG:.*]])
// LLVM: %[[V_ADDR:.*]] = alloca ptr
// LLVM: store ptr %[[ARG]], ptr %[[V_ADDR]]
// LLVM: %[[V:.*]] = load ptr, ptr %[[V_ADDR]]
// LLVM: %[[NOT_NULL:.*]] = icmp ne ptr %[[V]], null
// LLVM: br i1 %[[NOT_NULL]], label %[[NOTNULL:.*]], label %[[END:.*]]
// LLVM: [[NOTNULL]]:
// LLVM: %[[VTABLE:.*]] = load ptr, ptr %[[V]]
// LLVM: %[[VFN_PTR:.*]] = getelementptr inbounds ptr, ptr %[[VTABLE]], i32 1
// LLVM: %[[VFN:.*]] = load ptr, ptr %[[VFN_PTR]]
// LLVM: call void %[[VFN]](ptr {{.*}} %[[V]])
// LLVM-NOT: call void @_ZN1VdlEPS_St19destroying_delete_t
// LLVM-NOT: call void @_ZN1VD{{[12]}}Ev
// LLVM: [[END]]:
// LLVM: ret void
// OGCG: define {{.*}} void @_Z30test_virtual_destroying_deleteP1V(ptr {{.*}} %[[ARG:.*]])
// OGCG: %[[V_ADDR:.*]] = alloca ptr
// OGCG: store ptr %[[ARG]], ptr %[[V_ADDR]]
// OGCG: %[[V:.*]] = load ptr, ptr %[[V_ADDR]]
// OGCG: %[[ISNULL:.*]] = icmp eq ptr %[[V]], null
// OGCG: br i1 %[[ISNULL]], label %[[END:.*]], label %[[NOTNULL:.*]]
// OGCG: [[NOTNULL]]:
// OGCG: %[[VTABLE:.*]] = load ptr, ptr %[[V]]
// OGCG: %[[VFN_PTR:.*]] = getelementptr inbounds ptr, ptr %[[VTABLE]], i64 1
// OGCG: %[[VFN:.*]] = load ptr, ptr %[[VFN_PTR]]
// OGCG: call void %[[VFN]](ptr {{.*}} %[[V]])
// OGCG-NOT: call void @_ZN1VdlEPS_St19destroying_delete_t
// OGCG-NOT: call void @_ZN1VD{{[12]}}Ev
// OGCG: [[END]]:
// OGCG: ret void