[clang] 26d3c25 - [CIR] Implement 'vtable initialization' lowering (#220946)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 4 08:58:34 PDT 2026
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
More information about the cfe-commits
mailing list