diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -527,6 +527,9 @@ /// Whether we are currently parsing base specifiers. unsigned IsParsingBaseSpecifiers : 1; + /// \brief Whether the class uses the relative C++ vtable ABI. + unsigned IsRelativeCXXABI : 1; + unsigned HasODRHash : 1; /// A hash of parts of the class to help in ODR checking. @@ -807,6 +810,12 @@ return data().IsParsingBaseSpecifiers; } + void setIsRelativeCXXABI() { data().IsRelativeCXXABI = true; } + + bool isRelativeCXXABI() const { + return data().IsRelativeCXXABI; + } + unsigned getODRHash() const; /// Sets the base classes of this struct or class. @@ -1972,6 +1981,12 @@ return getLambdaData().MethodTyInfo; } + /// Returns whether this class would have hidden LTO visibility if it were built + /// in a translation unit with LTO, and therefore may participate in + /// (single-module) CFI, whole-program vtable optimization and the relative + /// C++ ABI. + bool hasHiddenLTOVisibility() const; + // Determine whether this type is an Interface Like type for // __interface inheritance purposes. bool isInterfaceLike() const; diff --git a/clang/include/clang/AST/VTableBuilder.h b/clang/include/clang/AST/VTableBuilder.h --- a/clang/include/clang/AST/VTableBuilder.h +++ b/clang/include/clang/AST/VTableBuilder.h @@ -264,6 +264,10 @@ return VTableComponents; } + VTableComponent &getVTableComponent(size_t i) const { + return VTableComponents[i]; + } + ArrayRef vtable_thunks() const { return VTableThunks; } diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -274,10 +274,6 @@ CODEGENOPT(WholeProgramVTables, 1, 0) ///< Whether to apply whole-program /// vtable optimization. -/// Whether to use public LTO visibility for entities in std and stdext -/// namespaces. This is enabled by clang-cl's /MT and /MTd flags. -CODEGENOPT(LTOVisibilityPublicStd, 1, 0) - /// The user specified number of registers to be used for integral arguments, /// or 0 if unspecified. VALUE_CODEGENOPT(NumRegisterParameters, 32, 0) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -9527,4 +9527,9 @@ "%select{non-pointer|function pointer|void pointer}0 argument to " "'__builtin_launder' is not allowed">; +def err_abi_mismatch : Error< + "inconsistent ABI for class %0">; +def note_abi_relative_base : Note< + "base %0 uses the %select{platform|relative}1 ABI">; + } // end of sema component. diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -317,6 +317,14 @@ LANGOPT(RegisterStaticDestructors, 1, 1, "Register C++ static destructors") +LANGOPT(LTOVisibilityPublicStd, 1, 0, "Whether to use public LTO visibility " + "for entities in std and stdext " + "namespaces. This is enabled by " + "clang-cl's /MT and /MTd flags.") +LANGOPT(LTORelativeCXXABIVTables, 1, 0, + "Whether to use clang's relative C++ vtable ABI " + "for classes with hidden LTO visibility") + #undef LANGOPT #undef COMPATIBLE_LANGOPT #undef BENIGN_LANGOPT diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1245,6 +1245,9 @@ HelpText<"Enable LTO in 'full' mode">; def fno_lto : Flag<["-"], "fno-lto">, Group, HelpText<"Disable LTO mode (default)">; +def flto_relative_cxx_abi_vtables : Flag<["-"], "flto-relative-c++-abi-vtables">, + Group, Flags<[CC1Option]>, + HelpText<"Use the unstable C++ class ABI for classes with hidden LTO visibility">; def flto_jobs_EQ : Joined<["-"], "flto-jobs=">, Flags<[CC1Option]>, Group, HelpText<"Controls the backend parallelism of -flto=thin (default " diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -10885,6 +10885,12 @@ // (including field initializers) is fully parsed. SmallVector DelayedDllExportClasses; + /// Determine the ABI for this class using its attributes, bases and implicit + /// contexts. Check for conflicts between bases or between a base and an + /// attribute. Set the class's isRelativeCXXABI() flag according to the + /// result. + void checkClassABI(CXXRecordDecl *RD); + private: class SavePendingParsedClassStateRAII { public: diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -29,6 +29,7 @@ #include "clang/AST/UnresolvedSet.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/TargetInfo.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/OperatorKinds.h" @@ -102,7 +103,8 @@ ImplicitCopyAssignmentHasConstParam(true), HasDeclaredCopyConstructorWithConstParam(false), HasDeclaredCopyAssignmentWithConstParam(false), IsLambda(false), - IsParsingBaseSpecifiers(false), HasODRHash(false), Definition(D) {} + IsParsingBaseSpecifiers(false), IsRelativeCXXABI(false), + HasODRHash(false), Definition(D) {} CXXBaseSpecifier *CXXRecordDecl::DefinitionData::getBasesSlowCase() const { return Bases.get(Definition->getASTContext().getExternalSource()); @@ -1860,6 +1862,41 @@ return false; } +bool CXXRecordDecl::hasHiddenLTOVisibility() const { + LinkageInfo LV = getLinkageAndVisibility(); + if (!clang::isExternallyVisible(LV.getLinkage())) + return true; + + if (hasAttr() || hasAttr()) + return false; + + ASTContext &Context = getASTContext(); + if (Context.getTargetInfo().getTriple().isOSBinFormatCOFF()) { + if (hasAttr() || hasAttr()) + return false; + } else { + if (LV.getVisibility() != HiddenVisibility) + return false; + } + + if (Context.getLangOpts().LTOVisibilityPublicStd) { + const DeclContext *DC = this; + while (1) { + auto *D = cast(DC); + DC = DC->getParent(); + if (isa(DC->getRedeclContext())) { + if (auto *ND = dyn_cast(D)) + if (const IdentifierInfo *II = ND->getIdentifier()) + if (II->isStr("std") || II->isStr("stdext")) + return false; + break; + } + } + } + + return true; +} + void CXXDeductionGuideDecl::anchor() {} CXXDeductionGuideDecl *CXXDeductionGuideDecl::Create( diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -2573,6 +2573,24 @@ return VTable; } +llvm::Value *CodeGenFunction::GetVirtualFunctionFromVTable(const CXXRecordDecl *RD, + llvm::Value *VTable, + uint64_t VTableIndex, + llvm::Type *Ty) { + if (!RD->isRelativeCXXABI()) { + VTable = Builder.CreateBitCast(VTable, Ty->getPointerTo()->getPointerTo()); + llvm::Value *VTableSlotPtr = + Builder.CreateConstInBoundsGEP1_64(VTable, VTableIndex, "vfn"); + return Builder.CreateAlignedLoad(VTableSlotPtr, getPointerAlign()); + } + + VTable = Builder.CreateBitCast(VTable, Int8PtrTy); + llvm::Value *Load = Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::load_relative, {Int32Ty}), + {VTable, llvm::ConstantInt::get(Int32Ty, 4 * VTableIndex)}); + return Builder.CreateBitCast(Load, Ty->getPointerTo()); +} + // If a class has a single non-virtual base and does not introduce or override // virtual member functions or fields, it will have the same layout as its base. // This function returns the least derived such class. @@ -2614,7 +2632,7 @@ if (SanOpts.has(SanitizerKind::CFIVCall)) EmitVTablePtrCheckForCall(RD, VTable, CodeGenFunction::CFITCK_VCall, Loc); else if (CGM.getCodeGenOpts().WholeProgramVTables && - CGM.HasHiddenLTOVisibility(RD)) { + RD->hasHiddenLTOVisibility()) { llvm::Metadata *MD = CGM.CreateMetadataIdentifierForType(QualType(RD->getTypeForDecl(), 0)); llvm::Value *TypeId = @@ -2689,7 +2707,7 @@ CFITypeCheckKind TCK, SourceLocation Loc) { if (!CGM.getCodeGenOpts().SanitizeCfiCrossDso && - !CGM.HasHiddenLTOVisibility(RD)) + !RD->hasHiddenLTOVisibility()) return; SanitizerMask M; @@ -2762,7 +2780,7 @@ if (!CGM.getCodeGenOpts().WholeProgramVTables || !SanOpts.has(SanitizerKind::CFIVCall) || !CGM.getCodeGenOpts().SanitizeTrap.has(SanitizerKind::CFIVCall) || - !CGM.HasHiddenLTOVisibility(RD)) + !RD->hasHiddenLTOVisibility()) return false; std::string TypeName = RD->getQualifiedNameAsString(); diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp --- a/clang/lib/CodeGen/CGDebugInfo.cpp +++ b/clang/lib/CodeGen/CGDebugInfo.cpp @@ -1537,8 +1537,10 @@ if (CGM.getTarget().getCXXABI().isItaniumFamily()) { // It doesn't make sense to give a virtual destructor a vtable index, // since a single destructor has two entries in the vtable. - if (!isa(Method)) + if (!isa(Method) && !Method->getParent()->isRelativeCXXABI()) VIndex = CGM.getItaniumVTableContext().getMethodVTableIndex(Method); + else + VIndex = -1u; } else { // Emit MS ABI vftable information. There is only one entry for the // deleting dtor. diff --git a/clang/lib/CodeGen/CGVTables.h b/clang/lib/CodeGen/CGVTables.h --- a/clang/lib/CodeGen/CGVTables.h +++ b/clang/lib/CodeGen/CGVTables.h @@ -61,17 +61,35 @@ const ThunkInfo &ThunkAdjustments, bool ForVTable); + /// Get the constant to be added to a VTable. + llvm::Constant *getVTableComponent(const VTableLayout &layout, + unsigned componentIdx, unsigned vtableIdx, + llvm::Constant *rtti, + unsigned &nextVTableThunkIndex, + bool RelativeABI, llvm::GlobalVariable *VTable); + + /// Add a constant to a VTable through a builder. void addVTableComponent(ConstantArrayBuilder &builder, - const VTableLayout &layout, unsigned idx, + const VTableLayout &layout, + unsigned componentIdx, unsigned vtableIdx, llvm::Constant *rtti, - unsigned &nextVTableThunkIndex); + unsigned &nextVTableThunkIndex, + bool RelativeABI, llvm::GlobalVariable *VTable); + + /// Make a function pointer into a relative integer when using the relative + /// vtables ABI. + llvm::Constant *makeRelative(llvm::Constant *C, + llvm::GlobalVariable *VTable, + unsigned vtableIdx, + unsigned relCompIdx) const; public: /// Add vtable components for the given vtable layout to the given /// global initializer. - void createVTableInitializer(ConstantStructBuilder &builder, - const VTableLayout &layout, - llvm::Constant *rtti); + void SetVTableInitializer(llvm::GlobalVariable *VTable, + const VTableLayout &VTLayout, + llvm::Constant *RTTI, + bool RelativeABI); CodeGenVTables(CodeGenModule &CGM); @@ -123,7 +141,8 @@ /// 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. - llvm::Type *getVTableType(const VTableLayout &layout); + llvm::Type *getVTableType(const CXXRecordDecl *RD, + const VTableLayout &layout); }; } // end namespace CodeGen diff --git a/clang/lib/CodeGen/CGVTables.cpp b/clang/lib/CodeGen/CGVTables.cpp --- a/clang/lib/CodeGen/CGVTables.cpp +++ b/clang/lib/CodeGen/CGVTables.cpp @@ -586,15 +586,45 @@ maybeEmitThunk(GD, Thunk, /*ForVTable=*/false); } -void CodeGenVTables::addVTableComponent( - ConstantArrayBuilder &builder, const VTableLayout &layout, - unsigned idx, llvm::Constant *rtti, unsigned &nextVTableThunkIndex) { - auto &component = layout.vtable_components()[idx]; +llvm::Constant *CodeGenVTables::makeRelative(llvm::Constant *C, + llvm::GlobalVariable *VTable, + unsigned vtableIdx, + unsigned relCompIdx) const { + llvm::Type *Int32Ty = CGM.Int32Ty; + llvm::Type *PtrDiffTy = + CGM.getTypes().ConvertType(CGM.getContext().getPointerDiffType()); + llvm::Type *VTableTy = VTable->getValueType(); + llvm::Constant *gep = llvm::ConstantExpr::getGetElementPtr( + VTableTy, VTable, + llvm::ArrayRef{ + llvm::ConstantInt::get(Int32Ty, 0), + llvm::ConstantInt::get(Int32Ty, vtableIdx), + llvm::ConstantInt::get(Int32Ty, relCompIdx)}); + llvm::Constant *AddrPointInt = llvm::ConstantExpr::getPtrToInt(gep, PtrDiffTy); + + // FIXME: Need a better way of identifying address points that works with + // the Itanium and MS ABIs. + llvm::Constant *ptrToInt = llvm::ConstantExpr::getPtrToInt(C, PtrDiffTy); + return llvm::ConstantExpr::getIntegerCast( + llvm::ConstantExpr::getSub(ptrToInt, AddrPointInt), + Int32Ty, /*isSigned=*/true); +} + +llvm::Constant *CodeGenVTables::getVTableComponent( + const VTableLayout &layout, + unsigned componentIdx, unsigned vtableIdx, + llvm::Constant *rtti, unsigned &nextVTableThunkIndex, + bool RelativeABI, llvm::GlobalVariable *VTable) { + auto &component = layout.getVTableComponent(componentIdx); + + llvm::Type *Int8PtrTy = CGM.Int8PtrTy; + llvm::Type *Int32Ty = CGM.Int32Ty; + llvm::Type *FunctionPtrTy = RelativeABI ? Int32Ty : Int8PtrTy; auto addOffsetConstant = [&](CharUnits offset) { - builder.add(llvm::ConstantExpr::getIntToPtr( + return llvm::ConstantExpr::getIntToPtr( llvm::ConstantInt::get(CGM.PtrDiffTy, offset.getQuantity()), - CGM.Int8PtrTy)); + CGM.Int8PtrTy); }; switch (component.getKind()) { @@ -607,8 +637,9 @@ case VTableComponent::CK_OffsetToTop: return addOffsetConstant(component.getOffsetToTop()); - case VTableComponent::CK_RTTI: - return builder.add(llvm::ConstantExpr::getBitCast(rtti, CGM.Int8PtrTy)); + case VTableComponent::CK_RTTI: { + return llvm::ConstantExpr::getBitCast(rtti, CGM.Int8PtrTy); + } case VTableComponent::CK_FunctionPointer: case VTableComponent::CK_CompleteDtorPointer: @@ -642,7 +673,7 @@ ? MD->hasAttr() : (MD->hasAttr() || !MD->hasAttr()); if (!CanEmitMethod) - return builder.addNullPointer(CGM.Int8PtrTy); + return llvm::ConstantExpr::getNullValue(FunctionPtrTy); // Method is acceptable, continue processing as usual. } @@ -674,7 +705,7 @@ // Thunks. } else if (nextVTableThunkIndex < layout.vtable_thunks().size() && - layout.vtable_thunks()[nextVTableThunkIndex].first == idx) { + layout.vtable_thunks()[nextVTableThunkIndex].first == componentIdx) { auto &thunkInfo = layout.vtable_thunks()[nextVTableThunkIndex].second; nextVTableThunkIndex++; @@ -687,39 +718,94 @@ } fnPtr = llvm::ConstantExpr::getBitCast(fnPtr, CGM.Int8PtrTy); - builder.add(fnPtr); - return; + if (RelativeABI && component.isFunctionPointerKind()) { + size_t thisIndex = layout.getVTableOffset(vtableIdx); + return makeRelative(fnPtr, VTable, vtableIdx, componentIdx - thisIndex); + } + return fnPtr; } case VTableComponent::CK_UnusedFunctionPointer: - return builder.addNullPointer(CGM.Int8PtrTy); + return llvm::ConstantExpr::getNullValue(FunctionPtrTy); } llvm_unreachable("Unexpected vtable component kind"); } -llvm::Type *CodeGenVTables::getVTableType(const VTableLayout &layout) { +void CodeGenVTables::addVTableComponent( + ConstantArrayBuilder &builder, const VTableLayout &layout, + unsigned componentIdx, unsigned vtableIdx, + llvm::Constant *rtti, unsigned &nextVTableThunkIndex, + bool RelativeABI, llvm::GlobalVariable *VTable) { + builder.add(getVTableComponent(layout, componentIdx, vtableIdx, rtti, + nextVTableThunkIndex, + RelativeABI, VTable)); +} + +llvm::Type *CodeGenVTables::getVTableType(const CXXRecordDecl *RD, + const VTableLayout &layout) { SmallVector tys; for (unsigned i = 0, e = layout.getNumVTables(); i != e; ++i) { - tys.push_back(llvm::ArrayType::get(CGM.Int8PtrTy, layout.getVTableSize(i))); + if (!RD->isRelativeCXXABI()) { + tys.push_back(llvm::ArrayType::get(CGM.Int8PtrTy, layout.getVTableSize(i))); + } else { + SmallVector innerTypes; + size_t thisIndex = layout.getVTableOffset(i); + size_t nextIndex = thisIndex + layout.getVTableSize(i); + for (unsigned componentIdx = thisIndex; componentIdx != nextIndex; + ++componentIdx) { + auto &component = layout.getVTableComponent(componentIdx); + if (component.isFunctionPointerKind()) + innerTypes.push_back(CGM.Int32Ty); + else + innerTypes.push_back(CGM.Int8PtrTy); + } + tys.push_back(llvm::StructType::get(CGM.getLLVMContext(), innerTypes)); + } } return llvm::StructType::get(CGM.getLLVMContext(), tys); } -void CodeGenVTables::createVTableInitializer(ConstantStructBuilder &builder, - const VTableLayout &layout, - llvm::Constant *rtti) { +void CodeGenVTables::SetVTableInitializer(llvm::GlobalVariable *VTable, + const VTableLayout &VTLayout, + llvm::Constant *RTTI, + bool RelativeABI) { + ConstantInitBuilder builder(CGM); + auto components = builder.beginStruct(); + unsigned nextVTableThunkIndex = 0; - for (unsigned i = 0, e = layout.getNumVTables(); i != e; ++i) { - auto vtableElem = builder.beginArray(CGM.Int8PtrTy); - size_t thisIndex = layout.getVTableOffset(i); - size_t nextIndex = thisIndex + layout.getVTableSize(i); - for (unsigned i = thisIndex; i != nextIndex; ++i) { - addVTableComponent(vtableElem, layout, i, rtti, nextVTableThunkIndex); + for (unsigned vtableIdx = 0; vtableIdx != VTLayout.getNumVTables(); ++vtableIdx) { + size_t thisIndex = VTLayout.getVTableOffset(vtableIdx); + size_t nextIndex = thisIndex + VTLayout.getVTableSize(vtableIdx); + + if (!RelativeABI) { + // Construct a normal array of pointers. + auto vtableElem = components.beginArray(CGM.Int8PtrTy); + for (unsigned componentIdx = thisIndex; componentIdx != nextIndex; + ++componentIdx) { + addVTableComponent(vtableElem, VTLayout, + componentIdx, vtableIdx, + RTTI, nextVTableThunkIndex, RelativeABI, + VTable); + } + vtableElem.finishAndAddTo(components); + } else { + // Instead use an i32 to indicate an offset. + SmallVector Inits; + for (unsigned componentIdx = thisIndex; componentIdx != nextIndex; + ++componentIdx) { + Inits.push_back(getVTableComponent(VTLayout, componentIdx, vtableIdx, + RTTI, nextVTableThunkIndex, + RelativeABI, VTable)); + } + auto *VTableTy = cast(VTable->getValueType()); + auto *StructTy = cast(VTableTy->getElementType(vtableIdx)); + components.add(llvm::ConstantStruct::get(StructTy, Inits)); } - vtableElem.finishAndAddTo(builder); } + + components.finishAndSetAsInitializer(VTable); } llvm::GlobalVariable * @@ -746,7 +832,7 @@ Base.getBase(), Out); StringRef Name = OutName.str(); - llvm::Type *VTType = getVTableType(*VTLayout); + llvm::Type *VTType = getVTableType(RD, *VTLayout); // Construction vtable symbols are not part of the Itanium ABI, so we cannot // guarantee that they actually will be available externally. Instead, when @@ -769,10 +855,7 @@ CGM.getContext().getTagDeclType(Base.getBase())); // Create and set the initializer. - ConstantInitBuilder builder(CGM); - auto components = builder.beginStruct(); - createVTableInitializer(components, *VTLayout, RTTI); - components.finishAndSetAsInitializer(VTable); + SetVTableInitializer(VTable, *VTLayout, RTTI, RD->isRelativeCXXABI()); // Set properties only after the initializer has been set to ensure that the // GV is treated as definition and not declaration. @@ -976,40 +1059,6 @@ DeferredVTables.clear(); } -bool CodeGenModule::HasHiddenLTOVisibility(const CXXRecordDecl *RD) { - LinkageInfo LV = RD->getLinkageAndVisibility(); - if (!isExternallyVisible(LV.getLinkage())) - return true; - - if (RD->hasAttr() || RD->hasAttr()) - return false; - - if (getTriple().isOSBinFormatCOFF()) { - if (RD->hasAttr() || RD->hasAttr()) - return false; - } else { - if (LV.getVisibility() != HiddenVisibility) - return false; - } - - if (getCodeGenOpts().LTOVisibilityPublicStd) { - const DeclContext *DC = RD; - while (1) { - auto *D = cast(DC); - DC = DC->getParent(); - if (isa(DC->getRedeclContext())) { - if (auto *ND = dyn_cast(D)) - if (const IdentifierInfo *II = ND->getIdentifier()) - if (II->isStr("std") || II->isStr("stdext")) - return false; - break; - } - } - } - - return true; -} - void CodeGenModule::EmitVTableTypeMetadata(llvm::GlobalVariable *VTable, const VTableLayout &VTLayout) { if (!getCodeGenOpts().LTOUnit) @@ -1053,8 +1102,10 @@ ArrayRef Comps = VTLayout.vtable_components(); for (auto AP : AddressPoints) { + CharUnits ByteOffset = PointerWidth * AP.second; + // Create type metadata for the address point. - AddVTableTypeMetadata(VTable, PointerWidth * AP.second, AP.first); + AddVTableTypeMetadata(VTable, ByteOffset, AP.first); // The class associated with each address point could also potentially be // used for indirect calls via a member function pointer, so we need to diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -1904,6 +1904,11 @@ llvm::Value *GetVTablePtr(Address This, llvm::Type *VTableTy, const CXXRecordDecl *VTableClass); + llvm::Value *GetVirtualFunctionFromVTable(const CXXRecordDecl *RD, + llvm::Value *VTable, + uint64_t VTableIndex, + llvm::Type *Ty); + enum CFITypeCheckKind { CFITCK_VCall, CFITCK_NVCall, diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -531,6 +531,12 @@ CodeGenOpts.FlushDenorm ? 1 : 0); } + if (CodeGenOpts.PrepareForLTO) { + getModule().addModuleFlag(llvm::Module::Error, + "lto-relative-c++-abi-vtables", + getLangOpts().LTORelativeCXXABIVTables); + } + // Emit OpenCL specific module metadata: OpenCL/SPIR version. if (LangOpts.OpenCL) { EmitOpenCLMetadata(); @@ -1198,7 +1204,7 @@ const CXXMethodDecl *MD) { // Check that the type metadata can ever actually be used by a call. if (!CGM.getCodeGenOpts().LTOUnit || - !CGM.HasHiddenLTOVisibility(MD->getParent())) + !MD->getParent()->hasHiddenLTOVisibility()) return false; // Only functions whose address can be taken with a member function pointer @@ -2988,7 +2994,7 @@ F->setCallingConv(getRuntimeCC()); if (!Local && getTriple().isOSBinFormatCOFF() && - !getCodeGenOpts().LTOVisibilityPublicStd && + !getLangOpts().LTOVisibilityPublicStd && !getTriple().isWindowsGNUEnvironment()) { const FunctionDecl *FD = GetRuntimeFunctionDecl(Context, Name); if (!FD || FD->hasAttr()) { diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -630,7 +630,7 @@ llvm::Constant *CheckSourceLocation; llvm::Constant *CheckTypeDesc; bool ShouldEmitCFICheck = CGF.SanOpts.has(SanitizerKind::CFIMFCall) && - CGM.HasHiddenLTOVisibility(RD); + RD->hasHiddenLTOVisibility(); if (ShouldEmitCFICheck) { CodeGenFunction::SanitizerScope SanScope(&CGF); @@ -666,9 +666,20 @@ } // Load the virtual function to call. - VFPAddr = Builder.CreateBitCast(VFPAddr, FTy->getPointerTo()->getPointerTo()); - llvm::Value *VirtualFn = Builder.CreateAlignedLoad( - VFPAddr, CGF.getPointerAlign(), "memptr.virtualfn"); + llvm::Value *VirtualFn; + if (RD->hasDefinition() && RD->isRelativeCXXABI()) { + VirtualFn = + Builder.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::load_relative, + {VTableOffset->getType()}), + {VTable, VTableOffset}); + VirtualFn = Builder.CreateBitCast(VirtualFn, FTy->getPointerTo()); + } else { + // Apply the offset. + llvm::Value *VTableSlotPtr = Builder.CreateBitCast(VFPAddr, + FTy->getPointerTo()->getPointerTo()); + VirtualFn = Builder.CreateAlignedLoad(VTableSlotPtr, CGF.getPointerAlign(), + "memptr.virtualfn"); + } CGF.EmitBranch(FnEnd); // In the non-virtual path, the function pointer is actually a @@ -914,7 +925,11 @@ const ASTContext &Context = getContext(); CharUnits PointerWidth = Context.toCharUnitsFromBits(Context.getTargetInfo().getPointerWidth(0)); - uint64_t VTableOffset = (Index * PointerWidth.getQuantity()); + uint64_t VTableOffset = Index; + if (MD->getParent()->isRelativeCXXABI()) + VTableOffset *= 4; + else + VTableOffset *= PointerWidth.getQuantity(); if (UseARMMethodPtrABI) { // ARM C++ ABI 3.2.1: @@ -1584,10 +1599,7 @@ CGM.GetAddrOfRTTIDescriptor(CGM.getContext().getTagDeclType(RD)); // Create and set the initializer. - ConstantInitBuilder Builder(CGM); - auto Components = Builder.beginStruct(); - CGVT.createVTableInitializer(Components, VTLayout, RTTI); - Components.finishAndSetAsInitializer(VTable); + CGVT.SetVTableInitializer(VTable, VTLayout, RTTI, RD->isRelativeCXXABI()); // Set the correct linkage. VTable->setLinkage(Linkage); @@ -1695,7 +1707,7 @@ const VTableLayout &VTLayout = CGM.getItaniumVTableContext().getVTableLayout(RD); - llvm::Type *VTableType = CGM.getVTables().getVTableType(VTLayout); + llvm::Type *VTableType = CGM.getVTables().getVTableType(RD, VTLayout); // Use pointer alignment for the vtable. Otherwise we would align them based // on the size of the initializer which doesn't make sense as only single @@ -1717,9 +1729,9 @@ Address This, llvm::Type *Ty, SourceLocation Loc) { - Ty = Ty->getPointerTo()->getPointerTo(); auto *MethodDecl = cast(GD.getDecl()); - llvm::Value *VTable = CGF.GetVTablePtr(This, Ty, MethodDecl->getParent()); + llvm::Value *VTable = CGF.GetVTablePtr(This, Ty->getPointerTo()->getPointerTo(), + MethodDecl->getParent()); uint64_t VTableIndex = CGM.getItaniumVTableContext().getMethodVTableIndex(GD); llvm::Value *VFunc; @@ -1730,10 +1742,8 @@ } else { CGF.EmitTypeMetadataCodeForVCall(MethodDecl->getParent(), VTable, Loc); - llvm::Value *VFuncPtr = - CGF.Builder.CreateConstInBoundsGEP1_64(VTable, VTableIndex, "vfn"); - auto *VFuncLoad = - CGF.Builder.CreateAlignedLoad(VFuncPtr, CGF.getPointerAlign()); + auto *VFuncLoad = CGF.GetVirtualFunctionFromVTable(MethodDecl->getParent(), + VTable, VTableIndex, Ty); // Add !invariant.load md to virtual function load to indicate that // function didn't change inside vtable. @@ -1742,11 +1752,14 @@ // the same virtual function loads from the same vtable load, which won't // happen without enabled devirtualization with -fstrict-vtable-pointers. if (CGM.getCodeGenOpts().OptimizationLevel > 0 && - CGM.getCodeGenOpts().StrictVTablePointers) - VFuncLoad->setMetadata( - llvm::LLVMContext::MD_invariant_load, - llvm::MDNode::get(CGM.getLLVMContext(), - llvm::ArrayRef())); + CGM.getCodeGenOpts().StrictVTablePointers) { + if (auto *VFuncLoadInstr = dyn_cast(VFuncLoad)) { + VFuncLoadInstr->setMetadata( + llvm::LLVMContext::MD_invariant_load, + llvm::MDNode::get(CGM.getLLVMContext(), + llvm::ArrayRef())); + } + } VFunc = VFuncLoad; } diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -1646,10 +1646,7 @@ [](const VTableComponent &VTC) { return VTC.isRTTIKind(); })) RTTI = getMSCompleteObjectLocator(RD, *Info); - ConstantInitBuilder Builder(CGM); - auto Components = Builder.beginStruct(); - CGVT.createVTableInitializer(Components, VTLayout, RTTI); - Components.finishAndSetAsInitializer(VTable); + CGVT.SetVTableInitializer(VTable, VTLayout, RTTI, RD->isRelativeCXXABI()); emitVTableTypeMetadata(*Info, RD, VTable); } @@ -1775,7 +1772,7 @@ StringRef VTableName = VTableAliasIsRequred ? StringRef() : VFTableName.str(); - llvm::Type *VTableType = CGM.getVTables().getVTableType(VTLayout); + llvm::Type *VTableType = CGM.getVTables().getVTableType(RD, VTLayout); // Create a backing variable for the contents of VTable. The VTable may // or may not include space for a pointer to RTTI data. @@ -1808,6 +1805,7 @@ if (C) C->setSelectionKind(llvm::Comdat::Largest); } + VTableGEP = llvm::ConstantExpr::getBitCast(VTableGEP, CGM.Int8PtrPtrTy); VFTable = llvm::GlobalAlias::create(CGM.Int8PtrTy, /*AddressSpace=*/0, VFTableLinkage, VFTableName.str(), VTableGEP, @@ -1835,14 +1833,12 @@ Address This, llvm::Type *Ty, SourceLocation Loc) { - CGBuilderTy &Builder = CGF.Builder; - - Ty = Ty->getPointerTo()->getPointerTo(); Address VPtr = adjustThisArgumentForVirtualFunctionCall(CGF, GD, This, true); auto *MethodDecl = cast(GD.getDecl()); - llvm::Value *VTable = CGF.GetVTablePtr(VPtr, Ty, MethodDecl->getParent()); + llvm::Value *VTable = CGF.GetVTablePtr(VPtr, Ty->getPointerTo()->getPointerTo(), + MethodDecl->getParent()); MicrosoftVTableContext &VFTContext = CGM.getMicrosoftVTableContext(); MethodVFTableLocation ML = VFTContext.getMethodVFTableLocation(GD); @@ -1868,9 +1864,8 @@ if (CGM.getCodeGenOpts().PrepareForLTO) CGF.EmitTypeMetadataCodeForVCall(getObjectWithVPtr(), VTable, Loc); - llvm::Value *VFuncPtr = - Builder.CreateConstInBoundsGEP1_64(VTable, ML.Index, "vfn"); - VFunc = Builder.CreateAlignedLoad(VFuncPtr, CGF.getPointerAlign()); + VFunc = CGF.GetVirtualFunctionFromVTable(MethodDecl->getParent(), + VTable, ML.Index, Ty); } CGCallee Callee(GD, VFunc); @@ -1992,10 +1987,8 @@ llvm::Value *VTable = CGF.GetVTablePtr( getThisAddress(CGF), ThunkTy->getPointerTo()->getPointerTo(), MD->getParent()); - llvm::Value *VFuncPtr = - CGF.Builder.CreateConstInBoundsGEP1_64(VTable, ML.Index, "vfn"); - llvm::Value *Callee = - CGF.Builder.CreateAlignedLoad(VFuncPtr, CGF.getPointerAlign()); + llvm::Value *Callee = CGF.GetVirtualFunctionFromVTable( + MD->getParent(), VTable, ML.Index, ThunkTy); CGF.EmitMustTailThunk(MD, getThisValue(CGF), {ThunkTy, Callee}); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -5256,6 +5256,15 @@ CmdArgs.push_back("-fwhole-program-vtables"); } + // Add unstable C++ ABI flags. + if (Args.hasArg(options::OPT_flto_relative_cxx_abi_vtables)) { + if (!D.isUsingLTO()) + D.Diag(diag::err_drv_argument_only_allowed_with) + << "-fwhole-program-vtables" + << "-flto"; + CmdArgs.push_back("-flto-relative-c++-abi-vtables"); + } + bool RequiresSplitLTOUnit = WholeProgramVTables || Sanitize.needsLTO(); bool SplitLTOUnit = Args.hasFlag(options::OPT_fsplit_lto_unit, diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -708,7 +708,6 @@ Opts.CodeViewGHash = Args.hasArg(OPT_gcodeview_ghash); Opts.MacroDebugInfo = Args.hasArg(OPT_debug_info_macro); Opts.WholeProgramVTables = Args.hasArg(OPT_fwhole_program_vtables); - Opts.LTOVisibilityPublicStd = Args.hasArg(OPT_flto_visibility_public_std); Opts.SplitDwarfFile = Args.getLastArgValue(OPT_split_dwarf_file); Opts.SplitDwarfInlining = !Args.hasArg(OPT_fno_split_dwarf_inlining); @@ -2961,6 +2960,8 @@ getLastArgIntValue(Args, OPT_fsanitize_address_field_padding, 0, Diags); Opts.SanitizerBlacklistFiles = Args.getAllArgValues(OPT_fsanitize_blacklist); + Opts.LTORelativeCXXABIVTables = Args.hasArg(OPT_flto_relative_cxx_abi_vtables); + // -fxray-instrument Opts.XRayInstrument = Args.hasFlag(OPT_fxray_instrument, OPT_fnoxray_instrument, false); @@ -3023,6 +3024,9 @@ Opts.CompleteMemberPointers = Args.hasArg(OPT_fcomplete_member_pointers); Opts.BuildingPCHWithObjectFile = Args.hasArg(OPT_building_pch_with_obj); + + Opts.LTOVisibilityPublicStd = Args.hasArg(OPT_flto_visibility_public_std); + Opts.LTORelativeCXXABIVTables = Args.hasArg(OPT_flto_relative_cxx_abi_vtables); } static bool isStrictlyPreprocessorAction(frontend::ActionKind Action) { diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -5886,6 +5886,60 @@ } } +void Sema::checkClassABI(CXXRecordDecl *Record) { + if (!getLangOpts().LTORelativeCXXABIVTables) + return; + + // This can only be done accurately for non-dependent types, as the + // determination uses the class's bases, which may be dependent. + if (Record->isDependentType()) + return; + + // No need to do this for non-dynamic classes. + if (!Record->isDynamicClass()) + return; + + // First, see if this class inherits an ABI from a dynamic base class. If the + // bases disagree on which ABI to use, diagnose. + bool InheritsABI = false; + bool InheritedABIIsRelative; + CXXRecordDecl *InheritedABIFrom; + for (CXXBaseSpecifier &B : Record->bases()) { + auto Base = B.getType()->getAsCXXRecordDecl(); + // Base can be null in invalid programs (see PR16677). + if (!Base || !Base->isDynamicClass()) + continue; + if (InheritsABI) { + if (Base->isRelativeCXXABI() != InheritedABIIsRelative) { + Diag(Record->getLocation(), diag::err_abi_mismatch) << Record; + CXXRecordDecl *Platform = + InheritedABIIsRelative ? Base : InheritedABIFrom; + CXXRecordDecl *Relative = + InheritedABIIsRelative ? InheritedABIFrom : Base; + Diag(Platform->getLocation(), diag::note_abi_relative_base) + << Platform << /*Relative=*/false; + Diag(Relative->getLocation(), diag::note_abi_relative_base) + << Relative << /*Relative=*/true; + } + } else { + InheritsABI = true; + InheritedABIIsRelative = Base->isRelativeCXXABI(); + InheritedABIFrom = Base; + } + } + + // If the class's ABI is inherited, apply it. + if (InheritsABI) { + if (InheritedABIIsRelative) + Record->setIsRelativeCXXABI(); + return; + } + + // This class's ABI is not inherited. Infer it from LTO visibility. + if (Record->hasHiddenLTOVisibility()) + Record->setIsRelativeCXXABI(); +} + /// Determine whether a type is permitted to be passed or returned in /// registers, per C++ [class.temporary]p3. static bool canPassInRegisters(Sema &S, CXXRecordDecl *D, @@ -6165,6 +6219,7 @@ checkClassLevelDLLAttribute(Record); checkClassLevelCodeSegAttribute(Record); + checkClassABI(Record); bool ClangABICompat4 = Context.getLangOpts().getClangABICompat() <= LangOptions::ClangABI::Ver4; diff --git a/clang/test/CodeGenCXX/debug-info-virtual-fn-relative.cpp b/clang/test/CodeGenCXX/debug-info-virtual-fn-relative.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/debug-info-virtual-fn-relative.cpp @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 -fvisibility hidden -emit-llvm -debug-info-kind=limited %s -o - | FileCheck --check-prefix=CHECK-STABLE %s +// RUN: %clang_cc1 -fvisibility hidden -flto-relative-c++-abi-vtables -emit-llvm -debug-info-kind=limited %s -o - | FileCheck --check-prefix=CHECK-UNSTABLE %s + +struct S { + S(); + virtual void f(); +}; + +// CHECK-STABLE: virtualIndex: 0, +// CHECK-UNSTABLE: virtualIndex: 4294967295, +S::S() {} +void S::f() {} diff --git a/clang/test/CodeGenCXX/type-metadata.cpp b/clang/test/CodeGenCXX/type-metadata.cpp --- a/clang/test/CodeGenCXX/type-metadata.cpp +++ b/clang/test/CodeGenCXX/type-metadata.cpp @@ -5,18 +5,22 @@ // RUN: %clang_cc1 -flto -flto-unit -triple x86_64-pc-windows-msvc -fsanitize=cfi-vcall -fsanitize-trap=cfi-vcall -emit-llvm -o - %s | FileCheck --check-prefix=CFI --check-prefix=CFI-NVT --check-prefix=MS --check-prefix=TT-MS --check-prefix=NDIAG %s // Tests for the whole-program-vtables feature: -// RUN: %clang_cc1 -flto -flto-unit -triple x86_64-unknown-linux -fvisibility hidden -fwhole-program-vtables -emit-llvm -o - %s | FileCheck --check-prefix=VTABLE-OPT --check-prefix=ITANIUM --check-prefix=TT-ITANIUM %s -// RUN: %clang_cc1 -flto -flto-unit -triple x86_64-pc-windows-msvc -fwhole-program-vtables -emit-llvm -o - %s | FileCheck --check-prefix=VTABLE-OPT --check-prefix=MS --check-prefix=TT-MS %s +// RUN: %clang_cc1 -flto -flto-unit -triple x86_64-unknown-linux -fvisibility hidden -fwhole-program-vtables -emit-llvm -o - %s | FileCheck --check-prefix=VTABLE-OPT --check-prefix=ITANIUM --check-prefix=TT-ITANIUM --check-prefix=ITANIUM-STABLE %s +// RUN: %clang_cc1 -flto -flto-unit -triple x86_64-unknown-linux -fvisibility hidden -fwhole-program-vtables -flto-relative-c++-abi-vtables -emit-llvm -o - %s | FileCheck --check-prefix=VTABLE-OPT --check-prefix=TT-ITANIUM --check-prefix=ITANIUM-UNSTABLE %s +// RUN: %clang_cc1 -flto -flto-unit -triple x86_64-pc-windows-msvc -fwhole-program-vtables -emit-llvm -o - %s | FileCheck --check-prefix=VTABLE-OPT --check-prefix=MS --check-prefix=TT-MS --check-prefix=MS-STABLE %s +// RUN: %clang_cc1 -flto -flto-unit -triple x86_64-pc-windows-msvc -fwhole-program-vtables -flto-relative-c++-abi-vtables -emit-llvm -o - %s | FileCheck --check-prefix=VTABLE-OPT --check-prefix=TT-MS --check-prefix=MS-UNSTABLE %s // Tests for cfi + whole-program-vtables: // RUN: %clang_cc1 -flto -flto-unit -triple x86_64-unknown-linux -fvisibility hidden -fsanitize=cfi-vcall -fsanitize-trap=cfi-vcall -fwhole-program-vtables -emit-llvm -o - %s | FileCheck --check-prefix=CFI --check-prefix=CFI-VT --check-prefix=ITANIUM --check-prefix=TC-ITANIUM %s // RUN: %clang_cc1 -flto -flto-unit -triple x86_64-pc-windows-msvc -fsanitize=cfi-vcall -fsanitize-trap=cfi-vcall -fwhole-program-vtables -emit-llvm -o - %s | FileCheck --check-prefix=CFI --check-prefix=CFI-VT --check-prefix=MS --check-prefix=TC-MS %s -// ITANIUM: @_ZTV1A = {{[^!]*}}, !type [[A16:![0-9]+]] +// ITANIUM: @_ZTV1A = {{.*}} { [3 x i8*] } {{[^!]*}}, !type [[A16:![0-9]+]] +// ITANIUM-UNSTABLE: @_ZTV1A = {{.*}} { { i8*, i8*, i32 } } {{[^!]*}}, !type [[A16:![0-9]+]] // ITANIUM-DIAG-SAME: !type [[ALL16:![0-9]+]] // ITANIUM-SAME: !type [[AF16:![0-9]+]] -// ITANIUM: @_ZTV1B = {{[^!]*}}, !type [[A32:![0-9]+]] +// ITANIUM: @_ZTV1B = {{.*}} { [7 x i8*] } {{[^!]*}}, !type [[A32:![0-9]+]] +// ITANIUM-UNSTABLE: @_ZTV1B = {{.*}} { { i8*, i8*, i8*, i8*, i32, i32, i32 } } {{[^!]*}}, !type [[A32:![0-9]+]] // ITANIUM-DIAG-SAME: !type [[ALL32:![0-9]+]] // ITANIUM-SAME: !type [[AF32:![0-9]+]] // ITANIUM-SAME: !type [[AF40:![0-9]+]] @@ -27,7 +31,8 @@ // ITANIUM-SAME: !type [[BF40:![0-9]+]] // ITANIUM-SAME: !type [[BF48:![0-9]+]] -// ITANIUM: @_ZTV1C = {{[^!]*}}, !type [[A32]] +// ITANIUM: @_ZTV1C = {{.*}} { [5 x i8*] } {{[^!]*}}, !type [[A32]] +// ITANIUM-UNSTABLE: @_ZTV1C = {{.*}} { { i8*, i8*, i8*, i8*, i32 } } {{[^!]*}}, !type [[A32]] // ITANIUM-DIAG-SAME: !type [[ALL32]] // ITANIUM-SAME: !type [[AF32]] // ITANIUM-SAME: !type [[C32:![0-9]+]] @@ -38,7 +43,8 @@ // DIAG: @[[TYPE:.*]] = private unnamed_addr constant { i16, i16, [4 x i8] } { i16 -1, i16 0, [4 x i8] c"'A'\00" } // DIAG: @[[BADTYPESTATIC:.*]] = private unnamed_addr global { i8, { [{{.*}} x i8]*, i32, i32 }, { i16, i16, [4 x i8] }* } { i8 0, { [{{.*}} x i8]*, i32, i32 } { [{{.*}} x i8]* @[[SRC]], i32 123, i32 3 }, { i16, i16, [4 x i8] }* @[[TYPE]] } -// ITANIUM: @_ZTVN12_GLOBAL__N_11DE = {{[^!]*}}, !type [[A32]] +// ITANIUM: @_ZTVN12_GLOBAL__N_11DE = {{.*}} { [7 x i8*], [5 x i8*] } {{[^!]*}}, !type [[A32]] +// ITANIUM-UNSTABLE: @_ZTVN12_GLOBAL__N_11DE = {{.*}} { { i8*, i8*, i8*, i8*, i32, i32, i32 }, { i8*, i8*, i8*, i8*, i32 } } {{[^!]*}}, !type [[A32]] // ITANIUM-DIAG-SAME: !type [[ALL32]] // ITANIUM-SAME: !type [[AF32]] // ITANIUM-SAME: !type [[AF40]] @@ -59,32 +65,43 @@ // ITANIUM-SAME: !type [[DF40:![0-9]+]] // ITANIUM-SAME: !type [[DF48:![0-9]+]] -// ITANIUM: @_ZTCN12_GLOBAL__N_11DE0_1B = {{[^!]*}}, !type [[A32]] +// ITANIUM: @_ZTCN12_GLOBAL__N_11DE0_1B = {{.*}} { [7 x i8*] } {{[^!]*}}, !type [[A32]] +// ITANIUM-UNSTABLE: @_ZTCN12_GLOBAL__N_11DE0_1B = {{.*}} { { i8*, i8*, i8*, i8*, i32, i32, i32 } } {{[^!]*}}, !type [[A32]] // ITANIUM-DIAG-SAME: !type [[ALL32]] // ITANIUM-SAME: !type [[B32]] // ITANIUM-DIAG-SAME: !type [[ALL32]] -// ITANIUM: @_ZTCN12_GLOBAL__N_11DE8_1C = {{[^!]*}}, !type [[A64:![0-9]+]] +// ITANIUM: @_ZTCN12_GLOBAL__N_11DE8_1C = {{.*}} { [5 x i8*], [4 x i8*] } {{[^!]*}}, !type [[A64:![0-9]+]] +// ITANIUM-UNSTABLE: @_ZTCN12_GLOBAL__N_11DE8_1C = {{.*}} { { i8*, i8*, i8*, i8*, i32 }, { i8*, i8*, i8*, i32 } } {{[^!]*}}, !type [[A64:![0-9]+]] // ITANIUM-DIAG-SAME: !type [[ALL64:![0-9]+]] // ITANIUM-SAME: !type [[AF64:![0-9]+]] // ITANIUM-SAME: !type [[C32]] // ITANIUM-DIAG-SAME: !type [[ALL32]] // ITANIUM-SAME: !type [[CF64:![0-9]+]] -// ITANIUM: @_ZTVZ3foovE2FA = {{[^!]*}}, !type [[A16]] +// ITANIUM: @_ZTVZ3foovE2FA = {{.*}} { [3 x i8*] } {{[^!]*}}, !type [[A16]] +// ITANIUM-UNSTABLE: @_ZTVZ3foovE2FA = {{.*}} { { i8*, i8*, i32 } } {{[^!]*}}, !type [[A16]] // ITANIUM-DIAG-SAME: !type [[ALL16]] // ITANIUM-SAME: !type [[AF16]] // ITANIUM-SAME: !type [[FA16:![0-9]+]] // ITANIUM-DIAG-SAME: !type [[ALL16]] // ITANIUM-SAME: !type [[FAF16:![0-9]+]] -// MS: comdat($"??_7A@@6B@"), !type [[A8:![0-9]+]] -// MS: comdat($"??_7B@@6B0@@"), !type [[B8:![0-9]+]] -// MS: comdat($"??_7B@@6BA@@@"), !type [[A8]] -// MS: comdat($"??_7C@@6B@"), !type [[A8]] -// MS: comdat($"??_7D@?A0x{{[^@]*}}@@6BB@@@"), !type [[B8]], !type [[D8:![0-9]+]] -// MS: comdat($"??_7D@?A0x{{[^@]*}}@@6BA@@@"), !type [[A8]] -// MS: comdat($"??_7FA@?1??foo@@YAXXZ@6B@"), !type [[A8]], !type [[FA8:![0-9]+]] +// MS-STABLE: @anon.[[NUM:[a-z0-9]+]].0 = {{.*}} { [2 x i8*] } {{.*}} comdat($"??_7A@@6B@"), !type [[A8:![0-9]+]] +// MS-STABLE: @anon.[[NUM]].1 = {{.*}} { [3 x i8*] } {{.*}} comdat($"??_7B@@6B0@@"), !type [[B8:![0-9]+]] +// MS-STABLE: @anon.[[NUM]].2 = {{.*}} { [2 x i8*] } {{.*}} comdat($"??_7B@@6BA@@@"), !type [[A8]] +// MS-STABLE: @anon.[[NUM]].3 = {{.*}} { [2 x i8*] } {{.*}} comdat($"??_7C@@6B@"), !type [[A8]] +// MS-STABLE: @anon.[[NUM]].4 = {{.*}} { [3 x i8*] } {{.*}} comdat($"??_7D@?A0x{{[^@]*}}@@6BB@@@"), !type [[B8]], !type [[D8:![0-9]+]] +// MS-STABLE: @anon.[[NUM]].5 = {{.*}} { [2 x i8*] } {{.*}} comdat($"??_7D@?A0x{{[^@]*}}@@6BA@@@"), !type [[A8]] +// MS-STABLE: @anon.[[NUM]].6 = {{.*}} { [2 x i8*] } {{.*}} comdat($"??_7FA@?1??foo@@YAXXZ@6B@"), !type [[A8]], !type [[FA8:![0-9]+]] + +// MS-UNSTABLE: @anon.[[NUM:[a-z0-9]+]].0 = {{.*}} { { i8*, i32 } } {{.*}} comdat($"??_7A@@6B@"), !type [[A8:![0-9]+]] +// MS-UNSTABLE: @anon.[[NUM]].1 = {{.*}} { { i8*, i32, i32 } } {{.*}} comdat($"??_7B@@6B0@@"), !type [[B8:![0-9]+]] +// MS-UNSTABLE: @anon.[[NUM]].2 = {{.*}} { { i8*, i32 } } {{.*}} comdat($"??_7B@@6BA@@@"), !type [[A8]] +// MS-UNSTABLE: @anon.[[NUM]].3 = {{.*}} { { i8*, i32 } } {{.*}} comdat($"??_7C@@6B@"), !type [[A8]] +// MS-UNSTABLE: @anon.[[NUM]].4 = {{.*}} { { i8*, i32, i32 } } {{.*}} comdat($"??_7D@?A0x{{[^@]*}}@@6BB@@@"), !type [[B8]], !type [[D8:![0-9]+]] +// MS-UNSTABLE: @anon.[[NUM]].5 = {{.*}} { { i8*, i32 } } {{.*}} comdat($"??_7D@?A0x{{[^@]*}}@@6BA@@@"), !type [[A8]] +// MS-UNSTABLE: @anon.[[NUM]].6 = {{.*}} { { i8*, i32 } } {{.*}} comdat($"??_7FA@?1??foo@@YAXXZ@6B@"), !type [[A8]], !type [[FA8:![0-9]+]] struct A { A(); @@ -283,9 +300,9 @@ // ITANIUM: [[FAF16]] = !{i64 16, [[FAF_ID:![0-9]+]]} // ITANIUM: [[FAF_ID]] = distinct !{} -// MS: [[A8]] = !{i64 8, !"?AUA@@"} -// MS: [[B8]] = !{i64 8, !"?AUB@@"} -// MS: [[D8]] = !{i64 8, [[D_ID:![0-9]+]]} -// MS: [[D_ID]] = distinct !{} -// MS: [[FA8]] = !{i64 8, [[FA_ID:![0-9]+]]} -// MS: [[FA_ID]] = distinct !{} +// MS-STABLE: [[A8]] = !{i64 8, !"?AUA@@"} +// MS-STABLE: [[B8]] = !{i64 8, !"?AUB@@"} +// MS-STABLE: [[D8]] = !{i64 8, [[D_ID:![0-9]+]]} +// MS-STABLE: [[D_ID]] = distinct !{} +// MS-STABLE: [[FA8]] = !{i64 8, [[FA_ID:![0-9]+]]} +// MS-STABLE: [[FA_ID]] = distinct !{} diff --git a/clang/test/CodeGenCXX/vtable-relative-abi.cpp b/clang/test/CodeGenCXX/vtable-relative-abi.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/vtable-relative-abi.cpp @@ -0,0 +1,123 @@ +// RUN: %clang_cc1 %s -flto -flto-unit -triple x86_64-unknown-linux-gnu -fvisibility hidden -flto-relative-c++-abi-vtables -emit-llvm -o - | FileCheck --check-prefix=CHECK --check-prefix=CHECK-ITANIUM %s +// RUN: %clang_cc1 %s -flto -flto-unit -triple x86_64-unknown-windows-msvc -flto-relative-c++-abi-vtables -emit-llvm -o - | FileCheck --check-prefix=CHECK --check-prefix=CHECK-MS %s +// RUN: %clang_cc1 %s -flto -flto-unit -triple x86_64-unknown-linux-gnu -fvisibility hidden -emit-llvm -o - | FileCheck --check-prefix=CHECK-NOABI %s + +// CHECK-ITANIUM: @_ZTV1S = hidden unnamed_addr constant { { i8*, i8*, i32, i32 } } { { i8*, i8*, i32, i32 } { i8* null, i8* bitcast ({ i8*, i8* }* @_ZTI1S to i8*), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.S*)* @_ZN1S2f1Ev to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i8*, i32, i32 } }, { { i8*, i8*, i32, i32 } }* @_ZTV1S, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.S*)* @_ZN1S2f2Ev to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i8*, i32, i32 } }, { { i8*, i8*, i32, i32 } }* @_ZTV1S, i32 0, i32 0, i32 3) to i64)) to i32) } }, align 8 +// CHECK-MS: @anon.[[NUM:[a-z0-9]+]].0 = private unnamed_addr constant { { i8*, i32, i32 } } { { i8*, i32, i32 } { i8* bitcast (%rtti.CompleteObjectLocator* @"??_R4S@@6B@" to i8*), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.S*)* @"?f1@S@@UEAAXXZ" to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i32, i32 } }, { { i8*, i32, i32 } }* @anon.[[NUM]].0, i32 0, i32 0, i32 1) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.S*)* @"?f2@S@@UEAAXXZ" to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i32, i32 } }, { { i8*, i32, i32 } }* @anon.[[NUM]].0, i32 0, i32 0, i32 2) to i64)) to i32) } }, comdat($"??_7S@@6B@") +struct S { + S(); + virtual void f1(); + virtual void f2(); +}; + +// CHECK-ITANIUM: @_ZTV1T = hidden unnamed_addr constant { { i8*, i8*, i32 } } { { i8*, i8*, i32 } { i8* null, i8* bitcast ({ i8*, i8* }* @_ZTI1T to i8*), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.T*)* @_ZN1T1gEv to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i8*, i32 } }, { { i8*, i8*, i32 } }* @_ZTV1T, i32 0, i32 0, i32 2) to i64)) to i32) } }, align 8 +// CHECK-MS: @anon.[[NUM]].1 = private unnamed_addr constant { { i8*, i32 } } { { i8*, i32 } { i8* bitcast (%rtti.CompleteObjectLocator* @"??_R4T@@6B@" to i8*), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.T*)* @"?g@T@@UEAAXXZ" to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i32 } }, { { i8*, i32 } }* @anon.[[NUM]].1, i32 0, i32 0, i32 1) to i64)) to i32) } }, comdat($"??_7T@@6B@") +struct T { + T(); + virtual void g(); +}; + +// CHECK-ITANIUM: @_ZTV1U = hidden unnamed_addr constant { { i8*, i8*, i32, i32 }, { i8*, i8*, i32 } } { { i8*, i8*, i32, i32 } { i8* null, i8* bitcast ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }* @_ZTI1U to i8*), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.U*)* @_ZN1U2f1Ev to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i8*, i32, i32 }, { i8*, i8*, i32 } }, { { i8*, i8*, i32, i32 }, { i8*, i8*, i32 } }* @_ZTV1U, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.S*)* @_ZN1S2f2Ev to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i8*, i32, i32 }, { i8*, i8*, i32 } }, { { i8*, i8*, i32, i32 }, { i8*, i8*, i32 } }* @_ZTV1U, i32 0, i32 0, i32 3) to i64)) to i32) }, { i8*, i8*, i32 } { i8* inttoptr (i64 -8 to i8*), i8* bitcast ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }* @_ZTI1U to i8*), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.T*)* @_ZN1T1gEv to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i8*, i32, i32 }, { i8*, i8*, i32 } }, { { i8*, i8*, i32, i32 }, { i8*, i8*, i32 } }* @_ZTV1U, i32 0, i32 1, i32 2) to i64)) to i32) } }, align 8 +// CHECK-MS: @anon.[[NUM]].2 = private unnamed_addr constant { { i8*, i32, i32 } } { { i8*, i32, i32 } { i8* bitcast (%rtti.CompleteObjectLocator* @"??_R4U@@6BS@@@" to i8*), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.U*)* @"?f1@U@@UEAAXXZ" to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i32, i32 } }, { { i8*, i32, i32 } }* @anon.[[NUM]].2, i32 0, i32 0, i32 1) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.S*)* @"?f2@S@@UEAAXXZ" to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i32, i32 } }, { { i8*, i32, i32 } }* @anon.[[NUM]].2, i32 0, i32 0, i32 2) to i64)) to i32) } }, comdat($"??_7U@@6BS@@@") +// CHECK-MS: @anon.[[NUM]].3 = private unnamed_addr constant { { i8*, i32 } } { { i8*, i32 } { i8* bitcast (%rtti.CompleteObjectLocator* @"??_R4U@@6BT@@@" to i8*), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.T*)* @"?g@T@@UEAAXXZ" to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i32 } }, { { i8*, i32 } }* @anon.[[NUM]].3, i32 0, i32 0, i32 1) to i64)) to i32) } }, comdat($"??_7U@@6BT@@@") +struct U : S, T { + U(); + virtual void f1(); +}; + +S::S() {} +void S::f1() {} + +T::T() {} +void T::g() {} + +U::U() {} +void U::f1() {} + +struct V { + virtual void f(); +}; + +struct V1 : virtual V { +}; + +struct V2 : virtual V { +}; + +// CHECK-ITANIUM: @_ZTC2V30_2V1 = linkonce_odr hidden unnamed_addr constant { { i8*, i8*, i8*, i8*, i32 } } { { i8*, i8*, i8*, i8*, i32 } { i8* null, i8* null, i8* null, i8* bitcast ({ i8*, i8*, i32, i32, i8*, i64 }* @_ZTI2V1 to i8*), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.V*)* @_ZN1V1fEv to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i8*, i8*, i8*, i32 } }, { { i8*, i8*, i8*, i8*, i32 } }* @_ZTC2V30_2V1, i32 0, i32 0, i32 4) to i64)) to i32) } }, comdat, align 8 +// CHECK-ITANIUM: @_ZTC2V38_2V2 = linkonce_odr hidden unnamed_addr constant { { i8*, i8*, i8*, i8*, i32 }, { i8*, i8*, i8*, i32 } } { { i8*, i8*, i8*, i8*, i32 } { i8* inttoptr (i64 -8 to i8*), i8* inttoptr (i64 -8 to i8*), i8* null, i8* bitcast ({ i8*, i8*, i32, i32, i8*, i64 }* @_ZTI2V2 to i8*), i32 0 }, { i8*, i8*, i8*, i32 } { i8* null, i8* inttoptr (i64 8 to i8*), i8* bitcast ({ i8*, i8*, i32, i32, i8*, i64 }* @_ZTI2V2 to i8*), i32 trunc (i64 sub (i64 ptrtoint (void (%struct.V*)* @_ZN1V1fEv to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i8*, i8*, i8*, i32 }, { i8*, i8*, i8*, i32 } }, { { i8*, i8*, i8*, i8*, i32 }, { i8*, i8*, i8*, i32 } }* @_ZTC2V38_2V2, i32 0, i32 1, i32 3) to i64)) to i32) } }, comdat, align 8 +struct V3 : V1, V2 { + V3(); +}; + +V3::V3() {} + +// CHECK-ITANIUM: define hidden void @_Z5call1P1S +// CHECK-MS: define {{.*}} void @"?call1@@YAXPEAUS@@@Z" +void call1(S *s) { + // CHECK: [[VTABLE:%.*]] = load void + // CHECK: [[VTABLEI8:%.*]] = bitcast {{.*}} [[VTABLE]] to i8* + // CHECK: [[VFN:%.*]] = call i8* @llvm.load.relative.i32(i8* [[VTABLEI8]], i32 0) + // CHECK: [[VFNCASTED:%.*]] = bitcast i8* [[VFN]] to void ( + // CHECK: call void [[VFNCASTED]]( + s->f1(); +} + +// CHECK-ITANIUM: define hidden void @_Z5call2P1S +// CHECK-MS: define {{.*}} void @"?call2@@YAXPEAUS@@@Z" +void call2(S *s) { + // CHECK: [[VTABLE:%.*]] = load void + // CHECK: [[VTABLEI8:%.*]] = bitcast {{.*}} [[VTABLE]] to i8* + // CHECK: [[VFN:%.*]] = call i8* @llvm.load.relative.i32(i8* [[VTABLEI8]], i32 4) + // CHECK: [[VFNCASTED:%.*]] = bitcast i8* [[VFN]] to void ( + // CHECK: call void [[VFNCASTED]]( + s->f2(); +} + +typedef void (S::*mfp)(); + +// CHECK-ITANIUM: define hidden { i64, i64 } @_Z7getmfp1v() +// CHECK-ITANIUM: ret { i64, i64 } { i64 1, i64 0 } +// CHECK-MS: define {{.*}} i8* @"?getmfp1@@YAP8S@@EAAXXZXZ"() +// CHECK-MS: ret i8* bitcast {{.*}} @"??_9S@@$BA@AA" +// CHECK-MS: define linkonce_odr void @"??_9S@@$BA@AA" +// CHECK-MS: [[VTABLE:%.*]] = load void +// CHECK-MS: [[VTABLEI8:%.*]] = bitcast {{.*}} [[VTABLE]] to i8* +// CHECK-MS: [[VFN:%.*]] = call i8* @llvm.load.relative.i32(i8* [[VTABLEI8]], i32 0) +// CHECK-MS: [[VFNCASTED:%.*]] = bitcast i8* [[VFN]] to void ( +// CHECK-MS: musttail call {{.*}} [[VFNCASTED]]( +mfp getmfp1() { + return &S::f1; +} + +// CHECK-ITANIUM: define hidden { i64, i64 } @_Z7getmfp2v() +// CHECK-ITANIUM: ret { i64, i64 } { i64 5, i64 0 } +// CHECK-MS: define {{.*}} i8* @"?getmfp2@@YAP8S@@EAAXXZXZ"() +// CHECK-MS: ret i8* bitcast {{.*}} @"??_9S@@$B7AA" +// CHECK-MS: define linkonce_odr void @"??_9S@@$B7AA" +// CHECK-MS: [[VTABLE:%.*]] = load void +// CHECK-MS: [[VTABLEI8:%.*]] = bitcast {{.*}} [[VTABLE]] to i8* +// CHECK-MS: [[VFN:%.*]] = call i8* @llvm.load.relative.i32(i8* [[VTABLEI8]], i32 4) +// CHECK-MS: [[VFNCASTED:%.*]] = bitcast i8* [[VFN]] to void ( +// CHECK-MS: musttail call {{.*}} [[VFNCASTED]]( +mfp getmfp2() { + return &S::f2; +} + +// In the MS ABI virtual member function calls use thunks (which we already +// tested above), so there's nothing to test that's specific to the relative +// ABI. + +// CHECK-ITANIUM: define hidden void @_Z7callmfpP1SMS_FvvE +void callmfp(S *s, mfp p) { + // CHECK-ITANIUM: [[VTOFFSET:%.*]] = sub i64 {{.*}}, 1 + // CHECK-ITANIUM: [[VFN:%.*]] = call i8* @llvm.load.relative.i64(i8* {{.*}}, i64 [[VTOFFSET]]) + // CHECK-ITANIUM: bitcast i8* [[VFN]] to void ( + (s->*p)(); +} + +// CHECK-NOABI: !llvm.module.flags = !{{{![0-9]+}}, [[MF:![0-9]+]]} +// CHECK-NOABI: [[MF]] = !{i32 1, !"lto-relative-c++-abi-vtables", i32 0} + +// CHECK: !llvm.module.flags = !{{{![0-9]+}}, [[MF:![0-9]+]]} +// CHECK: [[MF]] = !{i32 1, !"lto-relative-c++-abi-vtables", i32 1} diff --git a/clang/test/Driver/unstable-cxx-abi-vtables.cpp b/clang/test/Driver/unstable-cxx-abi-vtables.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Driver/unstable-cxx-abi-vtables.cpp @@ -0,0 +1,5 @@ +// RUN: %clang -flto-relative-c++-abi-vtables -### %s 2>&1 | FileCheck -check-prefix=CLASSES %s +// CLASSES: '-fwhole-program-vtables' only allowed with '-flto' +// +// RUN: %clang -flto -flto-relative-c++-abi-vtables -### %s 2>&1 | FileCheck -check-prefix=LTO-CLASSES %s +// LTO-CLASSES: "-flto-relative-c++-abi-vtables" diff --git a/clang/test/SemaCXX/unstable-cxx-abi.cpp b/clang/test/SemaCXX/unstable-cxx-abi.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/unstable-cxx-abi.cpp @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 %s -w -std=c++11 -fsyntax-only -verify -flto-relative-c++-abi-vtables -fvisibility hidden + +struct __attribute__((visibility("default"))) platform { // expected-note 2{{base 'platform' uses the platform ABI}} + virtual void f(); +}; +struct relative { // expected-note 2{{base 'relative' uses the relative ABI}} + virtual void f(); +}; + +struct mixed_bases : platform, relative {}; // expected-error {{inconsistent ABI for class 'mixed_bases'}} +struct mixed_base_vbase : platform, virtual relative {}; // expected-error {{inconsistent ABI for class 'mixed_base_vbase'}} + +void f() { + struct relative2 { + virtual void f(); + }; + struct relative_derived : relative, relative2 {}; +} + +struct __attribute__((visibility("default"))) platform2 { + void mf() { + struct platform3 { + virtual void f(); + }; + struct platform_derived : platform, platform3 {}; + } +}; + +struct __attribute__((visibility("default"))) non_dynamic {}; + +struct relative_derived : non_dynamic, relative {}; +struct platform_derived : non_dynamic, platform {}; + +struct relative_derived2 : relative_derived, relative {}; +struct platform_derived2 : platform_derived, platform {};