Author: Erich Keane Date: 2026-09-04T15:58:28Z New Revision: 26d3c25b8184193e7952f14151245166fa010f62
URL: https://github.com/llvm/llvm-project/commit/26d3c25b8184193e7952f14151245166fa010f62 DIFF: https://github.com/llvm/llvm-project/commit/26d3c25b8184193e7952f14151245166fa010f62.diff LOG: [CIR] Implement 'vtable initialization' lowering (#220946) This showed up in a test suite, and is basically just ensuring that our vtable pointers are properly cleaned up during destruction. The entirety of the static functions (and the implementation) are near word-for-word copies of what classic codegen does. However, there ARE a few parts that are potentially untested(including strict-vtable-pointers which aren't implemented yet), but the implementation is put in place, as it is a mechanical copy/paste implementation. Note: Claude came up with additional test cases. Added: clang/test/CIR/CodeGen/destructor-vtable-reinit-strict.cpp clang/test/CIR/CodeGen/destructor-vtable-reinit.cpp Modified: clang/include/clang/CIR/MissingFeatures.h clang/lib/CIR/CodeGen/CIRGenFunction.cpp Removed: ################################################################################ diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 184cb833b3ffb..23e903f197cfa 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -303,7 +303,6 @@ struct MissingFeatures { static bool vaArgABILowering() { return false; } static bool vectorConstants() { return false; } static bool vlas() { return false; } - static bool vtableInitialization() { return false; } static bool vtableEmitMetadata() { return false; } static bool vtableRelativeLayout() { return false; } static bool weakRefReference() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 8301627ad8123..6ec51939e02dc 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -21,6 +21,7 @@ #include "clang/AST/GlobalDecl.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" #include "clang/CIR/MissingFeatures.h" +#include "clang/CodeGenUtils/CodeGenUtils.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/IR/FPEnv.h" @@ -972,10 +973,21 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) { case Dtor_Base: assert(body); + bool needsVTableInit = + !CodeGenUtils::canSkipVTablePointerInitialization(getContext(), dtor); + // Launder 'this' if necessary. + if (needsVTableInit && cgm.getCodeGenOpts().StrictVTablePointers && + cgm.getCodeGenOpts().OptimizationLevel > 0) { + cxxThisValue = cir::LaunderOp::create( + builder, getLoc(dtor->getBeginLoc()), loadCXXThis()); + } + // Enter the cleanup scopes for fields and non-virtual bases. enterDtorCleanups(dtor, Dtor_Base); - assert(!cir::MissingFeatures::vtableInitialization()); + // Initialize the vtable pointers before entering the body. + if (needsVTableInit) + initializeVTablePointers(getLoc(dtor->getBeginLoc()), dtor->getParent()); if (isTryBody) { cgm.errorNYI(dtor->getSourceRange(), "function-try-block destructor"); diff --git a/clang/test/CIR/CodeGen/destructor-vtable-reinit-strict.cpp b/clang/test/CIR/CodeGen/destructor-vtable-reinit-strict.cpp new file mode 100644 index 0000000000000..a3636a9217f83 --- /dev/null +++ b/clang/test/CIR/CodeGen/destructor-vtable-reinit-strict.cpp @@ -0,0 +1,46 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fstrict-vtable-pointers -O1 \ +// RUN: -fclangir -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 -fstrict-vtable-pointers -O1 \ +// RUN: -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM,LLVMCIR --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fstrict-vtable-pointers -O1 \ +// RUN: -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=LLVM,OGCG --input-file=%t.ll %s + +void some_function(); + +struct Base { + virtual ~Base(); +}; + +struct Derived : Base { + virtual ~Derived(); +}; + +Derived::~Derived() { some_function(); } + +// CIR-LABEL: cir.func {{.*}} @_ZN7DerivedD2Ev( +// CIR-NEXT: %[[THIS_ADDR:.*]] = cir.alloca "this" {{.*}} init +// CIR: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] +// CIR-NEXT: %[[LAUNDERED:.*]] = cir.launder %[[THIS]] +// CIR-NEXT: cir.cleanup.scope { +// CIR-NEXT: %[[DERIVED_VPTR:.*]] = cir.vtable.address_point(@_ZTV7Derived, address_point = <index = 0, offset = 2>) : !cir.vptr +// CIR-NEXT: %[[DERIVED_VPTR_ADDR:.*]] = cir.vtable.get_vptr %[[LAUNDERED]] : !cir.ptr<!rec_Derived> -> !cir.ptr<!cir.vptr> +// CIR-NEXT: cir.store{{.*}} %[[DERIVED_VPTR]], %[[DERIVED_VPTR_ADDR]] : !cir.vptr, !cir.ptr<!cir.vptr> +// CIR-NEXT: cir.call @_Z13some_functionv() +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[BASE_ADDR:.*]] = cir.base_class_addr %[[LAUNDERED]] : !cir.ptr<!rec_Derived> nonnull [0] -> !cir.ptr<!rec_Base> +// CIR-NEXT: cir.call @_ZN4BaseD2Ev(%[[BASE_ADDR]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.return + +// LLVM-LABEL: define{{.*}} void @_ZN7DerivedD2Ev( +// LLVM: %[[LAUNDERED:.*]] = {{.*}}call ptr @llvm.launder.invariant.group.p0(ptr {{.*}}) +// LLVMCIR-NEXT:store ptr getelementptr inbounds nuw (i8, ptr @_ZTV7Derived, i64 16), ptr %[[LAUNDERED]] +// OGCG-NEXT: store ptr getelementptr inbounds nuw inrange(-16, 16) (i8, ptr @_ZTV7Derived, i64 16), ptr %[[LAUNDERED]] +// LLVM: call void @_Z13some_functionv() +// LLVM: call void @_ZN4BaseD2Ev(ptr {{.*}}%[[LAUNDERED]]) +// LLVM: ret void diff --git a/clang/test/CIR/CodeGen/destructor-vtable-reinit.cpp b/clang/test/CIR/CodeGen/destructor-vtable-reinit.cpp new file mode 100644 index 0000000000000..887c22588d263 --- /dev/null +++ b/clang/test/CIR/CodeGen/destructor-vtable-reinit.cpp @@ -0,0 +1,179 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -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 -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM,LLVMCIR --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=LLVM,OGCG --input-file=%t.ll %s + +void some_function(); + +struct Base { + virtual ~Base(); +}; + +struct Derived : Base { + virtual ~Derived(); +}; + +Base::~Base() { some_function(); } + +// CIR-LABEL: cir.func {{.*}} @_ZN4BaseD2Ev( +// CIR-NEXT: %[[THIS_ADDR:.*]] = cir.alloca "this" {{.*}} init +// CIR: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] +// CIR-NEXT: %[[BASE_VPTR:.*]] = cir.vtable.address_point(@_ZTV4Base, address_point = <index = 0, offset = 2>) : !cir.vptr +// CIR-NEXT: %[[BASE_VPTR_ADDR:.*]] = cir.vtable.get_vptr %[[THIS]] : !cir.ptr<!rec_Base> -> !cir.ptr<!cir.vptr> +// CIR-NEXT: cir.store{{.*}} %[[BASE_VPTR]], %[[BASE_VPTR_ADDR]] : !cir.vptr, !cir.ptr<!cir.vptr> +// CIR-NEXT: cir.call @_Z13some_functionv() +// CIR-NEXT: cir.return + +// LLVM-LABEL: define{{.*}} void @_ZN4BaseD2Ev( +// LLVM: %[[THIS_ADDR:.*]] = alloca ptr +// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]] +// LLVMCIR-NEXT:store ptr getelementptr inbounds nuw (i8, ptr @_ZTV4Base, i64 16), ptr %[[THIS]] +// OGCG-NEXT: store ptr getelementptr inbounds inrange(-16, 16) ({ [4 x ptr] }, ptr @_ZTV4Base, i32 0, i32 0, i32 2), ptr %[[THIS]] +// LLVM-NEXT: call void @_Z13some_functionv() +// LLVM-NEXT: ret void + + +Derived::~Derived() { some_function(); } + +// CIR-LABEL: cir.func {{.*}} @_ZN7DerivedD2Ev( +// CIR-NEXT: %[[THIS_ADDR:.*]] = cir.alloca "this" {{.*}} init +// CIR: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] +// CIR-NEXT: cir.cleanup.scope { +// CIR-NEXT: %[[DERIVED_VPTR:.*]] = cir.vtable.address_point(@_ZTV7Derived, address_point = <index = 0, offset = 2>) : !cir.vptr +// CIR-NEXT: %[[DERIVED_VPTR_ADDR:.*]] = cir.vtable.get_vptr %[[THIS]] : !cir.ptr<!rec_Derived> -> !cir.ptr<!cir.vptr> +// CIR-NEXT: cir.store{{.*}} %[[DERIVED_VPTR]], %[[DERIVED_VPTR_ADDR]] : !cir.vptr, !cir.ptr<!cir.vptr> +// CIR-NEXT: cir.call @_Z13some_functionv() +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[BASE_ADDR:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_Derived> nonnull [0] -> !cir.ptr<!rec_Base> +// CIR-NEXT: cir.call @_ZN4BaseD2Ev(%[[BASE_ADDR]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.return + +// Cleanup scopes insert a bunch of empty blocks, so we can't use LLVM-NEXT as +// aggressively as I'd like. +// LLVM-LABEL: define{{.*}} void @_ZN7DerivedD2Ev( +// LLVM: %[[THIS_ADDR:.*]] = alloca ptr +// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]] +// LLVMCIR: store ptr getelementptr inbounds nuw (i8, ptr @_ZTV7Derived, i64 16), ptr %[[THIS]] +// OGCG: store ptr getelementptr inbounds inrange(-16, 16) ({ [4 x ptr] }, ptr @_ZTV7Derived, i32 0, i32 0, i32 2), ptr %[[THIS]] +// LLVM-NEXT: call void @_Z13some_functionv() +// LLVM: call void @_ZN4BaseD2Ev(ptr {{.*}}%[[THIS]]) +// LLVM: ret void + + +// A destructor of an effectively-final class never needs to reinitialize its +// vtable pointer, since it's already known to point at the class's own +// vtable. +struct FinalDerived final : Base { + virtual ~FinalDerived(); +}; + +FinalDerived::~FinalDerived() { some_function(); } + +// CIR-LABEL: cir.func {{.*}} @_ZN12FinalDerivedD2Ev( +// CIR-NEXT: %[[THIS_ADDR:.*]] = cir.alloca "this" {{.*}} init +// CIR: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] +// CIR-NEXT: cir.cleanup.scope { +// CIR-NEXT: cir.call @_Z13some_functionv() +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[BASE_ADDR:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_FinalDerived> nonnull [0] -> !cir.ptr<!rec_Base> +// CIR-NEXT: cir.call @_ZN4BaseD2Ev(%[[BASE_ADDR]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.return + +// LLVM-LABEL: define{{.*}} void @_ZN12FinalDerivedD2Ev( +// LLVM: %[[THIS_ADDR:.*]] = alloca ptr +// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]] +// LLVM-NOT: store ptr {{.*}}@_ZTV12FinalDerived +// LLVM: call void @_Z13some_functionv() +// LLVM: call void @_ZN4BaseD2Ev(ptr {{.*}}%[[THIS]]) +// LLVM: ret void + + +// A destructor with a trivial body (and no non-trivial field destructors) +// also never needs to reinitialize the vtable pointer. +struct TrivialDtor : Base { + virtual ~TrivialDtor(); +}; + +TrivialDtor::~TrivialDtor() {} + +// CIR-LABEL: cir.func {{.*}} @_ZN11TrivialDtorD2Ev( +// CIR-NEXT: %[[THIS_ADDR:.*]] = cir.alloca "this" {{.*}} init +// CIR: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] +// CIR-NEXT: cir.cleanup.scope { +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[BASE_ADDR:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_TrivialDtor> nonnull [0] -> !cir.ptr<!rec_Base> +// CIR-NEXT: cir.call @_ZN4BaseD2Ev(%[[BASE_ADDR]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.return + +// LLVM-LABEL: define{{.*}} void @_ZN11TrivialDtorD2Ev( +// LLVM: %[[THIS_ADDR:.*]] = alloca ptr +// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]] +// LLVM-NOT: store ptr {{.*}}@_ZTV11TrivialDtor +// LLVM: call void @_ZN4BaseD2Ev(ptr {{.*}}%[[THIS]]) +// LLVM: ret void + + +// A class with more than one non-virtual polymorphic base has more than one +// vtable pointer of its own to reinitialize. +struct Mother { + virtual ~Mother(); +}; +struct Father { + virtual ~Father(); +}; +struct MultiBase : Mother, Father { + virtual ~MultiBase(); +}; + +MultiBase::~MultiBase() { some_function(); } + +// CIR-LABEL: cir.func {{.*}} @_ZN9MultiBaseD2Ev( +// CIR-NEXT: %[[THIS_ADDR:.*]] = cir.alloca "this" {{.*}} init +// CIR: %[[THIS:.*]] = cir.load %[[THIS_ADDR]] +// CIR-NEXT: cir.cleanup.scope { +// CIR-NEXT: cir.cleanup.scope { +// CIR-NEXT: %[[MOTHER_VPTR:.*]] = cir.vtable.address_point(@_ZTV9MultiBase, address_point = <index = 0, offset = 2>) : !cir.vptr +// CIR-NEXT: %[[MOTHER_VPTR_ADDR:.*]] = cir.vtable.get_vptr %[[THIS]] : !cir.ptr<!rec_MultiBase> -> !cir.ptr<!cir.vptr> +// CIR-NEXT: cir.store{{.*}} %[[MOTHER_VPTR]], %[[MOTHER_VPTR_ADDR]] : !cir.vptr, !cir.ptr<!cir.vptr> +// CIR-NEXT: %[[FATHER_VPTR:.*]] = cir.vtable.address_point(@_ZTV9MultiBase, address_point = <index = 1, offset = 2>) : !cir.vptr +// CIR-NEXT: %[[FATHER_ADDR:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_MultiBase> nonnull [8] -> !cir.ptr<!rec_Father> +// CIR-NEXT: %[[FATHER_VPTR_ADDR:.*]] = cir.vtable.get_vptr %[[FATHER_ADDR]] : !cir.ptr<!rec_Father> -> !cir.ptr<!cir.vptr> +// CIR-NEXT: cir.store{{.*}} %[[FATHER_VPTR]], %[[FATHER_VPTR_ADDR]] : !cir.vptr, !cir.ptr<!cir.vptr> +// CIR-NEXT: cir.call @_Z13some_functionv() +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[FATHER_ADDR:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_MultiBase> nonnull [8] -> !cir.ptr<!rec_Father> +// CIR-NEXT: cir.call @_ZN6FatherD2Ev(%[[FATHER_ADDR]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.yield +// CIR-NEXT: } cleanup normal { +// CIR-NEXT: %[[MOTHER_ADDR:.*]] = cir.base_class_addr %[[THIS]] : !cir.ptr<!rec_MultiBase> nonnull [0] -> !cir.ptr<!rec_Mother> +// CIR-NEXT: cir.call @_ZN6MotherD2Ev(%[[MOTHER_ADDR]]) +// CIR-NEXT: cir.yield +// CIR-NEXT: } +// CIR-NEXT: cir.return + +// LLVM-LABEL: define{{.*}} void @_ZN9MultiBaseD2Ev( +// LLVM: %[[THIS_ADDR:.*]] = alloca ptr +// LLVM: %[[THIS:.*]] = load ptr, ptr %[[THIS_ADDR]] +// LLVMCIR: store ptr getelementptr inbounds nuw (i8, ptr @_ZTV9MultiBase, i64 16), ptr %[[THIS]] +// OGCG: store ptr getelementptr inbounds inrange(-16, 16) ({ [4 x ptr], [4 x ptr] }, ptr @_ZTV9MultiBase, i32 0, i32 0, i32 2), ptr %[[THIS]] +// LLVM: %[[FATHER_ADDR:.*]] = getelementptr {{.*}}i8, ptr %[[THIS]], i{{32|64}} 8 +// LLVMCIR: store ptr getelementptr inbounds nuw (i8, ptr @_ZTV9MultiBase, i64 48), ptr %[[FATHER_ADDR]] +// OGCG: store ptr getelementptr inbounds inrange(-16, 16) ({ [4 x ptr], [4 x ptr] }, ptr @_ZTV9MultiBase, i32 0, i32 1, i32 2), ptr %[[FATHER_ADDR]] +// LLVM: call void @_Z13some_functionv() +// LLVM: call void @_ZN6FatherD2Ev(ptr {{.*}}) +// LLVM: call void @_ZN6MotherD2Ev(ptr {{.*}}%[[THIS]]) +// LLVM: ret void _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
