Author: Andy Kaylor Date: 2025-08-22T13:26:17-07:00 New Revision: ffe3768dfbbb3a598482d1f4a3dce4f02446340d
URL: https://github.com/llvm/llvm-project/commit/ffe3768dfbbb3a598482d1f4a3dce4f02446340d DIFF: https://github.com/llvm/llvm-project/commit/ffe3768dfbbb3a598482d1f4a3dce4f02446340d.diff LOG: [CIR] Add support for emitting vtables (#154808) This adds a simplified version of the code to emit vtables. It does not yet handle RTTI or cases that require multiple vtables. Added: clang/test/CIR/CodeGen/vtable-emission.cpp Modified: clang/include/clang/CIR/MissingFeatures.h clang/lib/CIR/CodeGen/CIRGenCXXABI.h clang/lib/CIR/CodeGen/CIRGenCall.cpp clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp clang/lib/CIR/CodeGen/CIRGenModule.cpp clang/lib/CIR/CodeGen/CIRGenModule.h clang/lib/CIR/CodeGen/CIRGenTypes.h clang/lib/CIR/CodeGen/CIRGenVTables.cpp clang/lib/CIR/CodeGen/CIRGenVTables.h clang/lib/CIR/CodeGen/CIRGenerator.cpp clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp Removed: ################################################################################ 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..438b483ccb9c2 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) { + const VTableComponent &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::SmallVectorImpl<unsigned> &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> 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 diff erent 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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits