Skip to content

Conversation

andykaylor
Copy link
Contributor

This change adds support for emitting multiple tables in a global vtable object to handle the case of multiple-inheritence.

This change adds support for emitting multiple tables in a global
vtable object to handle the case of multiple-inheritence.
@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Aug 22, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 22, 2025

@llvm/pr-subscribers-clangir

@llvm/pr-subscribers-clang

Author: Andy Kaylor (andykaylor)

Changes

This change adds support for emitting multiple tables in a global vtable object to handle the case of multiple-inheritence.


Full diff: https://github.com/llvm/llvm-project/pull/155027.diff

2 Files Affected:

  • (modified) clang/lib/CIR/CodeGen/CIRGenVTables.cpp (+20-23)
  • (added) clang/test/CIR/CodeGen/multi-vtable.cpp (+136)
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
index 438b483ccb9c2..28a630becc3dc 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
@@ -144,33 +144,30 @@ void CIRGenVTables::createVTableInitializer(cir::GlobalOp &vtableOp,
       layout.getAddressPointIndices();
   unsigned nextVTableThunkIndex = 0;
 
-  if (layout.getNumVTables() > 1)
-    cgm.errorNYI("emitVTableDefinitions: multiple vtables");
-
-  // We'll need a loop here to handle multiple vtables, but for now we only
-  // support one.
-  unsigned vtableIndex = 0;
-  size_t vtableStart = layout.getVTableOffset(vtableIndex);
-  size_t vtableEnd = vtableStart + layout.getVTableSize(vtableIndex);
-
-  // Build a ConstArrayAttr of the vtable components.
-  llvm::SmallVector<mlir::Attribute> components;
-  for (size_t componentIndex = vtableStart; componentIndex < vtableEnd;
-       ++componentIndex) {
-    components.push_back(
-        getVTableComponent(layout, componentIndex, rtti, nextVTableThunkIndex,
-                           addressPoints[vtableIndex], vtableHasLocalLinkage));
-  }
-
   mlir::MLIRContext *mlirContext = &cgm.getMLIRContext();
 
-  // Create a ConstArrayAttr to hold the components.
-  auto arr = cir::ConstArrayAttr::get(
-      cir::ArrayType::get(componentType, components.size()),
-      mlir::ArrayAttr::get(mlirContext, components));
+  SmallVector<mlir::Attribute> vtables;
+  for (unsigned vtableIndex = 0, endIndex = layout.getNumVTables();
+       vtableIndex != endIndex; ++vtableIndex) {
+    // Build a ConstArrayAttr of the vtable components.
+    size_t vtableStart = layout.getVTableOffset(vtableIndex);
+    size_t vtableEnd = vtableStart + layout.getVTableSize(vtableIndex);
+    llvm::SmallVector<mlir::Attribute> components;
+    for (size_t componentIndex = vtableStart; componentIndex < vtableEnd;
+         ++componentIndex) {
+      components.push_back(getVTableComponent(
+          layout, componentIndex, rtti, nextVTableThunkIndex,
+          addressPoints[vtableIndex], vtableHasLocalLinkage));
+    }
+    // Create a ConstArrayAttr to hold the components.
+    auto arr = cir::ConstArrayAttr::get(
+        cir::ArrayType::get(componentType, components.size()),
+        mlir::ArrayAttr::get(mlirContext, components));
+    vtables.push_back(arr);
+  }
 
   // Create a ConstRecordAttr to hold the component array.
-  const auto members = mlir::ArrayAttr::get(mlirContext, {arr});
+  const auto members = mlir::ArrayAttr::get(mlirContext, vtables);
   cir::ConstRecordAttr record = cgm.getBuilder().getAnonConstRecord(members);
 
   // Create a VTableAttr
diff --git a/clang/test/CIR/CodeGen/multi-vtable.cpp b/clang/test/CIR/CodeGen/multi-vtable.cpp
new file mode 100644
index 0000000000000..c127285800844
--- /dev/null
+++ b/clang/test/CIR/CodeGen/multi-vtable.cpp
@@ -0,0 +1,136 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -fno-rtti -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 -mconstructor-aliases -fno-rtti -fclangir -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 -mconstructor-aliases -fno-rtti -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+// Note: This test is using -fno-rtti so that we can delay implemntation of that handling.
+//       When rtti handling for vtables is implemented, that option should be removed.
+
+class Mother {
+public:
+  virtual void MotherKey();
+  void simple() { }
+  virtual void MotherNonKey() {}
+};
+
+class Father {
+public:
+  virtual void FatherKey();
+};
+
+class Child : public Mother, public Father {
+public:
+  void MotherKey() override;
+};
+
+void Mother::MotherKey() {}
+void Father::FatherKey() {}
+void Child::MotherKey() {}
+
+// CIR-DAG: [[MOTHER_VTABLE_TYPE:.*]] = !cir.record<struct  {!cir.array<!cir.ptr<!u8i> x 4>}>
+// CIR-DAG: [[FATHER_VTABLE_TYPE:.*]] = !cir.record<struct  {!cir.array<!cir.ptr<!u8i> x 3>}>
+// CIR-DAG: [[CHILD_VTABLE_TYPE:.*]] = !cir.record<struct  {!cir.array<!cir.ptr<!u8i> x 4>, !cir.array<!cir.ptr<!u8i> x 3>}>
+// CIR-DAG: !rec_Father = !cir.record<class "Father" {!cir.vptr}
+// CIR-DAG: !rec_Mother = !cir.record<class "Mother" {!cir.vptr}
+// CIR-DAG: !rec_Child = !cir.record<class "Child" {!rec_Mother, !rec_Father}
+
+// Mother vtable
+
+// CIR:      cir.global "private" external @_ZTV6Mother = #cir.vtable<{
+// CIR-SAME:      #cir.const_array<[
+// CIR-SAME:          #cir.ptr<null> : !cir.ptr<!u8i>,
+// CIR-SAME:          #cir.ptr<null> : !cir.ptr<!u8i>,
+// CIR-SAME:          #cir.global_view<@_ZN6Mother9MotherKeyEv> : !cir.ptr<!u8i>,
+// CIR-SAME:          #cir.global_view<@_ZN6Mother12MotherNonKeyEv> : !cir.ptr<!u8i>
+// CIR-SAME:      ]> : !cir.array<!cir.ptr<!u8i> x 4>
+// CIR-SAME: }> : [[MOTHER_VTABLE_TYPE]]
+
+// LLVM:      @_ZTV6Mother = global { [4 x ptr] } {
+// LLVM-SAME:     [4 x ptr] [
+// LLVM-SAME:         ptr null,
+// LLVM-SAME:         ptr null,
+// LLVM-SAME:         ptr @_ZN6Mother9MotherKeyEv,
+// LLVM-SAME:         ptr @_ZN6Mother12MotherNonKeyEv
+// LLVM-SAME:     ]
+// LLVM-SAME: }
+
+// OGCG:      @_ZTV6Mother = unnamed_addr constant { [4 x ptr] } {
+// OGCG-SAME:     [4 x ptr] [
+// OGCG-SAME:         ptr null,
+// OGCG-SAME:         ptr null,
+// OGCG-SAME:         ptr @_ZN6Mother9MotherKeyEv,
+// OGCG-SAME:         ptr @_ZN6Mother12MotherNonKeyEv
+// OGCG-SAME:     ]
+// OGCG-SAME: }
+
+// Father vtable
+
+// CIR:      cir.global "private" external @_ZTV6Father = #cir.vtable<{
+// CIR-SAME:     #cir.const_array<[
+// CIR-SAME:         #cir.ptr<null> : !cir.ptr<!u8i>,
+// CIR-SAME:         #cir.ptr<null> : !cir.ptr<!u8i>,
+// CIR-SAME:         #cir.global_view<@_ZN6Father9FatherKeyEv> : !cir.ptr<!u8i>
+// CIR-SAME:     ]> : !cir.array<!cir.ptr<!u8i> x 3>
+// CIR-SAME: }> : [[FATHER_VTABLE_TYPE]]
+
+// LLVM:      @_ZTV6Father = global { [3 x ptr] } {
+// LLVM-SAME:     [3 x ptr] [
+// LLVM-SAME:         ptr null,
+// LLVM-SAME:         ptr null,
+// LLVM-SAME:         ptr @_ZN6Father9FatherKeyEv
+// LLVM-SAME:     ]
+// LLVM-SAME: }
+
+// OGCG:      @_ZTV6Father = unnamed_addr constant { [3 x ptr] } {
+// OGCG-SAME:     [3 x ptr] [
+// OGCG-SAME:         ptr null,
+// OGCG-SAME:         ptr null,
+// OGCG-SAME:         ptr @_ZN6Father9FatherKeyEv
+// OGCG-SAME:     ]
+// OGCG-SAME: }
+
+// Child vtable
+
+// CIR:      cir.global "private" external @_ZTV5Child = #cir.vtable<{
+// CIR-SAME:     #cir.const_array<[
+// CIR-SAME:         #cir.ptr<null> : !cir.ptr<!u8i>,
+// CIR-SAME:         #cir.ptr<null> : !cir.ptr<!u8i>,
+// CIR-SAME:         #cir.global_view<@_ZN5Child9MotherKeyEv> : !cir.ptr<!u8i>,
+// CIR-SAME:         #cir.global_view<@_ZN6Mother12MotherNonKeyEv> : !cir.ptr<!u8i>
+// CIR-SAME:     ]> : !cir.array<!cir.ptr<!u8i> x 4>,
+// CIR-SAME:     #cir.const_array<[
+// CIR-SAME:         #cir.ptr<-8 : i64> : !cir.ptr<!u8i>,
+// CIR-SAME:         #cir.ptr<null> : !cir.ptr<!u8i>,
+// CIR-SAME:         #cir.global_view<@_ZN6Father9FatherKeyEv> : !cir.ptr<!u8i>
+// CIR-SAME:     ]> : !cir.array<!cir.ptr<!u8i> x 3>
+// CIR-SAME: }> : [[CHILD_VTABLE_TYPE]]
+
+// LLVM:      @_ZTV5Child = global { [4 x ptr], [3 x ptr] } {
+// LLVM-SAME:     [4 x ptr] [
+// LLVM-SAME:         ptr null,
+// LLVM-SAME:         ptr null,
+// LLVM-SAME:         ptr @_ZN5Child9MotherKeyEv,
+// LLVM-SAME:         ptr @_ZN6Mother12MotherNonKeyEv
+// LLVM-SAME:     ],
+// LLVM-SAME:     [3 x ptr] [
+// LLVM-SAME:         ptr inttoptr (i64 -8 to ptr),
+// LLVM-SAME:         ptr null,
+// LLVM-SAME:         ptr @_ZN6Father9FatherKeyEv
+// LLVM-SAME:     ]
+// LLVM-SAME: }
+
+// OGCG:      @_ZTV5Child = unnamed_addr constant { [4 x ptr], [3 x ptr] } {
+// OGCG-SAME:     [4 x ptr] [
+// OGCG-SAME:         ptr null,
+// OGCG-SAME:         ptr null,
+// OGCG-SAME:         ptr @_ZN5Child9MotherKeyEv,
+// OGCG-SAME:         ptr @_ZN6Mother12MotherNonKeyEv
+// OGCG-SAME:     ],
+// OGCG-SAME:     [3 x ptr] [
+// OGCG-SAME:         ptr inttoptr (i64 -8 to ptr),
+// OGCG-SAME:         ptr null,
+// OGCG-SAME:         ptr @_ZN6Father9FatherKeyEv
+// OGCG-SAME:     ]
+// OGCG-SAME: }

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM w/ minor suggestion

cir::ArrayType::get(componentType, components.size()),
mlir::ArrayAttr::get(mlirContext, components));
SmallVector<mlir::Attribute> vtables;
for (unsigned vtableIndex = 0, endIndex = layout.getNumVTables();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we llvm::enumerate here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I hadn't noticed that was a possibility here.

// Build a ConstArrayAttr of the vtable components.
size_t vtableStart = layout.getVTableOffset(vtableIndex);
size_t vtableEnd = vtableStart + layout.getVTableSize(vtableIndex);
llvm::SmallVector<mlir::Attribute> components;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could reserve capacity here, or use llvm::SmallVector<mlir::Attribute, num_components> since the size is known from vtableStart and vtableEnd?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

llvm::SmallVector<mlir::Attribute, num_components>

THAT wouldn't work, since vtableStart/vtableEnd aren't constant expressions. But a .reserve isn't a terrible idea. That said, our growth rate and slab allocator makes reserve not particularly valuable for SmallVector.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem like there's much downside to a reserve, maybe a few extra instructions in cases where we wouldn't need to grow the vector, and it might save us a memcpy in cases where the vector does need to grow.

size_t vtableStart = layout.getVTableOffset(vtableIndex);
size_t vtableEnd = vtableStart + layout.getVTableSize(vtableIndex);
llvm::SmallVector<mlir::Attribute> components;
for (size_t componentIndex = vtableStart; componentIndex < vtableEnd;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (size_t componentIndex = vtableStart; componentIndex < vtableEnd;
for (size_t componentIndex : llvm::seq(vtableStart, vtableEnd))

I think llvm::seq makes this loop shorter.

@andykaylor andykaylor merged commit d467bd9 into llvm:main Aug 25, 2025
9 checks passed
@andykaylor andykaylor deleted the cir-multi-vtable branch August 25, 2025 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants