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

Reply via email to