llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clangir Author: Andy Kaylor (andykaylor) <details> <summary>Changes</summary> This adds a simplified version of the code to emit vtables. It does not yet handle RTTI or cases that require multiple vtables. --- Patch is 27.15 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/154808.diff 12 Files Affected: - (modified) clang/include/clang/CIR/MissingFeatures.h (+1) - (modified) clang/lib/CIR/CodeGen/CIRGenCXXABI.h (+4) - (modified) clang/lib/CIR/CodeGen/CIRGenCall.cpp (+15) - (modified) clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp (+63) - (modified) clang/lib/CIR/CodeGen/CIRGenModule.cpp (+13-1) - (modified) clang/lib/CIR/CodeGen/CIRGenModule.h (+17) - (modified) clang/lib/CIR/CodeGen/CIRGenTypes.h (+7) - (modified) clang/lib/CIR/CodeGen/CIRGenVTables.cpp (+206-2) - (modified) clang/lib/CIR/CodeGen/CIRGenVTables.h (+22-1) - (modified) clang/lib/CIR/CodeGen/CIRGenerator.cpp (+1-1) - (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+23-4) - (added) clang/test/CIR/CodeGen/vtable-emission.cpp (+38) ``````````diff 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 giv... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/154808 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits