MSVC supports an extension allowing to delete an array of objects via pointer whose static type doesn't match its dynamic type. This is done via generation of special destructors - vector deleting destructors. MSVC's virtual tables always contain a pointer to the vector deleting destructor for classes with virtual destructors, so not having this extension implemented causes clang to generate code that is not compatible with the code generated by MSVC, because clang always puts a pointer to a scalar deleting destructor to the vtable. As a bonus the deletion of an array of polymorphic object will work just like it does with MSVC - no memory leaks and correct destructors are called. This patch will cause clang to emit code that is compatible with code produced by MSVC but not compatible with code produced with clang of older versions, so the new behavior can be disabled via passing -fclang-abi-compat=21 (or lower). This is yet another attempt to land vector deleting destructors support originally implemented by https://github.com/llvm/llvm-project/pull/133451. This PR contains fixes for issues reported in the original PR as well as fixes for issues related to operator delete[] search reported in several issues like https://github.com/llvm/llvm-project/pull/133950#issuecomment-2787510484 https://github.com/llvm/llvm-project/issues/134265 Fixes https://github.com/llvm/llvm-project/issues/19772
219 lines
7.7 KiB
C++
219 lines
7.7 KiB
C++
// RUN: %clang_cc1 -fno-rtti %s -emit-llvm -o %t -triple=i386-pc-win32 -fdump-vtable-layouts 2>&1 | FileCheck --check-prefix=VFTABLES %s
|
|
// RUN: FileCheck --check-prefix=GLOBALS %s < %t
|
|
// RUN: FileCheck --check-prefix=CODEGEN %s < %t
|
|
|
|
namespace test1 {
|
|
|
|
// Some covariant types.
|
|
struct A { int a; };
|
|
struct B { int b; };
|
|
struct C : A, B { int c; };
|
|
struct D : C { int d; };
|
|
struct E : D { int e; };
|
|
|
|
// One base class and two overrides, all with covariant return types.
|
|
struct H { virtual B *foo(); };
|
|
struct I : H { virtual C *foo(); };
|
|
struct J : I { virtual D *foo(); J(); };
|
|
struct K : J { virtual E *foo(); K(); };
|
|
|
|
J::J() {}
|
|
|
|
// VFTABLES-LABEL: VFTable for 'test1::H' in 'test1::I' in 'test1::J' (3 entries).
|
|
// VFTABLES-NEXT: 0 | D *test1::J::foo()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct test1::B *'): 4 non-virtual]
|
|
// VFTABLES-NEXT: 1 | D *test1::J::foo()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct test1::C *'): 0 non-virtual]
|
|
// VFTABLES-NEXT: 2 | D *test1::J::foo()
|
|
|
|
// GLOBALS-LABEL: @"??_7J@test1@@6B@" = linkonce_odr unnamed_addr constant { [3 x ptr] }
|
|
// GLOBALS: @"?foo@J@test1@@QAEPAUB@2@XZ"
|
|
// GLOBALS: @"?foo@J@test1@@QAEPAUC@2@XZ"
|
|
// GLOBALS: @"?foo@J@test1@@UAEPAUD@2@XZ"
|
|
|
|
K::K() {}
|
|
|
|
// VFTABLES-LABEL: VFTable for 'test1::H' in 'test1::I' in 'test1::J' in 'test1::K' (4 entries).
|
|
// VFTABLES-NEXT: 0 | E *test1::K::foo()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct test1::B *'): 4 non-virtual]
|
|
// VFTABLES-NEXT: 1 | E *test1::K::foo()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct test1::C *'): 0 non-virtual]
|
|
// VFTABLES-NEXT: 2 | E *test1::K::foo()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct test1::D *'): 0 non-virtual]
|
|
// VFTABLES-NEXT: 3 | E *test1::K::foo()
|
|
|
|
// Only B to C requires adjustment, but we get 3 thunks in K's vftable, two of
|
|
// which are trivial.
|
|
// GLOBALS-LABEL: @"??_7K@test1@@6B@" = linkonce_odr unnamed_addr constant { [4 x ptr] }
|
|
// GLOBALS: @"?foo@K@test1@@QAEPAUB@2@XZ"
|
|
// GLOBALS: @"?foo@K@test1@@QAEPAUC@2@XZ"
|
|
// GLOBALS: @"?foo@K@test1@@QAEPAUD@2@XZ"
|
|
// GLOBALS: @"?foo@K@test1@@UAEPAUE@2@XZ"
|
|
|
|
// This thunk has a return adjustment.
|
|
// CODEGEN-LABEL: define {{.*}} @"?foo@K@test1@@QAEPAUB@2@XZ"
|
|
// CODEGEN: call {{.*}} @"?foo@K@test1@@UAEPAUE@2@XZ"
|
|
// CODEGEN: icmp {{.*}}, null
|
|
// CODEGEN: getelementptr
|
|
// CODEGEN: ret
|
|
|
|
// These two don't.
|
|
// CODEGEN-LABEL: define {{.*}} @"?foo@K@test1@@QAEPAUC@2@XZ"
|
|
// CODEGEN: call {{.*}} @"?foo@K@test1@@UAEPAUE@2@XZ"
|
|
// CODEGEN-NEXT: ret
|
|
|
|
// CODEGEN-LABEL: define {{.*}} @"?foo@K@test1@@QAEPAUD@2@XZ"
|
|
// CODEGEN: call {{.*}} @"?foo@K@test1@@UAEPAUE@2@XZ"
|
|
// CODEGEN-NEXT: ret
|
|
|
|
}
|
|
|
|
namespace test2 {
|
|
|
|
// Covariant types. D* is not trivially convertible to C*.
|
|
struct A { int a; };
|
|
struct B { int b; };
|
|
struct C : B { int c; };
|
|
struct D : A, C { int d; };
|
|
struct E : D { int e; };
|
|
|
|
// J's foo will require an adjusting thunk, and K will require a trivial thunk.
|
|
struct H { virtual B *foo(); };
|
|
struct I : H { virtual C *foo(); };
|
|
struct J : I { virtual D *foo(); J(); };
|
|
struct K : J { virtual E *foo(); K(); };
|
|
|
|
J::J() {}
|
|
|
|
// VFTABLES-LABEL: VFTable for 'test2::H' in 'test2::I' in 'test2::J' (2 entries).
|
|
// VFTABLES-NEXT: 0 | D *test2::J::foo()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct test2::B *'): 4 non-virtual]
|
|
// VFTABLES-NEXT: 1 | D *test2::J::foo()
|
|
|
|
// GLOBALS-LABEL: @"??_7J@test2@@6B@" = linkonce_odr unnamed_addr constant { [2 x ptr] }
|
|
|
|
K::K() {}
|
|
|
|
// VFTABLES-LABEL: VFTable for 'test2::H' in 'test2::I' in 'test2::J' in 'test2::K' (3 entries).
|
|
// VFTABLES-NEXT: 0 | E *test2::K::foo()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct test2::B *'): 4 non-virtual]
|
|
// VFTABLES-NEXT: 1 | E *test2::K::foo()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct test2::D *'): 0 non-virtual]
|
|
// VFTABLES-NEXT: 2 | E *test2::K::foo()
|
|
|
|
// GLOBALS-LABEL: @"??_7K@test2@@6B@" = linkonce_odr unnamed_addr constant { [3 x ptr] }
|
|
|
|
}
|
|
|
|
namespace pr20479 {
|
|
struct A {
|
|
virtual A *f();
|
|
};
|
|
|
|
struct B : virtual A {
|
|
virtual B *f();
|
|
};
|
|
|
|
struct C : virtual A, B {
|
|
// VFTABLES-LABEL: VFTable for 'pr20479::A' in 'pr20479::B' in 'pr20479::C' (2 entries).
|
|
// VFTABLES-NEXT: 0 | B *pr20479::B::f()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct pr20479::A *'): vbase #1, 0 non-virtual]
|
|
// VFTABLES-NEXT: 1 | B *pr20479::B::f()
|
|
C();
|
|
};
|
|
|
|
C::C() {}
|
|
|
|
// GLOBALS-LABEL: @"??_7C@pr20479@@6B@" = linkonce_odr unnamed_addr constant { [2 x ptr] }
|
|
// GLOBALS: @"?f@B@pr20479@@QAEPAUA@2@XZ"
|
|
// GLOBALS: @"?f@B@pr20479@@UAEPAU12@XZ"
|
|
}
|
|
|
|
namespace pr21073 {
|
|
struct A {
|
|
virtual A *f();
|
|
};
|
|
|
|
struct B : virtual A {
|
|
virtual B *f();
|
|
};
|
|
|
|
struct C : virtual A, virtual B {
|
|
// VFTABLES-LABEL: VFTable for 'pr21073::A' in 'pr21073::B' in 'pr21073::C' (2 entries).
|
|
// VFTABLES-NEXT: 0 | B *pr21073::B::f()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct pr21073::A *'): vbase #1, 0 non-virtual]
|
|
// VFTABLES-NEXT: [this adjustment: 8 non-virtual]
|
|
// VFTABLES-NEXT: 1 | B *pr21073::B::f()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct pr21073::B *'): 0 non-virtual]
|
|
// VFTABLES-NEXT: [this adjustment: 8 non-virtual]
|
|
C();
|
|
};
|
|
|
|
C::C() {}
|
|
|
|
// GLOBALS-LABEL: @"??_7C@pr21073@@6B@" = linkonce_odr unnamed_addr constant { [2 x ptr] }
|
|
// GLOBALS: @"?f@B@pr21073@@WPPPPPPPI@AEPAUA@2@XZ"
|
|
// GLOBALS: @"?f@B@pr21073@@WPPPPPPPI@AEPAU12@XZ"
|
|
}
|
|
|
|
namespace pr21073_2 {
|
|
struct A { virtual A *foo(); };
|
|
struct B : virtual A {};
|
|
struct C : virtual A { virtual C *foo(); };
|
|
struct D : B, C { D(); };
|
|
D::D() {}
|
|
|
|
// VFTABLES-LABEL: VFTable for 'pr21073_2::A' in 'pr21073_2::C' in 'pr21073_2::D' (2 entries)
|
|
// VFTABLES-NEXT: 0 | C *pr21073_2::C::foo()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct pr21073_2::A *'): vbase #1, 0 non-virtual]
|
|
// VFTABLES-NEXT: 1 | C *pr21073_2::C::foo()
|
|
|
|
// GLOBALS-LABEL: @"??_7D@pr21073_2@@6B@" = {{.*}} constant { [2 x ptr] }
|
|
// GLOBALS: @"?foo@C@pr21073_2@@QAEPAUA@2@XZ"
|
|
// GLOBALS: @"?foo@C@pr21073_2@@UAEPAU12@XZ"
|
|
}
|
|
|
|
namespace test3 {
|
|
struct A { virtual A *fn(); };
|
|
struct B : virtual A { virtual B *fn(); };
|
|
struct X : virtual B {};
|
|
struct Y : virtual B {};
|
|
struct C : X, Y {};
|
|
struct D : virtual B, virtual A, C {
|
|
D *fn();
|
|
D();
|
|
};
|
|
D::D() {}
|
|
|
|
// VFTABLES-LABEL: VFTable for 'test3::A' in 'test3::B' in 'test3::X' in 'test3::C' in 'test3::D' (3 entries).
|
|
// VFTABLES-NEXT: 0 | D *test3::D::fn()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct test3::A *'): vbase #1, 0 non-virtual]
|
|
// VFTABLES-NEXT: [this adjustment: vtordisp at -4, 0 non-virtual]
|
|
// VFTABLES-NEXT: 1 | D *test3::D::fn()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct test3::B *'): vbase #2, 0 non-virtual]
|
|
// VFTABLES-NEXT: [this adjustment: vtordisp at -4, 0 non-virtual]
|
|
// VFTABLES-NEXT: 2 | D *test3::D::fn()
|
|
// VFTABLES-NEXT: [return adjustment (to type 'struct test3::D *'): 0 non-virtual]
|
|
// VFTABLES-NEXT: [this adjustment: vtordisp at -4, 0 non-virtual]
|
|
|
|
// GLOBALS-LABEL: @"??_7D@test3@@6B@" = {{.*}} constant { [3 x ptr] }
|
|
// GLOBALS: @"?fn@D@test3@@$4PPPPPPPM@A@AEPAUA@2@XZ"
|
|
// GLOBALS: @"?fn@D@test3@@$4PPPPPPPM@A@AEPAUB@2@XZ"
|
|
// GLOBALS: @"?fn@D@test3@@$4PPPPPPPM@A@AEPAU12@XZ"
|
|
}
|
|
|
|
namespace pr34302 {
|
|
// C::f is lives in the vftable inside its virtual B subobject. In the MS ABI,
|
|
// covariant return type virtual methods extend vftables from virtual bases,
|
|
// even though that can make it impossible to implement certain diamond
|
|
// hierarchies correctly.
|
|
struct A { virtual ~A(); };
|
|
struct B : A { virtual B *f(); };
|
|
struct C : virtual B { C *f(); };
|
|
C c;
|
|
// VFTABLES-LABEL: VFTable indices for 'pr34302::C' (2 entries).
|
|
// VFTABLES-NEXT: -- accessible via vbtable index 1, vfptr at offset 0 --
|
|
// VFTABLES-NEXT: 0 | pr34302::C::~C() [vector deleting]
|
|
// VFTABLES-NEXT: 2 | C *pr34302::C::f()
|
|
}
|