[clang] [CIR] Add support for emitting vtables (PR #154808)
Andy Kaylor via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 22 11:16:38 PDT 2025
https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/154808
>From bedcef440bc14e523d3145b7683ce8a5cdfe4661 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Thu, 31 Jul 2025 09:47:08 -0700
Subject: [PATCH 1/2] [CIR] Add support for emitting vtables
This adds a simplified version of the code to emit vtables. It does not
yet handle RTTI or cases that require multiple vtables.
---
clang/include/clang/CIR/MissingFeatures.h | 1 +
clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 4 +
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 15 ++
clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 63 ++++++
clang/lib/CIR/CodeGen/CIRGenModule.cpp | 14 +-
clang/lib/CIR/CodeGen/CIRGenModule.h | 17 ++
clang/lib/CIR/CodeGen/CIRGenTypes.h | 7 +
clang/lib/CIR/CodeGen/CIRGenVTables.cpp | 208 +++++++++++++++++-
clang/lib/CIR/CodeGen/CIRGenVTables.h | 23 +-
clang/lib/CIR/CodeGen/CIRGenerator.cpp | 2 +-
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 27 ++-
clang/test/CIR/CodeGen/vtable-emission.cpp | 38 ++++
12 files changed, 410 insertions(+), 9 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/vtable-emission.cpp
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 49c66a40e47b6..e2326b1031765 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -279,6 +279,7 @@ struct MissingFeatures {
static bool appleKext() { return false; }
static bool dtorCleanups() { return false; }
static bool vtableInitialization() { return false; }
+ static bool vtableEmitMetadata() { return false; }
static bool vtableRelativeLayout() { return false; }
static bool msvcBuiltins() { return false; }
static bool vaArgABILowering() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
index 3f1cb8363a556..b5f2e1a067274 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
@@ -95,6 +95,10 @@ class CIRGenCXXABI {
isVirtualOffsetNeededForVTableField(CIRGenFunction &cgf,
CIRGenFunction::VPtr vptr) = 0;
+ /// Emits the VTable definitions required for the given record type.
+ virtual void emitVTableDefinitions(CIRGenVTables &cgvt,
+ const CXXRecordDecl *rd) = 0;
+
/// Returns true if the given destructor type should be emitted as a linkonce
/// delegating thunk, regardless of whether the dtor is defined in this TU or
/// not.
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 6d749940fa128..8a15e5f96aea2 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -42,6 +42,11 @@ CIRGenFunctionInfo::create(CanQualType resultType,
return fi;
}
+cir::FuncType CIRGenTypes::getFunctionType(GlobalDecl gd) {
+ const CIRGenFunctionInfo &fi = arrangeGlobalDeclaration(gd);
+ return getFunctionType(fi);
+}
+
cir::FuncType CIRGenTypes::getFunctionType(const CIRGenFunctionInfo &info) {
mlir::Type resultType = convertType(info.getReturnType());
SmallVector<mlir::Type, 8> argTypes;
@@ -55,6 +60,16 @@ cir::FuncType CIRGenTypes::getFunctionType(const CIRGenFunctionInfo &info) {
info.isVariadic());
}
+cir::FuncType CIRGenTypes::getFunctionTypeForVTable(GlobalDecl gd) {
+ const CXXMethodDecl *md = cast<CXXMethodDecl>(gd.getDecl());
+ const FunctionProtoType *fpt = md->getType()->getAs<FunctionProtoType>();
+
+ if (!isFuncTypeConvertible(fpt))
+ cgm.errorNYI("getFunctionTypeForVTable: non-convertible function type");
+
+ return getFunctionType(gd);
+}
+
CIRGenCallee CIRGenCallee::prepareConcreteCallee(CIRGenFunction &cgf) const {
if (isVirtual()) {
const CallExpr *ce = getVirtualCallExpr();
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index 347656b5f6488..aaf7dc767d888 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -81,6 +81,8 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
CIRGenFunction &cgf, const clang::CXXRecordDecl *vtableClass,
clang::BaseSubobject base,
const clang::CXXRecordDecl *nearestVBase) override;
+ void emitVTableDefinitions(CIRGenVTables &cgvt,
+ const CXXRecordDecl *rd) override;
bool doStructorsInitializeVPtrs(const CXXRecordDecl *vtableClass) override {
return true;
@@ -270,6 +272,67 @@ bool CIRGenItaniumCXXABI::needsVTTParameter(GlobalDecl gd) {
return false;
}
+void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &cgvt,
+ const CXXRecordDecl *rd) {
+ cir::GlobalOp vtable = getAddrOfVTable(rd, CharUnits());
+ if (vtable.hasInitializer())
+ return;
+
+ ItaniumVTableContext &vtContext = cgm.getItaniumVTableContext();
+ const VTableLayout &vtLayout = vtContext.getVTableLayout(rd);
+ cir::GlobalLinkageKind linkage = cgm.getVTableLinkage(rd);
+ mlir::Attribute rtti =
+ cgm.getAddrOfRTTIDescriptor(cgm.getLoc(rd->getBeginLoc()),
+ cgm.getASTContext().getCanonicalTagType(rd));
+
+ // Classic codegen uses ConstantInitBuilder here, which is a very general
+ // and feature-rich class to generate initializers for global values.
+ // For now, this is using a simpler approach to create the initializer in CIR.
+ cgvt.createVTableInitializer(vtable, vtLayout, rtti,
+ cir::isLocalLinkage(linkage));
+
+ // Set the correct linkage.
+ vtable.setLinkage(linkage);
+
+ if (cgm.supportsCOMDAT() && cir::isWeakForLinker(linkage))
+ vtable.setComdat(true);
+
+ // Set the right visibility.
+ cgm.setGVProperties(vtable, rd);
+
+ // If this is the magic class __cxxabiv1::__fundamental_type_info,
+ // we will emit the typeinfo for the fundamental types. This is the
+ // same behaviour as GCC.
+ const DeclContext *DC = rd->getDeclContext();
+ if (rd->getIdentifier() &&
+ rd->getIdentifier()->isStr("__fundamental_type_info") &&
+ isa<NamespaceDecl>(DC) && cast<NamespaceDecl>(DC)->getIdentifier() &&
+ cast<NamespaceDecl>(DC)->getIdentifier()->isStr("__cxxabiv1") &&
+ DC->getParent()->isTranslationUnit()) {
+ cgm.errorNYI(rd->getSourceRange(),
+ "emitVTableDefinitions: __fundamental_type_info");
+ }
+
+ auto vtableAsGlobalValue = dyn_cast<cir::CIRGlobalValueInterface>(*vtable);
+ assert(vtableAsGlobalValue && "VTable must support CIRGlobalValueInterface");
+ // Always emit type metadata on non-available_externally definitions, and on
+ // available_externally definitions if we are performing whole program
+ // devirtualization. For WPD we need the type metadata on all vtable
+ // definitions to ensure we associate derived classes with base classes
+ // defined in headers but with a strong definition only in a shared
+ // library.
+ assert(!cir::MissingFeatures::vtableEmitMetadata());
+ if (cgm.getCodeGenOpts().WholeProgramVTables) {
+ cgm.errorNYI(rd->getSourceRange(),
+ "emitVTableDefinitions: WholeProgramVTables");
+ }
+
+ assert(!cir::MissingFeatures::vtableRelativeLayout());
+ if (vtContext.isRelativeLayout()) {
+ cgm.errorNYI(rd->getSourceRange(), "vtableRelativeLayout");
+ }
+}
+
void CIRGenItaniumCXXABI::emitDestructorCall(
CIRGenFunction &cgf, const CXXDestructorDecl *dd, CXXDtorType type,
bool forVirtualBase, bool delegating, Address thisAddr, QualType thisTy) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index a557d2aae9dd9..46bca51767c02 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -845,7 +845,7 @@ void CIRGenModule::emitGlobalDefinition(clang::GlobalDecl gd,
emitGlobalFunctionDefinition(gd, op);
if (method->isVirtual())
- errorNYI(method->getSourceRange(), "virtual member function");
+ getVTables().emitThunks(gd);
return;
}
@@ -2151,6 +2151,18 @@ bool CIRGenModule::verifyModule() const {
return mlir::verify(theModule).succeeded();
}
+mlir::Attribute CIRGenModule::getAddrOfRTTIDescriptor(mlir::Location loc,
+ QualType ty, bool forEh) {
+ // Return a bogus pointer if RTTI is disabled, unless it's for EH.
+ // FIXME: should we even be calling this method if RTTI is disabled
+ // and it's not for EH?
+ if (!shouldEmitRTTI(forEh))
+ return builder.getConstNullPtrAttr(builder.getUInt8PtrTy());
+
+ errorNYI(loc, "getAddrOfRTTIDescriptor");
+ return mlir::Attribute();
+}
+
// TODO(cir): this can be shared with LLVM codegen.
CharUnits CIRGenModule::computeNonVirtualBaseClassOffset(
const CXXRecordDecl *derivedClass,
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 128e2af5e1126..d90baa55d0b5c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -190,6 +190,16 @@ class CIRGenModule : public CIRGenTypeCache {
mlir::Location loc, llvm::StringRef name, mlir::Type ty,
cir::GlobalLinkageKind linkage, clang::CharUnits alignment);
+ void emitVTable(const CXXRecordDecl *rd);
+
+ /// Return the appropriate linkage for the vtable, VTT, and type information
+ /// of the given class.
+ cir::GlobalLinkageKind getVTableLinkage(const CXXRecordDecl *rd);
+
+ /// Get the address of the RTTI descriptor for the given type.
+ mlir::Attribute getAddrOfRTTIDescriptor(mlir::Location loc, QualType ty,
+ bool forEH = false);
+
/// Return a constant array for the given string.
mlir::Attribute getConstantArrayFromStringLiteral(const StringLiteral *e);
@@ -290,6 +300,13 @@ class CIRGenModule : public CIRGenTypeCache {
getAddrOfGlobal(clang::GlobalDecl gd,
ForDefinition_t isForDefinition = NotForDefinition);
+ // Return whether RTTI information should be emitted for this target.
+ bool shouldEmitRTTI(bool forEH = false) {
+ return (forEH || getLangOpts().RTTI) && !getLangOpts().CUDAIsDevice &&
+ !(getLangOpts().OpenMP && getLangOpts().OpenMPIsTargetDevice &&
+ getTriple().isNVPTX());
+ }
+
/// Emit type info if type of an expression is a variably modified
/// type. Also emit proper debug info for cast types.
void emitExplicitCastExprType(const ExplicitCastExpr *e,
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.h b/clang/lib/CIR/CodeGen/CIRGenTypes.h
index c2813d79bf63b..7af0d956e7d56 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.h
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.h
@@ -130,6 +130,13 @@ class CIRGenTypes {
/// Get the CIR function type for \arg Info.
cir::FuncType getFunctionType(const CIRGenFunctionInfo &info);
+ cir::FuncType getFunctionType(clang::GlobalDecl gd);
+
+ /// Get the CIR function type for use in a vtable, given a CXXMethodDecl. If
+ /// the method has an incomplete return type, and/or incomplete argument
+ /// types, this will return the opaque type.
+ cir::FuncType getFunctionTypeForVTable(clang::GlobalDecl gd);
+
// The arrangement methods are split into three families:
// - those meant to drive the signature and prologue/epilogue
// of a function declaration or definition,
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
index fdd1a6e3f57ef..dec73ba793b1d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
@@ -11,6 +11,8 @@
//===----------------------------------------------------------------------===//
#include "CIRGenVTables.h"
+
+#include "CIRGenCXXABI.h"
#include "CIRGenModule.h"
#include "mlir/IR/Types.h"
#include "clang/AST/VTableBuilder.h"
@@ -33,9 +35,9 @@ mlir::Type CIRGenVTables::getVTableComponentType() {
return cgm.getVTableComponentType();
}
-mlir::Type CIRGenVTables::getVTableType(const VTableLayout &layout) {
+cir::RecordType CIRGenVTables::getVTableType(const VTableLayout &layout) {
SmallVector<mlir::Type, 4> tys;
- auto componentType = getVTableComponentType();
+ mlir::Type componentType = getVTableComponentType();
for (unsigned i = 0, e = layout.getNumVTables(); i != e; ++i)
tys.push_back(cir::ArrayType::get(componentType, layout.getVTableSize(i)));
@@ -43,3 +45,205 @@ mlir::Type CIRGenVTables::getVTableType(const VTableLayout &layout) {
// AST nodes?
return cgm.getBuilder().getAnonRecordTy(tys, /*incomplete=*/false);
}
+
+/// This is a callback from Sema to tell us that a particular vtable is
+/// required to be emitted in this translation unit.
+///
+/// This is only called for vtables that _must_ be emitted (mainly due to key
+/// functions). For weak vtables, CodeGen tracks when they are needed and
+/// emits them as-needed.
+void CIRGenModule::emitVTable(const CXXRecordDecl *rd) {
+ vtables.generateClassData(rd);
+}
+
+void CIRGenVTables::generateClassData(const CXXRecordDecl *rd) {
+ assert(!cir::MissingFeatures::generateDebugInfo());
+
+ if (rd->getNumVBases())
+ cgm.errorNYI(rd->getSourceRange(), "emitVirtualInheritanceTables");
+
+ cgm.getCXXABI().emitVTableDefinitions(*this, rd);
+}
+
+mlir::Attribute CIRGenVTables::getVTableComponent(
+ const VTableLayout &layout, unsigned componentIndex, mlir::Attribute rtti,
+ unsigned &nextVTableThunkIndex, unsigned vtableAddressPoint,
+ bool vtableHasLocalLinkage) {
+ auto &component = layout.vtable_components()[componentIndex];
+
+ CIRGenBuilderTy builder = cgm.getBuilder();
+
+ assert(!cir::MissingFeatures::vtableRelativeLayout());
+
+ switch (component.getKind()) {
+ case VTableComponent::CK_VCallOffset:
+ cgm.errorNYI("getVTableComponent: VCallOffset");
+ return mlir::Attribute();
+ case VTableComponent::CK_VBaseOffset:
+ cgm.errorNYI("getVTableComponent: VBaseOffset");
+ return mlir::Attribute();
+ case VTableComponent::CK_CompleteDtorPointer:
+ cgm.errorNYI("getVTableComponent: CompleteDtorPointer");
+ return mlir::Attribute();
+ case VTableComponent::CK_DeletingDtorPointer:
+ cgm.errorNYI("getVTableComponent: DeletingDtorPointer");
+ return mlir::Attribute();
+ case VTableComponent::CK_UnusedFunctionPointer:
+ cgm.errorNYI("getVTableComponent: UnusedFunctionPointer");
+ return mlir::Attribute();
+
+ case VTableComponent::CK_OffsetToTop:
+ return builder.getConstPtrAttr(builder.getUInt8PtrTy(),
+ component.getOffsetToTop().getQuantity());
+
+ case VTableComponent::CK_RTTI:
+ assert((mlir::isa<cir::GlobalViewAttr>(rtti) ||
+ mlir::isa<cir::ConstPtrAttr>(rtti)) &&
+ "expected GlobalViewAttr or ConstPtrAttr");
+ return rtti;
+
+ case VTableComponent::CK_FunctionPointer: {
+ GlobalDecl gd = component.getGlobalDecl();
+
+ assert(!cir::MissingFeatures::cudaSupport());
+
+ cir::FuncOp fnPtr;
+ if (cast<CXXMethodDecl>(gd.getDecl())->isPureVirtual()) {
+ cgm.errorNYI("getVTableComponent: CK_FunctionPointer: pure virtual");
+ return mlir::Attribute();
+ } else if (cast<CXXMethodDecl>(gd.getDecl())->isDeleted()) {
+ cgm.errorNYI("getVTableComponent: CK_FunctionPointer: deleted virtual");
+ return mlir::Attribute();
+ } else if (nextVTableThunkIndex < layout.vtable_thunks().size() &&
+ layout.vtable_thunks()[nextVTableThunkIndex].first ==
+ componentIndex) {
+ cgm.errorNYI("getVTableComponent: CK_FunctionPointer: thunk");
+ return mlir::Attribute();
+ } else {
+ // Otherwise we can use the method definition directly.
+ cir::FuncType fnTy = cgm.getTypes().getFunctionTypeForVTable(gd);
+ fnPtr = cgm.getAddrOfFunction(gd, fnTy, /*ForVTable=*/true);
+ }
+
+ return cir::GlobalViewAttr::get(
+ builder.getUInt8PtrTy(),
+ mlir::FlatSymbolRefAttr::get(fnPtr.getSymNameAttr()));
+ }
+ }
+
+ llvm_unreachable("Unexpected vtable component kind");
+}
+
+void CIRGenVTables::createVTableInitializer(cir::GlobalOp &vtableOp,
+ const clang::VTableLayout &layout,
+ mlir::Attribute rtti,
+ bool vtableHasLocalLinkage) {
+ mlir::Type componentType = getVTableComponentType();
+
+ const llvm::SmallVector<unsigned, 4> &addressPoints =
+ 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, 4> 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));
+
+ // Create a ConstRecordAttr to hold the component array.
+ const auto members = mlir::ArrayAttr::get(mlirContext, {arr});
+ cir::ConstRecordAttr record = cgm.getBuilder().getAnonConstRecord(members);
+
+ // Create a VTableAttr
+ auto vtableAttr = cir::VTableAttr::get(record.getType(), record.getMembers());
+
+ // Add the vtable initializer to the vtable global op.
+ cgm.setInitializer(vtableOp, vtableAttr);
+}
+
+/// Compute the required linkage of the vtable for the given class.
+///
+/// Note that we only call this at the end of the translation unit.
+cir::GlobalLinkageKind CIRGenModule::getVTableLinkage(const CXXRecordDecl *rd) {
+ if (!rd->isExternallyVisible())
+ return cir::GlobalLinkageKind::InternalLinkage;
+
+ // We're at the end of the translation unit, so the current key
+ // function is fully correct.
+ const CXXMethodDecl *keyFunction = astContext.getCurrentKeyFunction(rd);
+ if (keyFunction && !rd->hasAttr<DLLImportAttr>()) {
+ // If this class has a key function, use that to determine the
+ // linkage of the vtable.
+ const FunctionDecl *def = nullptr;
+ if (keyFunction->hasBody(def))
+ keyFunction = cast<CXXMethodDecl>(def);
+
+ // All of the cases below do something different with AppleKext enabled.
+ assert(!cir::MissingFeatures::appleKext());
+ switch (keyFunction->getTemplateSpecializationKind()) {
+ case TSK_Undeclared:
+ case TSK_ExplicitSpecialization:
+ assert(
+ (def || codeGenOpts.OptimizationLevel > 0 ||
+ codeGenOpts.getDebugInfo() != llvm::codegenoptions::NoDebugInfo) &&
+ "Shouldn't query vtable linkage without key function, "
+ "optimizations, or debug info");
+ if (!def && codeGenOpts.OptimizationLevel > 0)
+ return cir::GlobalLinkageKind::AvailableExternallyLinkage;
+
+ if (keyFunction->isInlined())
+ return !astContext.getLangOpts().AppleKext
+ ? cir::GlobalLinkageKind::LinkOnceODRLinkage
+ : cir::GlobalLinkageKind::InternalLinkage;
+ return cir::GlobalLinkageKind::ExternalLinkage;
+
+ case TSK_ImplicitInstantiation:
+ return cir::GlobalLinkageKind::LinkOnceODRLinkage;
+
+ case TSK_ExplicitInstantiationDefinition:
+ return cir::GlobalLinkageKind::WeakODRLinkage;
+
+ case TSK_ExplicitInstantiationDeclaration:
+ llvm_unreachable("Should not have been asked to emit this");
+ }
+ }
+
+ errorNYI(rd->getSourceRange(), "getVTableLinkage: no key function");
+ return cir::GlobalLinkageKind::ExternalLinkage;
+}
+
+void CIRGenVTables::emitThunks(GlobalDecl gd) {
+ const CXXMethodDecl *md =
+ cast<CXXMethodDecl>(gd.getDecl())->getCanonicalDecl();
+
+ // We don't need to generate thunks for the base destructor.
+ if (isa<CXXDestructorDecl>(md) && gd.getDtorType() == Dtor_Base)
+ return;
+
+ const VTableContextBase::ThunkInfoVectorTy *thunkInfoVector =
+ vtContext->getThunkInfo(gd);
+
+ if (!thunkInfoVector)
+ return;
+
+ cgm.errorNYI(md->getSourceRange(), "emitThunks");
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.h b/clang/lib/CIR/CodeGen/CIRGenVTables.h
index 66318c5f2393a..518d7d78f1737 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.h
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.h
@@ -16,6 +16,7 @@
#include "mlir/IR/Types.h"
#include "clang/AST/GlobalDecl.h"
#include "clang/AST/VTableBuilder.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
namespace clang {
class CXXRecordDecl;
@@ -29,11 +30,23 @@ class CIRGenVTables {
clang::VTableContextBase *vtContext;
+ mlir::Attribute
+ getVTableComponent(const VTableLayout &layout, unsigned componentIndex,
+ mlir::Attribute rtti, unsigned &nextVTableThunkIndex,
+ unsigned vtableAddressPoint, bool vtableHasLocalLinkage);
+
mlir::Type getVTableComponentType();
public:
CIRGenVTables(CIRGenModule &cgm);
+ /// Add vtable components for the given vtable layout to the given
+ /// global initializer.
+ void createVTableInitializer(cir::GlobalOp &vtable,
+ const clang::VTableLayout &layout,
+ mlir::Attribute rtti,
+ bool vtableHasLocalLinkage);
+
clang::ItaniumVTableContext &getItaniumVTableContext() {
return *llvm::cast<clang::ItaniumVTableContext>(vtContext);
}
@@ -42,10 +55,18 @@ class CIRGenVTables {
return *llvm::cast<clang::ItaniumVTableContext>(vtContext);
}
+ /// Emit the associated thunks for the given global decl.
+ void emitThunks(GlobalDecl gd);
+
+ /// Generate all the class data required to be generated upon definition of a
+ /// KeyFunction. This includes the vtable, the RTTI data structure (if RTTI
+ /// is enabled) and the VTT (if the class has virtual bases).
+ void generateClassData(const CXXRecordDecl *rd);
+
/// Returns the type of a vtable with the given layout. Normally a struct of
/// arrays of pointers, with one struct element for each vtable in the vtable
/// group.
- mlir::Type getVTableType(const clang::VTableLayout &layout);
+ cir::RecordType getVTableType(const clang::VTableLayout &layout);
};
} // namespace clang::CIRGen
diff --git a/clang/lib/CIR/CodeGen/CIRGenerator.cpp b/clang/lib/CIR/CodeGen/CIRGenerator.cpp
index fb013d1532689..aa4d9eba35c04 100644
--- a/clang/lib/CIR/CodeGen/CIRGenerator.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenerator.cpp
@@ -177,5 +177,5 @@ void CIRGenerator::HandleVTable(CXXRecordDecl *rd) {
if (diags.hasErrorOccurred())
return;
- cgm->errorNYI(rd->getSourceRange(), "HandleVTable");
+ cgm->emitVTable(rd);
}
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 9972d7612105d..9f0e4e6ecb8ce 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -202,8 +202,8 @@ class CIRAttrToValue {
return llvm::TypeSwitch<mlir::Attribute, mlir::Value>(attr)
.Case<cir::IntAttr, cir::FPAttr, cir::ConstComplexAttr,
cir::ConstArrayAttr, cir::ConstRecordAttr, cir::ConstVectorAttr,
- cir::ConstPtrAttr, cir::GlobalViewAttr, cir::ZeroAttr>(
- [&](auto attrT) { return visitCirAttr(attrT); })
+ cir::ConstPtrAttr, cir::GlobalViewAttr, cir::VTableAttr,
+ cir::ZeroAttr>([&](auto attrT) { return visitCirAttr(attrT); })
.Default([&](auto attrT) { return mlir::Value(); });
}
@@ -215,6 +215,7 @@ class CIRAttrToValue {
mlir::Value visitCirAttr(cir::ConstRecordAttr attr);
mlir::Value visitCirAttr(cir::ConstVectorAttr attr);
mlir::Value visitCirAttr(cir::GlobalViewAttr attr);
+ mlir::Value visitCirAttr(cir::VTableAttr attr);
mlir::Value visitCirAttr(cir::ZeroAttr attr);
private:
@@ -500,6 +501,21 @@ mlir::Value CIRAttrToValue::visitCirAttr(cir::GlobalViewAttr globalAttr) {
llvm_unreachable("Expecting pointer or integer type for GlobalViewAttr");
}
+// VTableAttr visitor.
+mlir::Value CIRAttrToValue::visitCirAttr(cir::VTableAttr vtableArr) {
+ mlir::Type llvmTy = converter->convertType(vtableArr.getType());
+ mlir::Location loc = parentOp->getLoc();
+ mlir::Value result = mlir::LLVM::UndefOp::create(rewriter, loc, llvmTy);
+
+ for (auto [idx, elt] : llvm::enumerate(vtableArr.getData())) {
+ mlir::Value init = visit(elt);
+ result =
+ mlir::LLVM::InsertValueOp::create(rewriter, loc, result, init, idx);
+ }
+
+ return result;
+}
+
/// ZeroAttr visitor.
mlir::Value CIRAttrToValue::visitCirAttr(cir::ZeroAttr attr) {
mlir::Location loc = parentOp->getLoc();
@@ -1569,7 +1585,7 @@ CIRToLLVMGlobalOpLowering::matchAndRewriteRegionInitializedGlobal(
// TODO: Generalize this handling when more types are needed here.
assert((isa<cir::ConstArrayAttr, cir::ConstRecordAttr, cir::ConstVectorAttr,
cir::ConstPtrAttr, cir::ConstComplexAttr, cir::GlobalViewAttr,
- cir::ZeroAttr>(init)));
+ cir::VTableAttr, cir::ZeroAttr>(init)));
// TODO(cir): once LLVM's dialect has proper equivalent attributes this
// should be updated. For now, we use a custom op to initialize globals
@@ -1624,7 +1640,7 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
} else if (mlir::isa<cir::ConstArrayAttr, cir::ConstVectorAttr,
cir::ConstRecordAttr, cir::ConstPtrAttr,
cir::ConstComplexAttr, cir::GlobalViewAttr,
- cir::ZeroAttr>(init.value())) {
+ cir::VTableAttr, cir::ZeroAttr>(init.value())) {
// TODO(cir): once LLVM's dialect has proper equivalent attributes this
// should be updated. For now, we use a custom op to initialize globals
// to the appropriate value.
@@ -2256,6 +2272,9 @@ static void prepareTypeConverter(mlir::LLVMTypeConverter &converter,
}
break;
}
+ converter.addConversion([&](cir::VoidType type) -> mlir::Type {
+ return mlir::LLVM::LLVMVoidType::get(type.getContext());
+ });
// Record has a name: lower as an identified record.
mlir::LLVM::LLVMStructType llvmStruct;
diff --git a/clang/test/CIR/CodeGen/vtable-emission.cpp b/clang/test/CIR/CodeGen/vtable-emission.cpp
new file mode 100644
index 0000000000000..9a34573b475c3
--- /dev/null
+++ b/clang/test/CIR/CodeGen/vtable-emission.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-rtti -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-rtti -fclangir -emit-llvm -o %t-cir.ll %s
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-rtti -emit-llvm -o %t.ll %s
+// 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.
+
+struct S {
+ virtual void key();
+ virtual void nonKey() {}
+};
+
+void S::key() {}
+
+// CHECK-DAG: !rec_anon_struct = !cir.record<struct {!cir.array<!cir.ptr<!u8i> x 4>}>
+
+// The definition of the key function should result in the vtable being emitted.
+// CHECK: cir.global "private" external @_ZTV1S = #cir.vtable<{
+// CHECK-SAME: #cir.const_array<[
+// CHECK-SAME: #cir.ptr<null> : !cir.ptr<!u8i>,
+// CHECK-SAME: #cir.ptr<null> : !cir.ptr<!u8i>,
+// CHECK-SAME: #cir.global_view<@_ZN1S3keyEv> : !cir.ptr<!u8i>,
+// CHECK-SAME: #cir.global_view<@_ZN1S6nonKeyEv> : !cir.ptr<!u8i>]>
+// CHECK-SAME: : !cir.array<!cir.ptr<!u8i> x 4>}> : !rec_anon_struct
+
+// LLVM: @_ZTV1S = global { [4 x ptr] } { [4 x ptr]
+// LLVM-SAME: [ptr null, ptr null, ptr @_ZN1S3keyEv, ptr @_ZN1S6nonKeyEv] }
+
+// OGCG: @_ZTV1S = unnamed_addr constant { [4 x ptr] } { [4 x ptr]
+// OGCG-SAME: [ptr null, ptr null, ptr @_ZN1S3keyEv, ptr @_ZN1S6nonKeyEv] }
+
+// CHECK: cir.func dso_local @_ZN1S3keyEv
+
+// The reference from the vtable should result in nonKey being emitted.
+// CHECK: cir.func comdat linkonce_odr @_ZN1S6nonKeyEv
>From 75546d901184b508d278cf138368ceba710f99e5 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Fri, 22 Aug 2025 11:15:56 -0700
Subject: [PATCH 2/2] Address review feedback
---
clang/lib/CIR/CodeGen/CIRGenVTables.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
index dec73ba793b1d..0e50b20079e37 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
@@ -69,7 +69,7 @@ mlir::Attribute CIRGenVTables::getVTableComponent(
const VTableLayout &layout, unsigned componentIndex, mlir::Attribute rtti,
unsigned &nextVTableThunkIndex, unsigned vtableAddressPoint,
bool vtableHasLocalLinkage) {
- auto &component = layout.vtable_components()[componentIndex];
+ const VTableComponent &component = layout.vtable_components()[componentIndex];
CIRGenBuilderTy builder = cgm.getBuilder();
@@ -140,7 +140,7 @@ void CIRGenVTables::createVTableInitializer(cir::GlobalOp &vtableOp,
bool vtableHasLocalLinkage) {
mlir::Type componentType = getVTableComponentType();
- const llvm::SmallVector<unsigned, 4> &addressPoints =
+ const llvm::SmallVector<unsigned> &addressPoints =
layout.getAddressPointIndices();
unsigned nextVTableThunkIndex = 0;
@@ -154,7 +154,7 @@ void CIRGenVTables::createVTableInitializer(cir::GlobalOp &vtableOp,
size_t vtableEnd = vtableStart + layout.getVTableSize(vtableIndex);
// Build a ConstArrayAttr of the vtable components.
- llvm::SmallVector<mlir::Attribute, 4> components;
+ llvm::SmallVector<mlir::Attribute> components;
for (size_t componentIndex = vtableStart; componentIndex < vtableEnd;
++componentIndex) {
components.push_back(
More information about the cfe-commits
mailing list