From 85b5116ba2fa38ef76c78fd6bd5dc9b77237d5f8 Mon Sep 17 00:00:00 2001 From: Steven Wu Date: Fri, 28 Aug 2015 07:14:10 +0000 Subject: [PATCH] Revert r246214 and r246213 These two commits causes llvm LTO bootstrap to hang in ScalarEvolution. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@246282 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/CodeGen/CGCXXABI.h | 30 +- lib/CodeGen/CGCall.cpp | 3 +- lib/CodeGen/CGClass.cpp | 126 +++----- lib/CodeGen/CGVTables.cpp | 6 +- lib/CodeGen/CodeGenFunction.h | 41 +-- lib/CodeGen/ItaniumCXXABI.cpp | 95 +++--- lib/CodeGen/MicrosoftCXXABI.cpp | 54 +--- test/CodeGen/available-externally-hidden.cpp | 2 +- test/CodeGenCXX/ctor-globalopt.cpp | 4 +- test/CodeGenCXX/thunks.cpp | 32 +- test/CodeGenCXX/virtual-base-ctor.cpp | 2 +- test/CodeGenCXX/vtable-assume-load.cpp | 295 ------------------ .../vtable-available-externally.cpp | 25 +- 13 files changed, 144 insertions(+), 571 deletions(-) delete mode 100644 test/CodeGenCXX/vtable-assume-load.cpp diff --git a/lib/CodeGen/CGCXXABI.h b/lib/CodeGen/CGCXXABI.h index 259c878f7f..5ef409ecde 100644 --- a/lib/CodeGen/CGCXXABI.h +++ b/lib/CodeGen/CGCXXABI.h @@ -218,10 +218,8 @@ public: virtual void emitThrow(CodeGenFunction &CGF, const CXXThrowExpr *E) = 0; virtual llvm::GlobalVariable *getThrowInfo(QualType T) { return nullptr; } - /// \brief Determine whether it's possible to emit a vtable for \p RD, even - /// though we do not know that the vtable has been marked as used by semantic - /// analysis. - virtual bool canSpeculativelyEmitVTable(const CXXRecordDecl *RD) const = 0; + virtual bool canEmitAvailableExternallyVTable( + const CXXRecordDecl *RD) const = 0; virtual void emitBeginCatch(CodeGenFunction &CGF, const CXXCatchStmt *C) = 0; @@ -348,25 +346,13 @@ public: virtual void emitVTableDefinitions(CodeGenVTables &CGVT, const CXXRecordDecl *RD) = 0; - /// Checks if ABI requires extra virtual offset for vtable field. - virtual bool - isVirtualOffsetNeededForVTableField(CodeGenFunction &CGF, - CodeGenFunction::VPtr Vptr) = 0; - - /// Checks if ABI requires to initilize vptrs for given dynamic class. - virtual bool doStructorsInitializeVPtrs(const CXXRecordDecl *VTableClass) = 0; - - /// Get the address point of the vtable for the given base subobject. - virtual llvm::Constant * - getVTableAddressPoint(BaseSubobject Base, - const CXXRecordDecl *VTableClass) = 0; - /// Get the address point of the vtable for the given base subobject while - /// building a constructor or a destructor. - virtual llvm::Value * - getVTableAddressPointInStructor(CodeGenFunction &CGF, const CXXRecordDecl *RD, - BaseSubobject Base, - const CXXRecordDecl *NearestVBase) = 0; + /// building a constructor or a destructor. On return, NeedsVirtualOffset + /// tells if a virtual base adjustment is needed in order to get the offset + /// of the base subobject. + virtual llvm::Value *getVTableAddressPointInStructor( + CodeGenFunction &CGF, const CXXRecordDecl *RD, BaseSubobject Base, + const CXXRecordDecl *NearestVBase, bool &NeedsVirtualOffset) = 0; /// Get the address point of the vtable for the given base subobject while /// building a constexpr. diff --git a/lib/CodeGen/CGCall.cpp b/lib/CodeGen/CGCall.cpp index fac2516863..74a47bfc2b 100644 --- a/lib/CodeGen/CGCall.cpp +++ b/lib/CodeGen/CGCall.cpp @@ -1412,8 +1412,7 @@ void CodeGenModule::ConstructAttributeList(const CGFunctionInfo &FI, if (const FunctionDecl *Fn = dyn_cast(TargetDecl)) { const FunctionProtoType *FPT = Fn->getType()->getAs(); - if (FPT && !isUnresolvedExceptionSpec(FPT->getExceptionSpecType()) && - FPT->isNothrow(getContext())) + if (FPT && FPT->isNothrow(getContext())) FuncAttrs.addAttribute(llvm::Attribute::NoUnwind); // Don't use [[noreturn]] or _Noreturn for a call to a virtual function. // These attributes are not inherited by overloads. diff --git a/lib/CodeGen/CGClass.cpp b/lib/CodeGen/CGClass.cpp index 507bae8d09..ea943c2986 100644 --- a/lib/CodeGen/CGClass.cpp +++ b/lib/CodeGen/CGClass.cpp @@ -1806,14 +1806,12 @@ void CodeGenFunction::EmitCXXConstructorCall(const CXXConstructorDecl *D, bool ForVirtualBase, bool Delegating, llvm::Value *This, const CXXConstructExpr *E) { - const CXXRecordDecl *ClassDecl = D->getParent(); - // C++11 [class.mfct.non-static]p2: // If a non-static member function of a class X is called for an object that // is not of type X, or of a type derived from X, the behavior is undefined. // FIXME: Provide a source location here. EmitTypeCheck(CodeGenFunction::TCK_ConstructorCall, SourceLocation(), This, - getContext().getRecordType(ClassDecl)); + getContext().getRecordType(D->getParent())); if (D->isTrivial() && D->isDefaultConstructor()) { assert(E->getNumArgs() == 0 && "trivial default ctor with args"); @@ -1829,7 +1827,7 @@ void CodeGenFunction::EmitCXXConstructorCall(const CXXConstructorDecl *D, const Expr *Arg = E->getArg(0); QualType SrcTy = Arg->getType(); llvm::Value *Src = EmitLValue(Arg).getAddress(); - QualType DestTy = getContext().getTypeDeclType(ClassDecl); + QualType DestTy = getContext().getTypeDeclType(D->getParent()); EmitAggregateCopyCtor(This, Src, DestTy, SrcTy); return; } @@ -1852,48 +1850,6 @@ void CodeGenFunction::EmitCXXConstructorCall(const CXXConstructorDecl *D, const CGFunctionInfo &Info = CGM.getTypes().arrangeCXXConstructorCall(Args, D, Type, ExtraArgs); EmitCall(Info, Callee, ReturnValueSlot(), Args, D); - - // Generate vtable assumptions if we're constructing a complete object - // with a vtable. We don't do this for base subobjects for two reasons: - // first, it's incorrect for classes with virtual bases, and second, we're - // about to overwrite the vptrs anyway. - // We also have to make sure if we can refer to vtable: - // - If vtable is external then it's safe to use it (for available_externally - // CGVTables will make sure if it can emit it). - // - Otherwise we can refer to vtable if it's safe to speculatively emit. - // FIXME: If vtable is used by ctor/dtor, we are always safe to refer to it. - if (CGM.getCodeGenOpts().OptimizationLevel > 0 && - ClassDecl->isDynamicClass() && Type != Ctor_Base && - (CGM.getVTables().isVTableExternal(ClassDecl) || - CGM.getCXXABI().canSpeculativelyEmitVTable(ClassDecl))) - EmitVTableAssumptionLoads(ClassDecl, This); -} - -void CodeGenFunction::EmitVTableAssumptionLoad(const VPtr &Vptr, - llvm::Value *This) { - llvm::Value *VTableGlobal = - CGM.getCXXABI().getVTableAddressPoint(Vptr.Base, Vptr.VTableClass); - if (!VTableGlobal) - return; - - // We can just use the base offset in the complete class. - CharUnits NonVirtualOffset = Vptr.Base.getBaseOffset(); - - if (!NonVirtualOffset.isZero()) - This = - ApplyNonVirtualAndVirtualOffset(*this, This, NonVirtualOffset, nullptr); - - llvm::Value *VPtrValue = GetVTablePtr(This, VTableGlobal->getType()); - llvm::Value *Cmp = - Builder.CreateICmpEQ(VPtrValue, VTableGlobal, "cmp.vtables"); - Builder.CreateAssumption(Cmp); -} - -void CodeGenFunction::EmitVTableAssumptionLoads(const CXXRecordDecl *ClassDecl, - llvm::Value *This) { - if (CGM.getCXXABI().doStructorsInitializeVPtrs(ClassDecl)) - for (const VPtr &Vptr : getVTablePointers(ClassDecl)) - EmitVTableAssumptionLoad(Vptr, This); } void @@ -2061,12 +2017,24 @@ void CodeGenFunction::PushDestructorCleanup(QualType T, llvm::Value *Addr) { PushDestructorCleanup(D, Addr); } -void CodeGenFunction::InitializeVTablePointer(const VPtr &Vptr) { +void +CodeGenFunction::InitializeVTablePointer(BaseSubobject Base, + const CXXRecordDecl *NearestVBase, + CharUnits OffsetFromNearestVBase, + const CXXRecordDecl *VTableClass) { + const CXXRecordDecl *RD = Base.getBase(); + + // Don't initialize the vtable pointer if the class is marked with the + // 'novtable' attribute. + if ((RD == VTableClass || RD == NearestVBase) && + VTableClass->hasAttr()) + return; + // Compute the address point. + bool NeedsVirtualOffset; llvm::Value *VTableAddressPoint = CGM.getCXXABI().getVTableAddressPointInStructor( - *this, Vptr.VTableClass, Vptr.Base, Vptr.NearestVBase); - + *this, VTableClass, Base, NearestVBase, NeedsVirtualOffset); if (!VTableAddressPoint) return; @@ -2074,15 +2042,17 @@ void CodeGenFunction::InitializeVTablePointer(const VPtr &Vptr) { llvm::Value *VirtualOffset = nullptr; CharUnits NonVirtualOffset = CharUnits::Zero(); - if (CGM.getCXXABI().isVirtualOffsetNeededForVTableField(*this, Vptr)) { + if (NeedsVirtualOffset) { // We need to use the virtual base offset offset because the virtual base // might have a different offset in the most derived class. - VirtualOffset = CGM.getCXXABI().GetVirtualBaseClassOffset( - *this, LoadCXXThis(), Vptr.VTableClass, Vptr.NearestVBase); - NonVirtualOffset = Vptr.OffsetFromNearestVBase; + VirtualOffset = CGM.getCXXABI().GetVirtualBaseClassOffset(*this, + LoadCXXThis(), + VTableClass, + NearestVBase); + NonVirtualOffset = OffsetFromNearestVBase; } else { // We can just use the base offset in the complete class. - NonVirtualOffset = Vptr.Base.getBaseOffset(); + NonVirtualOffset = Base.getBaseOffset(); } // Apply the offsets. @@ -2101,36 +2071,23 @@ void CodeGenFunction::InitializeVTablePointer(const VPtr &Vptr) { ->getPointerTo(); VTableField = Builder.CreateBitCast(VTableField, VTablePtrTy->getPointerTo()); VTableAddressPoint = Builder.CreateBitCast(VTableAddressPoint, VTablePtrTy); - llvm::StoreInst *Store = Builder.CreateStore(VTableAddressPoint, VTableField); CGM.DecorateInstruction(Store, CGM.getTBAAInfoForVTablePtr()); } -CodeGenFunction::VPtrsVector -CodeGenFunction::getVTablePointers(const CXXRecordDecl *VTableClass) { - CodeGenFunction::VPtrsVector VPtrsResult; - VisitedVirtualBasesSetTy VBases; - getVTablePointers(BaseSubobject(VTableClass, CharUnits::Zero()), - /*NearestVBase=*/nullptr, - /*OffsetFromNearestVBase=*/CharUnits::Zero(), - /*BaseIsNonVirtualPrimaryBase=*/false, VTableClass, VBases, - VPtrsResult); - return VPtrsResult; -} - -void CodeGenFunction::getVTablePointers(BaseSubobject Base, - const CXXRecordDecl *NearestVBase, - CharUnits OffsetFromNearestVBase, - bool BaseIsNonVirtualPrimaryBase, - const CXXRecordDecl *VTableClass, - VisitedVirtualBasesSetTy &VBases, - VPtrsVector &Vptrs) { +void +CodeGenFunction::InitializeVTablePointers(BaseSubobject Base, + const CXXRecordDecl *NearestVBase, + CharUnits OffsetFromNearestVBase, + bool BaseIsNonVirtualPrimaryBase, + const CXXRecordDecl *VTableClass, + VisitedVirtualBasesSetTy& VBases) { // If this base is a non-virtual primary base the address point has already // been set. if (!BaseIsNonVirtualPrimaryBase) { // Initialize the vtable pointer for this base. - VPtr Vptr = {Base, NearestVBase, OffsetFromNearestVBase, VTableClass}; - Vptrs.push_back(Vptr); + InitializeVTablePointer(Base, NearestVBase, OffsetFromNearestVBase, + VTableClass); } const CXXRecordDecl *RD = Base.getBase(); @@ -2168,10 +2125,11 @@ void CodeGenFunction::getVTablePointers(BaseSubobject Base, BaseDeclIsNonVirtualPrimaryBase = Layout.getPrimaryBase() == BaseDecl; } - getVTablePointers( - BaseSubobject(BaseDecl, BaseOffset), - I.isVirtual() ? BaseDecl : NearestVBase, BaseOffsetFromNearestVBase, - BaseDeclIsNonVirtualPrimaryBase, VTableClass, VBases, Vptrs); + InitializeVTablePointers(BaseSubobject(BaseDecl, BaseOffset), + I.isVirtual() ? BaseDecl : NearestVBase, + BaseOffsetFromNearestVBase, + BaseDeclIsNonVirtualPrimaryBase, + VTableClass, VBases); } } @@ -2181,9 +2139,11 @@ void CodeGenFunction::InitializeVTablePointers(const CXXRecordDecl *RD) { return; // Initialize the vtable pointers for this class and all of its bases. - if (CGM.getCXXABI().doStructorsInitializeVPtrs(RD)) - for (const VPtr &Vptr : getVTablePointers(RD)) - InitializeVTablePointer(Vptr); + VisitedVirtualBasesSetTy VBases; + InitializeVTablePointers(BaseSubobject(RD, CharUnits::Zero()), + /*NearestVBase=*/nullptr, + /*OffsetFromNearestVBase=*/CharUnits::Zero(), + /*BaseIsNonVirtualPrimaryBase=*/false, RD, VBases); if (RD->getNumVBases()) CGM.getCXXABI().initializeHiddenVirtualInheritanceMembers(*this, RD); diff --git a/lib/CodeGen/CGVTables.cpp b/lib/CodeGen/CGVTables.cpp index bd620a1ad4..fcb5c36649 100644 --- a/lib/CodeGen/CGVTables.cpp +++ b/lib/CodeGen/CGVTables.cpp @@ -682,7 +682,7 @@ CodeGenVTables::GenerateConstructionVTable(const CXXRecordDecl *RD, static bool shouldEmitAvailableExternallyVTable(const CodeGenModule &CGM, const CXXRecordDecl *RD) { return CGM.getCodeGenOpts().OptimizationLevel > 0 && - CGM.getCXXABI().canSpeculativelyEmitVTable(RD); + CGM.getCXXABI().canEmitAvailableExternallyVTable(RD); } /// Compute the required linkage of the v-table for the given class. @@ -832,11 +832,11 @@ bool CodeGenVTables::isVTableExternal(const CXXRecordDecl *RD) { /// we define that v-table? static bool shouldEmitVTableAtEndOfTranslationUnit(CodeGenModule &CGM, const CXXRecordDecl *RD) { - // If vtable is internal then it has to be done. + // If vtable is internal then it has to be done if (!CGM.getVTables().isVTableExternal(RD)) return true; - // If it's external then maybe we will need it as available_externally. + // If it's external then maybe we will need it as available_externally return shouldEmitAvailableExternallyVTable(CGM, RD); } diff --git a/lib/CodeGen/CodeGenFunction.h b/lib/CodeGen/CodeGenFunction.h index ca3d3c40de..154c0cb80e 100644 --- a/lib/CodeGen/CodeGenFunction.h +++ b/lib/CodeGen/CodeGenFunction.h @@ -1310,27 +1310,21 @@ public: void EmitInitializerForField(FieldDecl *Field, LValue LHS, Expr *Init, ArrayRef ArrayIndexes); - /// Struct with all informations about dynamic [sub]class needed to set vptr. - struct VPtr { - BaseSubobject Base; - const CXXRecordDecl *NearestVBase; - CharUnits OffsetFromNearestVBase; - const CXXRecordDecl *VTableClass; - }; - - /// Initialize the vtable pointer of the given subobject. - void InitializeVTablePointer(const VPtr &vptr); - - typedef llvm::SmallVector VPtrsVector; + /// InitializeVTablePointer - Initialize the vtable pointer of the given + /// subobject. + /// + void InitializeVTablePointer(BaseSubobject Base, + const CXXRecordDecl *NearestVBase, + CharUnits OffsetFromNearestVBase, + const CXXRecordDecl *VTableClass); typedef llvm::SmallPtrSet VisitedVirtualBasesSetTy; - VPtrsVector getVTablePointers(const CXXRecordDecl *VTableClass); - - void getVTablePointers(BaseSubobject Base, const CXXRecordDecl *NearestVBase, - CharUnits OffsetFromNearestVBase, - bool BaseIsNonVirtualPrimaryBase, - const CXXRecordDecl *VTableClass, - VisitedVirtualBasesSetTy &VBases, VPtrsVector &vptrs); + void InitializeVTablePointers(BaseSubobject Base, + const CXXRecordDecl *NearestVBase, + CharUnits OffsetFromNearestVBase, + bool BaseIsNonVirtualPrimaryBase, + const CXXRecordDecl *VTableClass, + VisitedVirtualBasesSetTy& VBases); void InitializeVTablePointers(const CXXRecordDecl *ClassDecl); @@ -1759,19 +1753,10 @@ public: // they are substantially the same. void EmitDelegatingCXXConstructorCall(const CXXConstructorDecl *Ctor, const FunctionArgList &Args); - void EmitCXXConstructorCall(const CXXConstructorDecl *D, CXXCtorType Type, bool ForVirtualBase, bool Delegating, llvm::Value *This, const CXXConstructExpr *E); - /// Emit assumption load for all bases. Requires to be be called only on - /// most-derived class and not under construction of the object. - void EmitVTableAssumptionLoads(const CXXRecordDecl *ClassDecl, - llvm::Value *This); - - /// Emit assumption that vptr load == global vtable. - void EmitVTableAssumptionLoad(const VPtr &vptr, llvm::Value *This); - void EmitSynthesizedCXXCopyCtorCall(const CXXConstructorDecl *D, llvm::Value *This, llvm::Value *Src, const CXXConstructExpr *E); diff --git a/lib/CodeGen/ItaniumCXXABI.cpp b/lib/CodeGen/ItaniumCXXABI.cpp index 31a1e488bc..2eebf8ee67 100644 --- a/lib/CodeGen/ItaniumCXXABI.cpp +++ b/lib/CodeGen/ItaniumCXXABI.cpp @@ -190,24 +190,10 @@ public: void emitVTableDefinitions(CodeGenVTables &CGVT, const CXXRecordDecl *RD) override; - bool isVirtualOffsetNeededForVTableField(CodeGenFunction &CGF, - CodeGenFunction::VPtr Vptr) override; - - bool doStructorsInitializeVPtrs(const CXXRecordDecl *VTableClass) override { - return true; - } - - llvm::Constant * - getVTableAddressPoint(BaseSubobject Base, - const CXXRecordDecl *VTableClass) override; - llvm::Value *getVTableAddressPointInStructor( CodeGenFunction &CGF, const CXXRecordDecl *VTableClass, - BaseSubobject Base, const CXXRecordDecl *NearestVBase) override; - - llvm::Value *getVTableAddressPointInStructorWithVTT( - CodeGenFunction &CGF, const CXXRecordDecl *VTableClass, - BaseSubobject Base, const CXXRecordDecl *NearestVBase); + BaseSubobject Base, const CXXRecordDecl *NearestVBase, + bool &NeedsVirtualOffset) override; llvm::Constant * getVTableAddressPointForConstExpr(BaseSubobject Base, @@ -229,7 +215,7 @@ public: void emitVirtualInheritanceTables(const CXXRecordDecl *RD) override; - bool canSpeculativelyEmitVTable(const CXXRecordDecl *RD) const override; + bool canEmitAvailableExternallyVTable(const CXXRecordDecl *RD) const override; void setThunkLinkage(llvm::Function *Thunk, bool ForVTable, GlobalDecl GD, bool ReturnAdjustment) override { @@ -1388,29 +1374,41 @@ void ItaniumCXXABI::emitVTableDefinitions(CodeGenVTables &CGVT, CGM.EmitVTableBitSetEntries(VTable, VTLayout); } -bool ItaniumCXXABI::isVirtualOffsetNeededForVTableField( - CodeGenFunction &CGF, CodeGenFunction::VPtr Vptr) { - if (Vptr.NearestVBase == nullptr) - return false; - return NeedsVTTParameter(CGF.CurGD); -} - llvm::Value *ItaniumCXXABI::getVTableAddressPointInStructor( CodeGenFunction &CGF, const CXXRecordDecl *VTableClass, BaseSubobject Base, - const CXXRecordDecl *NearestVBase) { - - if ((Base.getBase()->getNumVBases() || NearestVBase != nullptr) && - NeedsVTTParameter(CGF.CurGD)) { - return getVTableAddressPointInStructorWithVTT(CGF, VTableClass, Base, - NearestVBase); + const CXXRecordDecl *NearestVBase, bool &NeedsVirtualOffset) { + bool NeedsVTTParam = CGM.getCXXABI().NeedsVTTParameter(CGF.CurGD); + NeedsVirtualOffset = (NeedsVTTParam && NearestVBase); + + llvm::Value *VTableAddressPoint; + if (NeedsVTTParam && (Base.getBase()->getNumVBases() || NearestVBase)) { + // Get the secondary vpointer index. + uint64_t VirtualPointerIndex = + CGM.getVTables().getSecondaryVirtualPointerIndex(VTableClass, Base); + + /// Load the VTT. + llvm::Value *VTT = CGF.LoadCXXVTT(); + if (VirtualPointerIndex) + VTT = CGF.Builder.CreateConstInBoundsGEP1_64(VTT, VirtualPointerIndex); + + // And load the address point from the VTT. + VTableAddressPoint = CGF.Builder.CreateLoad(VTT); + } else { + llvm::Constant *VTable = + CGM.getCXXABI().getAddrOfVTable(VTableClass, CharUnits()); + uint64_t AddressPoint = CGM.getItaniumVTableContext() + .getVTableLayout(VTableClass) + .getAddressPoint(Base); + VTableAddressPoint = + CGF.Builder.CreateConstInBoundsGEP2_64(VTable, 0, AddressPoint); } - return getVTableAddressPoint(Base, VTableClass); + + return VTableAddressPoint; } -llvm::Constant * -ItaniumCXXABI::getVTableAddressPoint(BaseSubobject Base, - const CXXRecordDecl *VTableClass) { - llvm::GlobalValue *VTable = getAddrOfVTable(VTableClass, CharUnits()); +llvm::Constant *ItaniumCXXABI::getVTableAddressPointForConstExpr( + BaseSubobject Base, const CXXRecordDecl *VTableClass) { + auto *VTable = getAddrOfVTable(VTableClass, CharUnits()); // Find the appropriate vtable within the vtable group. uint64_t AddressPoint = CGM.getItaniumVTableContext() @@ -1425,30 +1423,6 @@ ItaniumCXXABI::getVTableAddressPoint(BaseSubobject Base, VTable, Indices); } -llvm::Value *ItaniumCXXABI::getVTableAddressPointInStructorWithVTT( - CodeGenFunction &CGF, const CXXRecordDecl *VTableClass, BaseSubobject Base, - const CXXRecordDecl *NearestVBase) { - assert((Base.getBase()->getNumVBases() || NearestVBase != nullptr) && - NeedsVTTParameter(CGF.CurGD) && "This class doesn't have VTT"); - - // Get the secondary vpointer index. - uint64_t VirtualPointerIndex = - CGM.getVTables().getSecondaryVirtualPointerIndex(VTableClass, Base); - - /// Load the VTT. - llvm::Value *VTT = CGF.LoadCXXVTT(); - if (VirtualPointerIndex) - VTT = CGF.Builder.CreateConstInBoundsGEP1_64(VTT, VirtualPointerIndex); - - // And load the address point from the VTT. - return CGF.Builder.CreateLoad(VTT); -} - -llvm::Constant *ItaniumCXXABI::getVTableAddressPointForConstExpr( - BaseSubobject Base, const CXXRecordDecl *VTableClass) { - return getVTableAddressPoint(Base, VTableClass); -} - llvm::GlobalVariable *ItaniumCXXABI::getAddrOfVTable(const CXXRecordDecl *RD, CharUnits VPtrOffset) { assert(VPtrOffset.isZero() && "Itanium ABI only supports zero vptr offsets"); @@ -1523,7 +1497,8 @@ void ItaniumCXXABI::emitVirtualInheritanceTables(const CXXRecordDecl *RD) { VTables.EmitVTTDefinition(VTT, CGM.getVTableLinkage(RD), RD); } -bool ItaniumCXXABI::canSpeculativelyEmitVTable(const CXXRecordDecl *RD) const { +bool ItaniumCXXABI::canEmitAvailableExternallyVTable( + const CXXRecordDecl *RD) const { // We don't emit available_externally vtables if we are in -fapple-kext mode // because kext mode does not permit devirtualization. if (CGM.getLangOpts().AppleKext) diff --git a/lib/CodeGen/MicrosoftCXXABI.cpp b/lib/CodeGen/MicrosoftCXXABI.cpp index de97ec3d5d..6a12cedd1b 100644 --- a/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/lib/CodeGen/MicrosoftCXXABI.cpp @@ -106,7 +106,8 @@ public: QualType DestTy) override; bool EmitBadCastCall(CodeGenFunction &CGF) override; - bool canSpeculativelyEmitVTable(const CXXRecordDecl *RD) const override { + bool canEmitAvailableExternallyVTable( + const CXXRecordDecl *RD) const override { return false; } @@ -214,22 +215,10 @@ public: void emitVTableDefinitions(CodeGenVTables &CGVT, const CXXRecordDecl *RD) override; - bool isVirtualOffsetNeededForVTableField(CodeGenFunction &CGF, - CodeGenFunction::VPtr Vptr) override; - - /// Don't initialize vptrs if dynamic class - /// is marked with with the 'novtable' attribute. - bool doStructorsInitializeVPtrs(const CXXRecordDecl *VTableClass) override { - return !VTableClass->hasAttr(); - } - - llvm::Constant * - getVTableAddressPoint(BaseSubobject Base, - const CXXRecordDecl *VTableClass) override; - llvm::Value *getVTableAddressPointInStructor( CodeGenFunction &CGF, const CXXRecordDecl *VTableClass, - BaseSubobject Base, const CXXRecordDecl *NearestVBase) override; + BaseSubobject Base, const CXXRecordDecl *NearestVBase, + bool &NeedsVirtualOffset) override; llvm::Constant * getVTableAddressPointForConstExpr(BaseSubobject Base, @@ -1579,15 +1568,14 @@ void MicrosoftCXXABI::emitVTableDefinitions(CodeGenVTables &CGVT, } } -bool MicrosoftCXXABI::isVirtualOffsetNeededForVTableField( - CodeGenFunction &CGF, CodeGenFunction::VPtr Vptr) { - return Vptr.NearestVBase != nullptr; -} - llvm::Value *MicrosoftCXXABI::getVTableAddressPointInStructor( CodeGenFunction &CGF, const CXXRecordDecl *VTableClass, BaseSubobject Base, - const CXXRecordDecl *NearestVBase) { - llvm::Constant *VTableAddressPoint = getVTableAddressPoint(Base, VTableClass); + const CXXRecordDecl *NearestVBase, bool &NeedsVirtualOffset) { + NeedsVirtualOffset = (NearestVBase != nullptr); + + (void)getAddrOfVTable(VTableClass, Base.getBaseOffset()); + VFTableIdTy ID(VTableClass, Base.getBaseOffset()); + llvm::GlobalValue *VTableAddressPoint = VFTablesMap[ID]; if (!VTableAddressPoint) { assert(Base.getBase()->getNumVBases() && !getContext().getASTRecordLayout(Base.getBase()).hasOwnVFPtr()); @@ -1602,17 +1590,11 @@ static void mangleVFTableName(MicrosoftMangleContext &MangleContext, MangleContext.mangleCXXVFTable(RD, VFPtr->MangledPath, Out); } -llvm::Constant * -MicrosoftCXXABI::getVTableAddressPoint(BaseSubobject Base, - const CXXRecordDecl *VTableClass) { - (void)getAddrOfVTable(VTableClass, Base.getBaseOffset()); - VFTableIdTy ID(VTableClass, Base.getBaseOffset()); - return VFTablesMap[ID]; -} - llvm::Constant *MicrosoftCXXABI::getVTableAddressPointForConstExpr( BaseSubobject Base, const CXXRecordDecl *VTableClass) { - llvm::Constant *VFTable = getVTableAddressPoint(Base, VTableClass); + (void)getAddrOfVTable(VTableClass, Base.getBaseOffset()); + VFTableIdTy ID(VTableClass, Base.getBaseOffset()); + llvm::GlobalValue *VFTable = VFTablesMap[ID]; assert(VFTable && "Couldn't find a vftable for the given base?"); return VFTable; } @@ -1622,7 +1604,6 @@ llvm::GlobalVariable *MicrosoftCXXABI::getAddrOfVTable(const CXXRecordDecl *RD, // getAddrOfVTable may return 0 if asked to get an address of a vtable which // shouldn't be used in the given record type. We want to cache this result in // VFTablesMap, thus a simple zero check is not sufficient. - VFTableIdTy ID(RD, VPtrOffset); VTablesMapTy::iterator I; bool Inserted; @@ -1676,11 +1657,10 @@ llvm::GlobalVariable *MicrosoftCXXABI::getAddrOfVTable(const CXXRecordDecl *RD, if (llvm::GlobalValue *VFTable = CGM.getModule().getNamedGlobal(VFTableName)) { VFTablesMap[ID] = VFTable; - VTable = VTableAliasIsRequred - ? cast( - cast(VFTable)->getBaseObject()) - : cast(VFTable); - return VTable; + return VTableAliasIsRequred + ? cast( + cast(VFTable)->getBaseObject()) + : cast(VFTable); } uint64_t NumVTableSlots = diff --git a/test/CodeGen/available-externally-hidden.cpp b/test/CodeGen/available-externally-hidden.cpp index 88ebfa9684..dc13f26b75 100644 --- a/test/CodeGen/available-externally-hidden.cpp +++ b/test/CodeGen/available-externally-hidden.cpp @@ -27,6 +27,6 @@ class TestSyncMessageFilter : public SyncMessageFilter { }; int main() { - TestSyncMessageFilter *f = new TestSyncMessageFilter; +TestSyncMessageFilter* f = new TestSyncMessageFilter; f->Send(new Message); } diff --git a/test/CodeGenCXX/ctor-globalopt.cpp b/test/CodeGenCXX/ctor-globalopt.cpp index 0951278d31..5ba868fb1b 100644 --- a/test/CodeGenCXX/ctor-globalopt.cpp +++ b/test/CodeGenCXX/ctor-globalopt.cpp @@ -1,7 +1,7 @@ // RUN: %clang_cc1 -triple %itanium_abi_triple -emit-llvm -o - %s | FileCheck %s -// RUN: %clang_cc1 -triple %itanium_abi_triple -emit-llvm -o - %s -O2 | opt - -S -globalopt -o - | FileCheck %s --check-prefix=O1 +// RUN: %clang_cc1 -triple %itanium_abi_triple -emit-llvm -o - %s -O1 | FileCheck %s --check-prefix=O1 // RUN: %clang_cc1 -triple %ms_abi_triple -emit-llvm -o - %s | FileCheck %s -// RUN: %clang_cc1 -triple %ms_abi_triple -emit-llvm -o - %s -O2 | opt - -S -globalopt -o - | FileCheck %s --check-prefix=O1 +// RUN: %clang_cc1 -triple %ms_abi_triple -emit-llvm -o - %s -O1 | FileCheck %s --check-prefix=O1 // Check that GlobalOpt can eliminate static constructors for simple implicit // constructors. This is a targeted integration test to make sure that LLVM's diff --git a/test/CodeGenCXX/thunks.cpp b/test/CodeGenCXX/thunks.cpp index d460fe3fd5..38afb9d0db 100644 --- a/test/CodeGenCXX/thunks.cpp +++ b/test/CodeGenCXX/thunks.cpp @@ -1,9 +1,5 @@ -// RUN: %clang_cc1 %s -triple=x86_64-pc-linux-gnu -munwind-tables -emit-llvm -o %t -// RUN: %clang_cc1 %s -triple=x86_64-pc-linux-gnu -munwind-tables -emit-llvm -o %t.opt -O1 -disable-llvm-optzns -// RUN: FileCheck %s < %t -// RUN: FileCheck %s < %t.opt -// RUN: FileCheck --check-prefix=CHECK-NONOPT %s < %t -// RUN: FileCheck --check-prefix=CHECK-OPT %s < %t.opt +// RUN: %clang_cc1 %s -triple=x86_64-pc-linux-gnu -munwind-tables -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 %s -triple=x86_64-pc-linux-gnu -munwind-tables -emit-llvm -o - -O1 -disable-llvm-optzns | FileCheck %s namespace Test1 { @@ -384,27 +380,13 @@ D::~D() {} /**** The following has to go at the end of the file ****/ -// checking without opt -// CHECK-NONOPT-LABEL: define internal void @_ZThn8_N6Test4B12_GLOBAL__N_11C1fEv( -// CHECK-NONOPT-NOT: comdat - -// This is from Test5: -// CHECK-NONOPT-LABEL: define linkonce_odr void @_ZTv0_n24_N5Test51B1fEv - -// This is from Test10: -// CHECK-NONOPT-LABEL: define linkonce_odr void @_ZN6Test101C3fooEv -// CHECK-NONOPT-LABEL: define linkonce_odr void @_ZThn8_N6Test101C3fooEv - -// Checking with opt -// CHECK-OPT-LABEL: define internal void @_ZThn8_N6Test4B12_GLOBAL__N_11C1fEv(%"struct.Test4B::(anonymous namespace)::C"* %this) unnamed_addr #0 align 2 - // This is from Test5: -// CHECK-OPT-LABEL: define linkonce_odr void @_ZTv0_n24_N5Test51B1fEv +// CHECK-LABEL: define internal void @_ZThn8_N6Test4B12_GLOBAL__N_11C1fEv( +// CHECK-NOT: comdat +// CHECK-LABEL: define linkonce_odr void @_ZTv0_n24_N5Test51B1fEv // This is from Test10: -// CHECK-OPT-LABEL: define linkonce_odr void @_ZN6Test101C3fooEv -// CHECK-OPT-LABEL: define linkonce_odr void @_ZThn8_N6Test101C3fooEv - - +// CHECK-LABEL: define linkonce_odr void @_ZN6Test101C3fooEv +// CHECK-LABEL: define linkonce_odr void @_ZThn8_N6Test101C3fooEv // CHECK: attributes [[NUW]] = { nounwind uwtable{{.*}} } diff --git a/test/CodeGenCXX/virtual-base-ctor.cpp b/test/CodeGenCXX/virtual-base-ctor.cpp index 20a88cd371..8c28965c5c 100644 --- a/test/CodeGenCXX/virtual-base-ctor.cpp +++ b/test/CodeGenCXX/virtual-base-ctor.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 %s -emit-llvm -triple %itanium_abi_triple -o - -O2 | opt - -S -globalopt -o - | FileCheck %s +// RUN: %clang_cc1 %s -emit-llvm -triple %itanium_abi_triple -o - -O2 | FileCheck %s struct B; extern B x; diff --git a/test/CodeGenCXX/vtable-assume-load.cpp b/test/CodeGenCXX/vtable-assume-load.cpp deleted file mode 100644 index 17cd477f19..0000000000 --- a/test/CodeGenCXX/vtable-assume-load.cpp +++ /dev/null @@ -1,295 +0,0 @@ -// RUN: %clang_cc1 %s -triple x86_64-apple-darwin10 -emit-llvm -o %t.ll -O1 -disable-llvm-optzns -fms-extensions -// RUN: %clang_cc1 %s -triple i686-pc-win32 -emit-llvm -o %t.ms.ll -O1 -disable-llvm-optzns -fms-extensions - -// RUN: FileCheck --check-prefix=CHECK1 --input-file=%t.ll %s -// RUN: FileCheck --check-prefix=CHECK2 --input-file=%t.ll %s -// RUN: FileCheck --check-prefix=CHECK3 --input-file=%t.ll %s -// RUN: FileCheck --check-prefix=CHECK4 --input-file=%t.ll %s -// RUN: FileCheck --check-prefix=CHECK-MS --input-file=%t.ms.ll %s -// RUN: FileCheck --check-prefix=CHECK6 --input-file=%t.ll %s -// RUN: FileCheck --check-prefix=CHECK7 --input-file=%t.ll %s -// RUN: FileCheck --check-prefix=CHECK8 --input-file=%t.ll %s -namespace test1 { - -struct A { - A(); - virtual void foo(); -}; - -struct B : A { - virtual void foo(); -}; - -void g(A *a) { a->foo(); } - -// CHECK1-LABEL: define void @_ZN5test14fooAEv() -// CHECK1: call void @_ZN5test11AC1Ev(%"struct.test1::A"* -// CHECK1: %[[VTABLE:.*]] = load i8**, i8*** %{{.*}} -// CHECK1: %[[CMP:.*]] = icmp eq i8** %[[VTABLE]], getelementptr inbounds ([3 x i8*], [3 x i8*]* @_ZTVN5test11AE, i64 0, i64 2) -// CHECK1: call void @llvm.assume(i1 %[[CMP]]) -// CHECK1-LABEL: } - -void fooA() { - A a; - g(&a); -} - -// CHECK1-LABEL: define void @_ZN5test14fooBEv() -// CHECK1: call void @_ZN5test11BC1Ev(%"struct.test1::B"* %{{.*}}) -// CHECK1: %[[VTABLE:.*]] = load i8**, i8*** %{{.*}} -// CHECK1: %[[CMP:.*]] = icmp eq i8** %[[VTABLE]], getelementptr inbounds ([3 x i8*], [3 x i8*]* @_ZTVN5test11BE, i64 0, i64 2) -// CHECK1: call void @llvm.assume(i1 %[[CMP]]) -// CHECK1-LABEL: } - -void fooB() { - B b; - g(&b); -} -// there should not be any assumes in the ctor that calls base ctor -// CHECK1-LABEL: define linkonce_odr void @_ZN5test11BC2Ev(%"struct.test1::B"* -// CHECK1-NOT: @llvm.assume( -// CHECK1-LABEL: } -} -namespace test2 { -struct A { - A(); - virtual void foo(); -}; - -struct B { - B(); - virtual void bar(); -}; - -struct C : A, B { - C(); - virtual void foo(); -}; -void g(A *a) { a->foo(); } -void h(B *b) { b->bar(); } - -// CHECK2-LABEL: define void @_ZN5test24testEv() -// CHECK2: call void @_ZN5test21CC1Ev(%"struct.test2::C"* -// CHECK2: %[[VTABLE:.*]] = load i8**, i8*** {{.*}} -// CHECK2: %[[CMP:.*]] = icmp eq i8** %[[VTABLE]], getelementptr inbounds ([6 x i8*], [6 x i8*]* @_ZTVN5test21CE, i64 0, i64 2) -// CHECK2: call void @llvm.assume(i1 %[[CMP]]) - -// CHECK2: %[[V2:.*]] = bitcast %"struct.test2::C"* %{{.*}} to i8* -// CHECK2: %[[ADD_PTR:.*]] = getelementptr inbounds i8, i8* %[[V2]], i64 8 -// CHECK2: %[[V3:.*]] = bitcast i8* %[[ADD_PTR]] to i8*** -// CHECK2: %[[VTABLE2:.*]] = load i8**, i8*** %[[V3]] -// CHECK2: %[[CMP2:.*]] = icmp eq i8** %[[VTABLE2]], getelementptr inbounds ([6 x i8*], [6 x i8*]* @_ZTVN5test21CE, i64 0, i64 5) -// CHECK2: call void @llvm.assume(i1 %[[CMP2]]) - -// CHECK2: call void @_ZN5test21gEPNS_1AE( -// CHECK2-LABEL: } - -void test() { - C c; - g(&c); - h(&c); -} -} - -namespace test3 { -struct A { - A(); -}; - -struct B : A { - B(); - virtual void foo(); -}; - -struct C : virtual A, B { - C(); - virtual void foo(); -}; -void g(B *a) { a->foo(); } - -// CHECK3-LABEL: define void @_ZN5test34testEv() -// CHECK3: call void @_ZN5test31CC1Ev(%"struct.test3::C"* -// CHECK3: %[[CMP:.*]] = icmp eq i8** %{{.*}}, getelementptr inbounds ([4 x i8*], [4 x i8*]* @_ZTVN5test31CE, i64 0, i64 3) -// CHECK3: call void @llvm.assume(i1 %[[CMP]]) -// CHECK3-LABLEL: } -void test() { - C c; - g(&c); -} -} // test3 - -namespace test4 { -struct A { - A(); - virtual void foo(); -}; - -struct B : virtual A { - B(); - virtual void foo(); -}; -struct C : B { - C(); - virtual void foo(); -}; - -void g(C *c) { c->foo(); } - -// CHECK4-LABEL: define void @_ZN5test44testEv() -// CHECK4: call void @_ZN5test41CC1Ev(%"struct.test4::C"* -// CHECK4: %[[VTABLE:.*]] = load i8**, i8*** %{{.*}} -// CHECK4: %[[CMP:.*]] = icmp eq i8** %[[VTABLE]], getelementptr inbounds ([5 x i8*], [5 x i8*]* @_ZTVN5test41CE, i64 0, i64 4) -// CHECK4: call void @llvm.assume(i1 %[[CMP]] - -// CHECK4: %[[VTABLE2:.*]] = load i8**, i8*** %{{.*}} -// CHECK4: %[[CMP2:.*]] = icmp eq i8** %[[VTABLE2]], getelementptr inbounds ([5 x i8*], [5 x i8*]* @_ZTVN5test41CE, i64 0, i64 4) -// CHECK4: call void @llvm.assume(i1 %[[CMP2]]) -// CHECK4-LABEL: } - -void test() { - C c; - g(&c); -} -} // test4 - -namespace testMS { - -struct __declspec(novtable) S { - virtual void foo(); -}; - -void g(S &s) { s.foo(); } - -// if struct has novtable specifier, then we can't generate assumes -// CHECK-MS-LABEL: define void @"\01?test@testMS@@YAXXZ"() -// CHECK-MS: call x86_thiscallcc %"struct.testMS::S"* @"\01??0S@testMS@@QAE@XZ"( -// CHECK-MS-NOT: @llvm.assume -// CHECK-MS-LABEL: } - -void test() { - S s; - g(s); -} - -} // testMS - -namespace test6 { -// CHECK6: @_ZTVN5test61AE = external -struct A { - A(); - virtual void foo(); - virtual ~A() {} -}; -struct B : A { - B(); -}; -// Because A's vtable is external, it's safe to generate assumption loads. -// CHECK6-LABEL: define void @_ZN5test61gEv() -// CHECK6: call void @_ZN5test61AC1Ev( -// CHECK6: call void @llvm.assume( - -// We can't emit assumption loads for B, because if we would refer to vtable -// it would refer to functions that will not be able to find (like implicit -// inline destructor). - -// CHECK6-LABEL: call void @_ZN5test61BC1Ev( -// CHECK6-NOT: call void @llvm.assume( -// CHECK6-LABEL: } -void g() { - A *a = new A; - B *b = new B; -} - -} - -namespace test7 { -// Because A's key function is defined here, vtable is generated in this TU -// CHECK7: @_ZTVN5test71AE = unnamed_addr constant -struct A { - A(); - virtual void foo(); - virtual void bar(); -}; -void A::foo() {} - -// CHECK7-LABEL: define void @_ZN5test71gEv() -// CHECK7: call void @_ZN5test71AC1Ev( -// CHECK7: call void @llvm.assume( -// CHECK7-LABEL: } -void g() { - A *a = new A(); - a->bar(); -} -} - -namespace test8 { - -struct A { - virtual void foo(); - virtual void bar(); -}; - -// CHECK8-DAG: @_ZTVN5test81BE = available_externally unnamed_addr constant -struct B : A { - B(); - void foo(); - void bar(); -}; - -// CHECK8-DAG: @_ZTVN5test81CE = linkonce_odr unnamed_addr constant -struct C : A { - C(); - void bar(); - void foo() {} -}; -inline void C::bar() {} - -// CHECK8-DAG: @_ZTVN5test81DE = external unnamed_addr constant -struct D : A { - D(); - void foo(); - void inline bar(); -}; -void D::bar() {} - -// CHECK8-DAG: @_ZTVN5test81EE = linkonce_odr unnamed_addr constant -struct E : A { - E(); -}; - -// CHECK8-LABEL: define void @_ZN5test81bEv() -// CHECK8: call void @llvm.assume( -// CHECK8-LABEL: } -void b() { - B b; - b.bar(); -} - -// FIXME: C has inline virtual functions which prohibits as from generating -// assumption loads, but because vtable is generated in this TU (key function -// defined here) it would be correct to refer to it. -// CHECK8-LABEL: define void @_ZN5test81cEv() -// CHECK8-NOT: call void @llvm.assume( -// CHECK8-LABEL: } -void c() { - C c; - c.bar(); -} - -// CHECK8-LABEL: define void @_ZN5test81dEv() -// CHECK8: call void @llvm.assume( -// CHECK8-LABEL: } -void d() { - D d; - d.bar(); -} - -// CHECK8-LABEL: define void @_ZN5test81eEv() -// CHECK8: call void @llvm.assume( -// CHECK8-LABEL: } -void e() { - E e; - e.bar(); -} -} - diff --git a/test/CodeGenCXX/vtable-available-externally.cpp b/test/CodeGenCXX/vtable-available-externally.cpp index ba56499f67..4527efca96 100644 --- a/test/CodeGenCXX/vtable-available-externally.cpp +++ b/test/CodeGenCXX/vtable-available-externally.cpp @@ -184,10 +184,10 @@ void f() { } // Test8 namespace Test9 { -// All virtual functions are outline, so we can assume that it will -// be generated in translation unit where foo is defined. -// CHECK-TEST9-DAG: @_ZTVN5Test91AE = available_externally unnamed_addr constant -// CHECK-TEST9-DAG: @_ZTVN5Test91BE = available_externally unnamed_addr constant +// all virtual functions are outline, so we can assume that it will +// be generated in translation unit where foo is defined +// CHECK-TEST9: @_ZTVN5Test91AE = available_externally unnamed_addr constant +// CHECK-TEST9: @_ZTVN5Test91BE = available_externally unnamed_addr constant struct A { virtual void foo(); virtual void bar(); @@ -210,22 +210,22 @@ void g() { namespace Test10 { // because A's key function is defined here, vtable is generated in this TU -// CHECK-TEST10-DAG: @_ZTVN6Test101AE = unnamed_addr constant +// CHECK-TEST10: @_ZTVN6Test101AE = unnamed_addr constant struct A { virtual void foo(); virtual void bar(); }; void A::foo() {} -// Because key function is inline we will generate vtable as linkonce_odr. -// CHECK-TEST10-DAG: @_ZTVN6Test101DE = linkonce_odr unnamed_addr constant +// Because key function is inline we will generate vtable as linkonce_odr +// CHECK-TEST10: @_ZTVN6Test101DE = linkonce_odr unnamed_addr constant struct D : A { void bar(); }; inline void D::bar() {} -// Because B has outline all virtual functions, we can refer to them. -// CHECK-TEST10-DAG: @_ZTVN6Test101BE = available_externally unnamed_addr constant +// because B has outline key function then we can refer to +// CHECK-TEST10: @_ZTVN6Test101BE = available_externally unnamed_addr constant struct B : A { void foo(); void bar(); @@ -233,8 +233,8 @@ struct B : A { // C's key function (car) is outline, but C has inline virtual function so we // can't guarantee that we will be able to refer to bar from name -// so (at the moment) we can't emit vtable available_externally. -// CHECK-TEST10-DAG: @_ZTVN6Test101CE = external unnamed_addr constant +// so (at the moment) we can't emit vtable available_externally +// CHECK-TEST10: @_ZTVN6Test101CE = external unnamed_addr constant struct C : A { void bar() {} // defined in body - not key function virtual inline void gar(); // inline in body - not key function @@ -242,7 +242,7 @@ struct C : A { }; // no key function, vtable will be generated everywhere it will be used -// CHECK-TEST10-DAG: @_ZTVN6Test101EE = linkonce_odr unnamed_addr constant +// CHECK-TEST10: @_ZTVN6Test101EE = linkonce_odr unnamed_addr constant struct E : A {}; void g(A& a) { @@ -365,3 +365,4 @@ void test() { } } + -- 2.40.0