From c1709d387e81fd7f18095363f5f6a4af883da3a7 Mon Sep 17 00:00:00 2001 From: David Majnemer Date: Tue, 23 Jun 2015 07:31:11 +0000 Subject: [PATCH] [MS ABI] Rework member pointer conversion Member pointers in the MS ABI are made complicated due to the following: - Virtual methods in the most derived class (MDC) might live in a vftable in a virtual base. - There are four different representations of member pointer: single inheritance, multiple inheritance, virtual inheritance and the "most general" representation. - Bases might have a *more* general representation than classes which derived from them, a most surprising result. We believed that we could treat all member pointers as-if they were a degenerate case of the multiple inheritance model. This fell apart once we realized that implementing standard member pointers using this ABI requires referencing members with a non-zero vbindex. On a bright note, all but the virtual inheritance model operate rather similarly. The virtual inheritance member pointer representation awkwardly requires a virtual base adjustment in order to refer to entities in the MDC. However, the first virtual base might be quite far from the start of the virtual base. This means that we must add a negative non-virtual displacement. However, things get even more complicated. The most general representation interprets vbindex zero differently from the virtual inheritance model: it doesn't reference the vbtable at all. It turns out that this complexity can increase for quite some time: consider a derived to base conversion from the most general model to the multiple inheritance model... To manage this complexity we introduce a concept of "normalized" member pointer which allows us to treat all three models as the most general model. Then we try to figure out how to map this generalized member pointer onto the destination member pointer model. I've done my best to furnish the code with comments explaining why each adjustment is performed. This fixes PR23878. llvm-svn: 240384 --- clang/include/clang/AST/Mangle.h | 4 + clang/lib/AST/MicrosoftMangle.cpp | 12 + clang/lib/CodeGen/CGClass.cpp | 17 +- clang/lib/CodeGen/CodeGenModule.h | 5 + clang/lib/CodeGen/MicrosoftCXXABI.cpp | 314 +++++++++++++++--- .../microsoft-abi-member-pointers.cpp | 23 +- 6 files changed, 307 insertions(+), 68 deletions(-) diff --git a/clang/include/clang/AST/Mangle.h b/clang/include/clang/AST/Mangle.h index c5a7ea16a7ec..735ae11a2339 100644 --- a/clang/include/clang/AST/Mangle.h +++ b/clang/include/clang/AST/Mangle.h @@ -204,6 +204,10 @@ public: virtual void mangleVirtualMemPtrThunk(const CXXMethodDecl *MD, raw_ostream &) = 0; + virtual void mangleCXXVirtualDisplacementMap(const CXXRecordDecl *SrcRD, + const CXXRecordDecl *DstRD, + raw_ostream &Out) = 0; + virtual void mangleCXXThrowInfo(QualType T, bool IsConst, bool IsVolatile, uint32_t NumEntries, raw_ostream &Out) = 0; diff --git a/clang/lib/AST/MicrosoftMangle.cpp b/clang/lib/AST/MicrosoftMangle.cpp index 29a95a5103ce..26894bb91eb2 100644 --- a/clang/lib/AST/MicrosoftMangle.cpp +++ b/clang/lib/AST/MicrosoftMangle.cpp @@ -115,6 +115,9 @@ public: void mangleCXXVBTable(const CXXRecordDecl *Derived, ArrayRef BasePath, raw_ostream &Out) override; + void mangleCXXVirtualDisplacementMap(const CXXRecordDecl *SrcRD, + const CXXRecordDecl *DstRD, + raw_ostream &Out) override; void mangleCXXThrowInfo(QualType T, bool IsConst, bool IsVolatile, uint32_t NumEntries, raw_ostream &Out) override; void mangleCXXCatchableTypeArray(QualType T, uint32_t NumEntries, @@ -2395,6 +2398,15 @@ void MicrosoftMangleContextImpl::mangleCXXCatchHandlerType(QualType T, Mangler.getStream() << '.' << Flags; } +void MicrosoftMangleContextImpl::mangleCXXVirtualDisplacementMap( + const CXXRecordDecl *SrcRD, const CXXRecordDecl *DstRD, raw_ostream &Out) { + MicrosoftCXXNameMangler Mangler(*this, Out); + Mangler.getStream() << "\01??_K"; + Mangler.mangleName(SrcRD); + Mangler.getStream() << "$C"; + Mangler.mangleName(DstRD); +} + void MicrosoftMangleContextImpl::mangleCXXThrowInfo(QualType T, bool IsConst, bool IsVolatile, diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index 4d6a4e288dd3..6538351edf22 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -29,13 +29,12 @@ using namespace clang; using namespace CodeGen; -static CharUnits -ComputeNonVirtualBaseClassOffset(ASTContext &Context, - const CXXRecordDecl *DerivedClass, - CastExpr::path_const_iterator Start, - CastExpr::path_const_iterator End) { +CharUnits CodeGenModule::computeNonVirtualBaseClassOffset( + const CXXRecordDecl *DerivedClass, CastExpr::path_const_iterator Start, + CastExpr::path_const_iterator End) { CharUnits Offset = CharUnits::Zero(); + const ASTContext &Context = getContext(); const CXXRecordDecl *RD = DerivedClass; for (CastExpr::path_const_iterator I = Start; I != End; ++I) { @@ -64,8 +63,7 @@ CodeGenModule::GetNonVirtualBaseClassOffset(const CXXRecordDecl *ClassDecl, assert(PathBegin != PathEnd && "Base path should not be empty!"); CharUnits Offset = - ComputeNonVirtualBaseClassOffset(getContext(), ClassDecl, - PathBegin, PathEnd); + computeNonVirtualBaseClassOffset(ClassDecl, PathBegin, PathEnd); if (Offset.isZero()) return nullptr; @@ -158,9 +156,8 @@ llvm::Value *CodeGenFunction::GetAddressOfBaseClass( // Compute the static offset of the ultimate destination within its // allocating subobject (the virtual base, if there is one, or else // the "complete" object that we see). - CharUnits NonVirtualOffset = - ComputeNonVirtualBaseClassOffset(getContext(), VBase ? VBase : Derived, - Start, PathEnd); + CharUnits NonVirtualOffset = CGM.computeNonVirtualBaseClassOffset( + VBase ? VBase : Derived, Start, PathEnd); // If there's a virtual step, we can sometimes "devirtualize" it. // For now, that's limited to when the derived type is final. diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index 8e671fa7878c..273fe70d52f3 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -732,6 +732,11 @@ public: /// Get a reference to the target of VD. llvm::Constant *GetWeakRefReference(const ValueDecl *VD); + CharUnits + computeNonVirtualBaseClassOffset(const CXXRecordDecl *DerivedClass, + CastExpr::path_const_iterator Start, + CastExpr::path_const_iterator End); + /// Returns the offset from a derived class to a class. Returns null if the /// offset is 0. llvm::Constant * diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp index 3c0aecaa161b..fabdef3d158d 100644 --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -247,6 +247,50 @@ public: getAddrOfVBTable(const VPtrInfo &VBT, const CXXRecordDecl *RD, llvm::GlobalVariable::LinkageTypes Linkage); + llvm::GlobalVariable * + getAddrOfVirtualDisplacementMap(const CXXRecordDecl *SrcRD, + const CXXRecordDecl *DstRD) { + SmallString<256> OutName; + llvm::raw_svector_ostream Out(OutName); + getMangleContext().mangleCXXVirtualDisplacementMap(SrcRD, DstRD, Out); + Out.flush(); + StringRef MangledName = OutName.str(); + + if (auto *VDispMap = CGM.getModule().getNamedGlobal(MangledName)) + return VDispMap; + + MicrosoftVTableContext &VTContext = CGM.getMicrosoftVTableContext(); + unsigned NumEntries = 1 + SrcRD->getNumVBases(); + SmallVector Map(NumEntries, + llvm::UndefValue::get(CGM.IntTy)); + Map[0] = llvm::ConstantInt::get(CGM.IntTy, 0); + bool AnyDifferent = false; + for (const auto &I : SrcRD->vbases()) { + const CXXRecordDecl *VBase = I.getType()->getAsCXXRecordDecl(); + if (!DstRD->isVirtuallyDerivedFrom(VBase)) + continue; + + unsigned SrcVBIndex = VTContext.getVBTableIndex(SrcRD, VBase); + unsigned DstVBIndex = VTContext.getVBTableIndex(DstRD, VBase); + Map[SrcVBIndex] = llvm::ConstantInt::get(CGM.IntTy, DstVBIndex * 4); + AnyDifferent |= SrcVBIndex != DstVBIndex; + } + // This map would be useless, don't use it. + if (!AnyDifferent) + return nullptr; + + llvm::ArrayType *VDispMapTy = llvm::ArrayType::get(CGM.IntTy, Map.size()); + llvm::Constant *Init = llvm::ConstantArray::get(VDispMapTy, Map); + llvm::GlobalValue::LinkageTypes Linkage = + SrcRD->isExternallyVisible() && DstRD->isExternallyVisible() + ? llvm::GlobalValue::LinkOnceODRLinkage + : llvm::GlobalValue::InternalLinkage; + auto *VDispMap = new llvm::GlobalVariable( + CGM.getModule(), VDispMapTy, /*Constant=*/true, Linkage, + /*Initializer=*/Init, MangledName); + return VDispMap; + } + void emitVBTableDefinition(const VPtrInfo &VBT, const CXXRecordDecl *RD, llvm::GlobalVariable *GV) const; @@ -2458,7 +2502,7 @@ MicrosoftCXXABI::EmitFullMemberPointer(llvm::Constant *FirstField, if (MSInheritanceAttr::hasVBPtrOffsetField(Inheritance)) { CharUnits Offs = CharUnits::Zero(); - if (VBTableIndex && RD->getNumVBases()) + if (VBTableIndex) Offs = getContext().getASTRecordLayout(RD).getVBPtrOffset(); fields.push_back(llvm::ConstantInt::get(CGM.IntTy, Offs.getQuantity())); } @@ -2470,10 +2514,33 @@ MicrosoftCXXABI::EmitFullMemberPointer(llvm::Constant *FirstField, return llvm::ConstantStruct::getAnon(fields); } +// Loading virtual member pointers using the virtual inheritance model +// always results in an adjustment using the vbtable even if the index is +// zero. +// +// This is usually OK because the first slot in the vbtable points +// backwards to the top of the MDC. However, the MDC might be reusing a +// vbptr from an nv-base. In this case, the first slot in the vbtable +// points to the start of the nv-base which introduced the vbptr and *not* +// the MDC. Modify the NonVirtualBaseAdjustment to account for this. +static CharUnits computeOffsetOfBaseWithVBPtr(const ASTContext &Ctx, + const CXXRecordDecl *RD) { + CharUnits Offset = CharUnits::Zero(); + const ASTRecordLayout *Layout = &Ctx.getASTRecordLayout(RD); + while (const CXXRecordDecl *Base = Layout->getBaseSharingVBPtr()) { + Offset += Layout->getBaseClassOffset(Base); + Layout = &Ctx.getASTRecordLayout(Base); + } + return Offset; +} + llvm::Constant * MicrosoftCXXABI::EmitMemberDataPointer(const MemberPointerType *MPT, CharUnits offset) { const CXXRecordDecl *RD = MPT->getMostRecentCXXRecordDecl(); + if (RD->getMSInheritanceModel() == + MSInheritanceAttr::Keyword_virtual_inheritance) + offset -= computeOffsetOfBaseWithVBPtr(getContext(), RD); llvm::Constant *FirstField = llvm::ConstantInt::get(CGM.IntTy, offset.getQuantity()); return EmitFullMemberPointer(FirstField, /*IsMemberFunction=*/false, RD, @@ -2558,20 +2625,24 @@ MicrosoftCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD) { Ty = CGM.PtrDiffTy; } FirstField = CGM.GetAddrOfFunction(MD, Ty); - FirstField = llvm::ConstantExpr::getBitCast(FirstField, CGM.VoidPtrTy); } else { auto &VTableContext = CGM.getMicrosoftVTableContext(); MicrosoftVTableContext::MethodVFTableLocation ML = VTableContext.getMethodVFTableLocation(MD); - llvm::Function *Thunk = EmitVirtualMemPtrThunk(MD, ML); - FirstField = llvm::ConstantExpr::getBitCast(Thunk, CGM.VoidPtrTy); + FirstField = EmitVirtualMemPtrThunk(MD, ML); // Include the vfptr adjustment if the method is in a non-primary vftable. NonVirtualBaseAdjustment += ML.VFPtrOffset; if (ML.VBase) VBTableIndex = VTableContext.getVBTableIndex(RD, ML.VBase) * 4; } + if (VBTableIndex == 0 && + RD->getMSInheritanceModel() == + MSInheritanceAttr::Keyword_virtual_inheritance) + NonVirtualBaseAdjustment -= computeOffsetOfBaseWithVBPtr(getContext(), RD); + // The rest of the fields are common with data member pointers. + FirstField = llvm::ConstantExpr::getBitCast(FirstField, CGM.VoidPtrTy); return EmitFullMemberPointer(FirstField, /*IsMemberFunction=*/true, RD, NonVirtualBaseAdjustment, VBTableIndex); } @@ -2829,11 +2900,6 @@ llvm::Value *MicrosoftCXXABI::EmitMemberDataPointerAddress( return Builder.CreateBitCast(Addr, PType); } -static MSInheritanceAttr::Spelling -getInheritanceFromMemptr(const MemberPointerType *MPT) { - return MPT->getMostRecentCXXRecordDecl()->getMSInheritanceModel(); -} - llvm::Value * MicrosoftCXXABI::EmitMemberPointerConversion(CodeGenFunction &CGF, const CastExpr *E, @@ -2887,10 +2953,11 @@ MicrosoftCXXABI::EmitMemberPointerConversion(CodeGenFunction &CGF, // Decompose src. llvm::Value *FirstField = Src; - llvm::Value *NonVirtualBaseAdjustment = nullptr; - llvm::Value *VirtualBaseAdjustmentOffset = nullptr; - llvm::Value *VBPtrOffset = nullptr; + llvm::Value *NonVirtualBaseAdjustment = getZeroInt(); + llvm::Value *VirtualBaseAdjustmentOffset = getZeroInt(); + llvm::Value *VBPtrOffset = getZeroInt(); MSInheritanceAttr::Spelling SrcInheritance = SrcRD->getMSInheritanceModel(); + MSInheritanceAttr::Spelling DstInheritance = DstRD->getMSInheritanceModel(); if (!MSInheritanceAttr::hasOnlyOneField(IsFunc, SrcInheritance)) { // We need to extract values. unsigned I = 0; @@ -2903,25 +2970,95 @@ MicrosoftCXXABI::EmitMemberPointerConversion(CodeGenFunction &CGF, VirtualBaseAdjustmentOffset = Builder.CreateExtractValue(Src, I++); } + bool IsDerivedToBase = (E->getCastKind() == CK_DerivedToBaseMemberPointer); + const MemberPointerType *DerivedTy = IsDerivedToBase ? SrcTy : DstTy; + const CXXRecordDecl *DerivedClass = DerivedTy->getMostRecentCXXRecordDecl(); + // For data pointers, we adjust the field offset directly. For functions, we // have a separate field. - llvm::Constant *Adj = getMemberPointerAdjustment(E); - if (Adj) { - Adj = llvm::ConstantExpr::getTruncOrBitCast(Adj, CGM.IntTy); - llvm::Value *&NVAdjustField = IsFunc ? NonVirtualBaseAdjustment : FirstField; - bool isDerivedToBase = (E->getCastKind() == CK_DerivedToBaseMemberPointer); - if (!NVAdjustField) // If this field didn't exist in src, it's zero. - NVAdjustField = getZeroInt(); - if (isDerivedToBase) - NVAdjustField = Builder.CreateNSWSub(NVAdjustField, Adj, "adj"); - else - NVAdjustField = Builder.CreateNSWAdd(NVAdjustField, Adj, "adj"); + llvm::Value *&NVAdjustField = IsFunc ? NonVirtualBaseAdjustment : FirstField; + + // The virtual inheritance model has a quirk: the virtual base table is always + // referenced when dereferencing a member pointer even if the member pointer + // is non-virtual. This is accounted for by adjusting the non-virtual offset + // to point backwards to the top of the MDC from the first VBase. Undo this + // adjustment to normalize the member pointer. + llvm::Value *SrcVBIndexEqZero = + Builder.CreateICmpEQ(VirtualBaseAdjustmentOffset, getZeroInt()); + if (SrcInheritance == MSInheritanceAttr::Keyword_virtual_inheritance) { + if (int64_t SrcOffsetToFirstVBase = + computeOffsetOfBaseWithVBPtr(getContext(), SrcRD).getQuantity()) { + llvm::Value *UndoSrcAdjustment = Builder.CreateSelect( + SrcVBIndexEqZero, + llvm::ConstantInt::get(CGM.IntTy, SrcOffsetToFirstVBase), + getZeroInt()); + NVAdjustField = Builder.CreateNSWAdd(NVAdjustField, UndoSrcAdjustment); + } + } + + // A non-zero vbindex implies that we are dealing with a source member in a + // floating virtual base in addition to some non-virtual offset. If the + // vbindex is zero, we are dealing with a source that exists in a non-virtual, + // fixed, base. The difference between these two cases is that the vbindex + + // nvoffset *always* point to the member regardless of what context they are + // evaluated in so long as the vbindex is adjusted. A member inside a fixed + // base requires explicit nv adjustment. + llvm::Constant *BaseClassOffset = llvm::ConstantInt::get( + CGM.IntTy, CGM.computeNonVirtualBaseClassOffset( + DerivedClass, E->path_begin(), E->path_end()) + .getQuantity()); + + llvm::Value *NVDisp; + if (IsDerivedToBase) + NVDisp = Builder.CreateNSWSub(NVAdjustField, BaseClassOffset, "adj"); + else + NVDisp = Builder.CreateNSWAdd(NVAdjustField, BaseClassOffset, "adj"); + + NVAdjustField = Builder.CreateSelect(SrcVBIndexEqZero, NVDisp, getZeroInt()); + + // Update the vbindex to an appropriate value in the destination because + // SrcRD's vbtable might not be a strict prefix of the one in DstRD. + llvm::Value *DstVBIndexEqZero = SrcVBIndexEqZero; + if (MSInheritanceAttr::hasVBTableOffsetField(DstInheritance) && + MSInheritanceAttr::hasVBTableOffsetField(SrcInheritance)) { + if (llvm::GlobalVariable *VDispMap = + getAddrOfVirtualDisplacementMap(SrcRD, DstRD)) { + llvm::Value *VBIndex = Builder.CreateExactUDiv( + VirtualBaseAdjustmentOffset, llvm::ConstantInt::get(CGM.IntTy, 4)); + llvm::Value *Idxs[] = {getZeroInt(), VBIndex}; + VirtualBaseAdjustmentOffset = + Builder.CreateLoad(Builder.CreateInBoundsGEP(VDispMap, Idxs)); + + DstVBIndexEqZero = + Builder.CreateICmpEQ(VirtualBaseAdjustmentOffset, getZeroInt()); + } + } + + // Set the VBPtrOffset to zero if the vbindex is zero. Otherwise, initialize + // it to the offset of the vbptr. + if (MSInheritanceAttr::hasVBPtrOffsetField(DstInheritance)) { + llvm::Value *DstVBPtrOffset = llvm::ConstantInt::get( + CGM.IntTy, + getContext().getASTRecordLayout(DstRD).getVBPtrOffset().getQuantity()); + VBPtrOffset = + Builder.CreateSelect(DstVBIndexEqZero, getZeroInt(), DstVBPtrOffset); } - // FIXME PR15713: Support conversions through virtually derived classes. + // Likewise, apply a similar adjustment so that dereferencing the member + // pointer correctly accounts for the distance between the start of the first + // virtual base and the top of the MDC. + if (DstInheritance == MSInheritanceAttr::Keyword_virtual_inheritance) { + if (int64_t DstOffsetToFirstVBase = + computeOffsetOfBaseWithVBPtr(getContext(), DstRD).getQuantity()) { + llvm::Value *DoDstAdjustment = Builder.CreateSelect( + DstVBIndexEqZero, + llvm::ConstantInt::get(CGM.IntTy, DstOffsetToFirstVBase), + getZeroInt()); + NVAdjustField = Builder.CreateNSWSub(NVAdjustField, DoDstAdjustment); + } + } // Recompose dst from the null struct and the adjusted fields from src. - MSInheritanceAttr::Spelling DstInheritance = DstRD->getMSInheritanceModel(); llvm::Value *Dst; if (MSInheritanceAttr::hasOnlyOneField(IsFunc, DstInheritance)) { Dst = FirstField; @@ -2930,14 +3067,11 @@ MicrosoftCXXABI::EmitMemberPointerConversion(CodeGenFunction &CGF, unsigned Idx = 0; Dst = Builder.CreateInsertValue(Dst, FirstField, Idx++); if (MSInheritanceAttr::hasNVOffsetField(IsFunc, DstInheritance)) - Dst = Builder.CreateInsertValue( - Dst, getValueOrZeroInt(NonVirtualBaseAdjustment), Idx++); + Dst = Builder.CreateInsertValue(Dst, NonVirtualBaseAdjustment, Idx++); if (MSInheritanceAttr::hasVBPtrOffsetField(DstInheritance)) - Dst = Builder.CreateInsertValue( - Dst, getValueOrZeroInt(VBPtrOffset), Idx++); + Dst = Builder.CreateInsertValue(Dst, VBPtrOffset, Idx++); if (MSInheritanceAttr::hasVBTableOffsetField(DstInheritance)) - Dst = Builder.CreateInsertValue( - Dst, getValueOrZeroInt(VirtualBaseAdjustmentOffset), Idx++); + Dst = Builder.CreateInsertValue(Dst, VirtualBaseAdjustmentOffset, Idx++); } Builder.CreateBr(ContinueBB); @@ -2980,14 +3114,16 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberPointerConversion( if (CK == CK_ReinterpretMemberPointer) return Src; - MSInheritanceAttr::Spelling SrcInheritance = getInheritanceFromMemptr(SrcTy); - MSInheritanceAttr::Spelling DstInheritance = getInheritanceFromMemptr(DstTy); + const CXXRecordDecl *SrcRD = SrcTy->getMostRecentCXXRecordDecl(); + const CXXRecordDecl *DstRD = DstTy->getMostRecentCXXRecordDecl(); + MSInheritanceAttr::Spelling SrcInheritance = SrcRD->getMSInheritanceModel(); + MSInheritanceAttr::Spelling DstInheritance = DstRD->getMSInheritanceModel(); // Decompose src. llvm::Constant *FirstField = Src; - llvm::Constant *NonVirtualBaseAdjustment = nullptr; - llvm::Constant *VirtualBaseAdjustmentOffset = nullptr; - llvm::Constant *VBPtrOffset = nullptr; + llvm::Constant *NonVirtualBaseAdjustment = getZeroInt(); + llvm::Constant *VirtualBaseAdjustmentOffset = getZeroInt(); + llvm::Constant *VBPtrOffset = getZeroInt(); bool IsFunc = SrcTy->isMemberFunctionPointer(); if (!MSInheritanceAttr::hasOnlyOneField(IsFunc, SrcInheritance)) { // We need to extract values. @@ -3001,27 +3137,95 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberPointerConversion( VirtualBaseAdjustmentOffset = Src->getAggregateElement(I++); } + bool IsDerivedToBase = (CK == CK_DerivedToBaseMemberPointer); + const MemberPointerType *DerivedTy = IsDerivedToBase ? SrcTy : DstTy; + const CXXRecordDecl *DerivedClass = DerivedTy->getMostRecentCXXRecordDecl(); + // For data pointers, we adjust the field offset directly. For functions, we // have a separate field. - const MemberPointerType *DerivedTy = - CK == CK_DerivedToBaseMemberPointer ? SrcTy : DstTy; - const CXXRecordDecl *DerivedClass = DerivedTy->getMostRecentCXXRecordDecl(); - llvm::Constant *Adj = - CGM.GetNonVirtualBaseClassOffset(DerivedClass, PathBegin, PathEnd); - if (Adj) { - Adj = llvm::ConstantExpr::getTruncOrBitCast(Adj, CGM.IntTy); - llvm::Constant *&NVAdjustField = + llvm::Constant *&NVAdjustField = IsFunc ? NonVirtualBaseAdjustment : FirstField; - bool IsDerivedToBase = (CK == CK_DerivedToBaseMemberPointer); - if (!NVAdjustField) // If this field didn't exist in src, it's zero. - NVAdjustField = getZeroInt(); - if (IsDerivedToBase) - NVAdjustField = llvm::ConstantExpr::getNSWSub(NVAdjustField, Adj); - else - NVAdjustField = llvm::ConstantExpr::getNSWAdd(NVAdjustField, Adj); + + // The virtual inheritance model has a quirk: the virtual base table is always + // referenced when dereferencing a member pointer even if the member pointer + // is non-virtual. This is accounted for by adjusting the non-virtual offset + // to point backwards to the top of the MDC from the first VBase. Undo this + // adjustment to normalize the member pointer. + llvm::Constant *SrcVBIndexEqZero = llvm::ConstantExpr::getICmp( + llvm::ICmpInst::ICMP_EQ, VirtualBaseAdjustmentOffset, getZeroInt()); + if (SrcInheritance == MSInheritanceAttr::Keyword_virtual_inheritance) { + llvm::Constant *SrcOffsetToFirstVBase = llvm::ConstantInt::get( + CGM.IntTy, + computeOffsetOfBaseWithVBPtr(getContext(), SrcRD).getQuantity()); + llvm::Constant *UndoSrcAdjustment = llvm::ConstantExpr::getSelect( + SrcVBIndexEqZero, SrcOffsetToFirstVBase, getZeroInt()); + NVAdjustField = + llvm::ConstantExpr::getNSWAdd(NVAdjustField, UndoSrcAdjustment); + } + + // A non-zero vbindex implies that we are dealing with a source member in a + // floating virtual base in addition to some non-virtual offset. If the + // vbindex is zero, we are dealing with a source that exists in a non-virtual, + // fixed, base. The difference between these two cases is that the vbindex + + // nvoffset *always* point to the member regardless of what context they are + // evaluated in so long as the vbindex is adjusted. A member inside a fixed + // base requires explicit nv adjustment. + llvm::Constant *BaseClassOffset = llvm::ConstantInt::get( + CGM.IntTy, + CGM.computeNonVirtualBaseClassOffset(DerivedClass, PathBegin, PathEnd) + .getQuantity()); + + llvm::Constant *NVDisp; + if (IsDerivedToBase) + NVDisp = llvm::ConstantExpr::getNSWSub(NVAdjustField, BaseClassOffset); + else + NVDisp = llvm::ConstantExpr::getNSWAdd(NVAdjustField, BaseClassOffset); + + // An nv-base adjustment must only be made if the vbindex is zero (or does not + // exist). + NVAdjustField = + llvm::ConstantExpr::getSelect(SrcVBIndexEqZero, NVDisp, getZeroInt()); + + // Update the vbindex to an appropriate value in the destination because + // SrcRD's vbtable might not be a strict prefix of the one in DstRD. + llvm::Constant *DstVBIndexEqZero = SrcVBIndexEqZero; + if (MSInheritanceAttr::hasVBTableOffsetField(DstInheritance) && + MSInheritanceAttr::hasVBTableOffsetField(SrcInheritance)) { + if (llvm::GlobalVariable *VDispMap = + getAddrOfVirtualDisplacementMap(SrcRD, DstRD)) { + llvm::Constant *Mapping = VDispMap->getInitializer(); + llvm::Constant *VBIndex = llvm::ConstantExpr::getUDiv( + VirtualBaseAdjustmentOffset, llvm::ConstantInt::get(CGM.IntTy, 4), + /*IsExact=*/true); + VirtualBaseAdjustmentOffset = Mapping->getAggregateElement(VBIndex); + + DstVBIndexEqZero = llvm::ConstantExpr::getICmp( + llvm::ICmpInst::ICMP_EQ, VirtualBaseAdjustmentOffset, getZeroInt()); + } + } + + // Set the VBPtrOffset to zero if the vbindex is zero. Otherwise, initialize + // it to the offset of the vbptr. + if (MSInheritanceAttr::hasVBPtrOffsetField(DstInheritance)) { + llvm::Constant *DstVBPtrOffset = llvm::ConstantInt::get( + CGM.IntTy, + getContext().getASTRecordLayout(DstRD).getVBPtrOffset().getQuantity()); + VBPtrOffset = llvm::ConstantExpr::getSelect(DstVBIndexEqZero, getZeroInt(), + DstVBPtrOffset); } - // FIXME PR15713: Support conversions through virtually derived classes. + // Likewise, apply a similar adjustment so that dereferencing the member + // pointer correctly accounts for the distance between the start of the first + // virtual base and the top of the MDC. + if (DstInheritance == MSInheritanceAttr::Keyword_virtual_inheritance) { + llvm::Constant *DstOffsetToFirstVBase = llvm::ConstantInt::get( + CGM.IntTy, + computeOffsetOfBaseWithVBPtr(getContext(), DstRD).getQuantity()); + llvm::Constant *DoDstAdjustment = llvm::ConstantExpr::getSelect( + DstVBIndexEqZero, DstOffsetToFirstVBase, getZeroInt()); + NVAdjustField = + llvm::ConstantExpr::getNSWSub(NVAdjustField, DoDstAdjustment); + } // Recompose dst from the null struct and the adjusted fields from src. if (MSInheritanceAttr::hasOnlyOneField(IsFunc, DstInheritance)) @@ -3030,11 +3234,11 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberPointerConversion( llvm::SmallVector Fields; Fields.push_back(FirstField); if (MSInheritanceAttr::hasNVOffsetField(IsFunc, DstInheritance)) - Fields.push_back(getConstantOrZeroInt(NonVirtualBaseAdjustment)); + Fields.push_back(NonVirtualBaseAdjustment); if (MSInheritanceAttr::hasVBPtrOffsetField(DstInheritance)) - Fields.push_back(getConstantOrZeroInt(VBPtrOffset)); + Fields.push_back(VBPtrOffset); if (MSInheritanceAttr::hasVBTableOffsetField(DstInheritance)) - Fields.push_back(getConstantOrZeroInt(VirtualBaseAdjustmentOffset)); + Fields.push_back(VirtualBaseAdjustmentOffset); return llvm::ConstantStruct::getAnon(Fields); } diff --git a/clang/test/CodeGenCXX/microsoft-abi-member-pointers.cpp b/clang/test/CodeGenCXX/microsoft-abi-member-pointers.cpp index 8cb4a1f8bd24..a509d57194f2 100644 --- a/clang/test/CodeGenCXX/microsoft-abi-member-pointers.cpp +++ b/clang/test/CodeGenCXX/microsoft-abi-member-pointers.cpp @@ -541,9 +541,13 @@ void (D::*convertCToD(void (C::*mp)()))() { // // memptr.convert: ; preds = %entry // CHECK: extractvalue { i8*, i32, i32 } %{{.*}}, 0 -// CHECK: extractvalue { i8*, i32, i32 } %{{.*}}, 1 -// CHECK: extractvalue { i8*, i32, i32 } %{{.*}}, 2 -// CHECK: %[[adj:.*]] = add nsw i32 %{{.*}}, 4 +// CHECK: %[[nvoff:.*]] = extractvalue { i8*, i32, i32 } %{{.*}}, 1 +// CHECK: %[[vbidx:.*]] = extractvalue { i8*, i32, i32 } %{{.*}}, 2 +// CHECK: %[[is_nvbase:.*]] = icmp eq i32 %[[vbidx]], 0 +// CHECK: %[[nv_disp:.*]] = add nsw i32 %[[nvoff]], 4 +// CHECK: %[[nv_adj:.*]] = select i1 %[[is_nvbase]], i32 %[[nv_disp]], i32 0 +// CHECK: %[[dst_adj:.*]] = select i1 %[[is_nvbase]], i32 4, i32 0 +// CHECK: %[[adj:.*]] = sub nsw i32 %[[nv_adj]], %[[dst_adj]] // CHECK: insertvalue { i8*, i32, i32 } undef, i8* {{.*}}, 0 // CHECK: insertvalue { i8*, i32, i32 } {{.*}}, i32 %[[adj]], 1 // CHECK: insertvalue { i8*, i32, i32 } {{.*}}, i32 {{.*}}, 2 @@ -712,3 +716,16 @@ int foo(A *a, int A::*mp) { } #endif #endif + +namespace pr23878 { +struct A { virtual void g(); }; +struct B { virtual void f(); }; +struct C : virtual B { void f(); }; +struct D : A, C {}; + +typedef void (D::*DMemPtrTy)(); + +// CHECK-LABEL: define void @"\01?get_memptr@pr23878@@YAP8D@1@AEXXZXZ" +// CHECK: @"\01??_9C@pr23878@@$BA@AE" to i8*), i32 0, i32 4 +DMemPtrTy get_memptr() { return &D::f; } +} -- GitLab