forked from OSchip/llvm-project
parent
f3e218a021
commit
df7e7fb642
|
@ -268,12 +268,38 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class VTableContext {
|
||||
class VTableContextBase {
|
||||
public:
|
||||
typedef SmallVector<std::pair<uint64_t, ThunkInfo>, 1>
|
||||
VTableThunksTy;
|
||||
typedef SmallVector<ThunkInfo, 1> ThunkInfoVectorTy;
|
||||
|
||||
protected:
|
||||
typedef llvm::DenseMap<const CXXMethodDecl *, ThunkInfoVectorTy> ThunksMapTy;
|
||||
|
||||
/// \brief Contains all thunks that a given method decl will need.
|
||||
ThunksMapTy Thunks;
|
||||
|
||||
/// Compute and store all vtable related information (vtable layout, vbase
|
||||
/// offset offsets, thunks etc) for the given record decl.
|
||||
virtual void computeVTableRelatedInformation(const CXXRecordDecl *RD) = 0;
|
||||
|
||||
virtual ~VTableContextBase() {}
|
||||
|
||||
public:
|
||||
const ThunkInfoVectorTy *getThunkInfo(const CXXMethodDecl *MD) {
|
||||
computeVTableRelatedInformation(MD->getParent());
|
||||
|
||||
ThunksMapTy::const_iterator I = Thunks.find(MD);
|
||||
if (I == Thunks.end()) {
|
||||
// We did not find a thunk for this method.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return &I->second;
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME: rename to ItaniumVTableContext.
|
||||
class VTableContext : public VTableContextBase {
|
||||
private:
|
||||
bool IsMicrosoftABI;
|
||||
|
||||
|
@ -297,14 +323,7 @@ private:
|
|||
VirtualBaseClassOffsetOffsetsMapTy;
|
||||
VirtualBaseClassOffsetOffsetsMapTy VirtualBaseClassOffsetOffsets;
|
||||
|
||||
typedef llvm::DenseMap<const CXXMethodDecl *, ThunkInfoVectorTy> ThunksMapTy;
|
||||
|
||||
/// \brief Contains all thunks that a given method decl will need.
|
||||
ThunksMapTy Thunks;
|
||||
|
||||
/// Compute and store all vtable related information (vtable layout, vbase
|
||||
/// offset offsets, thunks etc) for the given record decl.
|
||||
void ComputeVTableRelatedInformation(const CXXRecordDecl *RD);
|
||||
void computeVTableRelatedInformation(const CXXRecordDecl *RD);
|
||||
|
||||
public:
|
||||
VTableContext(ASTContext &Context);
|
||||
|
@ -318,7 +337,7 @@ public:
|
|||
}
|
||||
|
||||
const VTableLayout &getVTableLayout(const CXXRecordDecl *RD) {
|
||||
ComputeVTableRelatedInformation(RD);
|
||||
computeVTableRelatedInformation(RD);
|
||||
assert(VTableLayouts.count(RD) && "No layout for this record decl!");
|
||||
|
||||
return *VTableLayouts[RD];
|
||||
|
@ -330,18 +349,6 @@ public:
|
|||
bool MostDerivedClassIsVirtual,
|
||||
const CXXRecordDecl *LayoutClass);
|
||||
|
||||
const ThunkInfoVectorTy *getThunkInfo(const CXXMethodDecl *MD) {
|
||||
ComputeVTableRelatedInformation(MD->getParent());
|
||||
|
||||
ThunksMapTy::const_iterator I = Thunks.find(MD);
|
||||
if (I == Thunks.end()) {
|
||||
// We did not find a thunk for this method.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return &I->second;
|
||||
}
|
||||
|
||||
/// \brief Locate a virtual function in the vtable.
|
||||
///
|
||||
/// Return the index (relative to the vtable address point) where the
|
||||
|
@ -357,6 +364,118 @@ public:
|
|||
const CXXRecordDecl *VBase);
|
||||
};
|
||||
|
||||
/// \brief Computes the index of VBase in the vbtable of Derived.
|
||||
/// VBase must be a morally virtual base of Derived. The vbtable is
|
||||
/// an array of i32 offsets. The first entry is a self entry, and the rest are
|
||||
/// offsets from the vbptr to virtual bases. The bases are ordered the same way
|
||||
/// our vbases are ordered: as they appear in a left-to-right depth-first search
|
||||
/// of the hierarchy.
|
||||
// FIXME: make this a static method of VBTableBuilder when we move it to AST.
|
||||
unsigned GetVBTableIndex(const CXXRecordDecl *Derived,
|
||||
const CXXRecordDecl *VBase);
|
||||
|
||||
struct VFPtrInfo {
|
||||
typedef SmallVector<const CXXRecordDecl *, 1> BasePath;
|
||||
|
||||
VFPtrInfo(CharUnits VFPtrOffset, const BasePath &PathToBaseWithVFPtr)
|
||||
: VBTableIndex(0), LastVBase(0), VFPtrOffset(VFPtrOffset),
|
||||
PathToBaseWithVFPtr(PathToBaseWithVFPtr), VFPtrFullOffset(VFPtrOffset) {
|
||||
}
|
||||
|
||||
VFPtrInfo(uint64_t VBTableIndex, const CXXRecordDecl *LastVBase,
|
||||
CharUnits VFPtrOffset, const BasePath &PathToBaseWithVFPtr,
|
||||
CharUnits VFPtrFullOffset)
|
||||
: VBTableIndex(VBTableIndex), LastVBase(LastVBase),
|
||||
VFPtrOffset(VFPtrOffset), PathToBaseWithVFPtr(PathToBaseWithVFPtr),
|
||||
VFPtrFullOffset(VFPtrFullOffset) {
|
||||
assert(VBTableIndex && "The full constructor should only be used "
|
||||
"for vfptrs in virtual bases");
|
||||
assert(LastVBase);
|
||||
}
|
||||
|
||||
/// If nonzero, holds the vbtable index of the virtual base with the vfptr.
|
||||
uint64_t VBTableIndex;
|
||||
|
||||
/// Stores the last vbase on the path from the complete type to the vfptr.
|
||||
const CXXRecordDecl *LastVBase;
|
||||
|
||||
/// This is the offset of the vfptr from the start of the last vbase,
|
||||
/// or the complete type if there are no virtual bases.
|
||||
CharUnits VFPtrOffset;
|
||||
|
||||
/// This holds the base classes path from the complete type to the first base
|
||||
/// with the given vfptr offset.
|
||||
BasePath PathToBaseWithVFPtr;
|
||||
|
||||
/// This is the full offset of the vfptr from the start of the complete type.
|
||||
CharUnits VFPtrFullOffset;
|
||||
};
|
||||
|
||||
class MicrosoftVFTableContext : public VTableContextBase {
|
||||
public:
|
||||
struct MethodVFTableLocation {
|
||||
/// If nonzero, holds the vbtable index of the virtual base with the vfptr.
|
||||
uint64_t VBTableIndex;
|
||||
|
||||
/// This is the offset of the vfptr from the start of the last vbase, or the
|
||||
/// complete type if there are no virtual bases.
|
||||
CharUnits VFTableOffset;
|
||||
|
||||
/// Method's index in the vftable.
|
||||
uint64_t Index;
|
||||
|
||||
MethodVFTableLocation()
|
||||
: VBTableIndex(0), VFTableOffset(CharUnits::Zero()), Index(0) {}
|
||||
|
||||
MethodVFTableLocation(uint64_t VBTableIndex, CharUnits VFTableOffset,
|
||||
uint64_t Index)
|
||||
: VBTableIndex(VBTableIndex), VFTableOffset(VFTableOffset),
|
||||
Index(Index) {}
|
||||
|
||||
bool operator<(const MethodVFTableLocation &other) const {
|
||||
if (VBTableIndex != other.VBTableIndex)
|
||||
return VBTableIndex < other.VBTableIndex;
|
||||
if (VFTableOffset != other.VFTableOffset)
|
||||
return VFTableOffset < other.VFTableOffset;
|
||||
return Index < other.Index;
|
||||
}
|
||||
};
|
||||
|
||||
typedef SmallVector<VFPtrInfo, 1> VFPtrListTy;
|
||||
|
||||
private:
|
||||
ASTContext &Context;
|
||||
|
||||
typedef llvm::DenseMap<GlobalDecl, MethodVFTableLocation>
|
||||
MethodVFTableLocationsTy;
|
||||
MethodVFTableLocationsTy MethodVFTableLocations;
|
||||
|
||||
typedef llvm::DenseMap<const CXXRecordDecl *, VFPtrListTy>
|
||||
VFPtrLocationsMapTy;
|
||||
VFPtrLocationsMapTy VFPtrLocations;
|
||||
|
||||
typedef std::pair<const CXXRecordDecl *, CharUnits> VFTableIdTy;
|
||||
typedef llvm::DenseMap<VFTableIdTy, const VTableLayout *> VFTableLayoutMapTy;
|
||||
VFTableLayoutMapTy VFTableLayouts;
|
||||
|
||||
void computeVTableRelatedInformation(const CXXRecordDecl *RD);
|
||||
|
||||
void dumpMethodLocations(const CXXRecordDecl *RD,
|
||||
const MethodVFTableLocationsTy &NewMethods,
|
||||
raw_ostream &);
|
||||
|
||||
public:
|
||||
MicrosoftVFTableContext(ASTContext &Context) : Context(Context) {}
|
||||
|
||||
~MicrosoftVFTableContext() { llvm::DeleteContainerSeconds(VFTableLayouts); }
|
||||
|
||||
const VFPtrListTy &getVFPtrOffsets(const CXXRecordDecl *RD);
|
||||
|
||||
const VTableLayout &getVFTableLayout(const CXXRecordDecl *RD,
|
||||
CharUnits VFPtrOffset);
|
||||
|
||||
const MethodVFTableLocation &getMethodVFTableLocation(GlobalDecl GD);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -89,6 +89,8 @@ public:
|
|||
void mangleNumber(const llvm::APSInt &Value);
|
||||
void mangleType(QualType T, SourceRange Range,
|
||||
QualifierMangleMode QMM = QMM_Mangle);
|
||||
void mangleFunctionType(const FunctionType *T, const FunctionDecl *D,
|
||||
bool IsStructor, bool IsInstMethod);
|
||||
|
||||
private:
|
||||
void disableBackReferences() { UseNameBackReferences = false; }
|
||||
|
@ -122,8 +124,6 @@ private:
|
|||
#undef TYPE
|
||||
|
||||
void mangleType(const TagType*);
|
||||
void mangleFunctionType(const FunctionType *T, const FunctionDecl *D,
|
||||
bool IsStructor, bool IsInstMethod);
|
||||
void mangleDecayedArrayType(const ArrayType *T, bool IsGlobal);
|
||||
void mangleArrayType(const ArrayType *T);
|
||||
void mangleFunctionClass(const FunctionDecl *FD);
|
||||
|
@ -1781,13 +1781,30 @@ void MicrosoftMangleContext::mangleName(const NamedDecl *D,
|
|||
MicrosoftCXXNameMangler Mangler(*this, Out);
|
||||
return Mangler.mangle(D);
|
||||
}
|
||||
|
||||
void MicrosoftMangleContext::mangleThunk(const CXXMethodDecl *MD,
|
||||
const ThunkInfo &Thunk,
|
||||
raw_ostream &) {
|
||||
unsigned DiagID = getDiags().getCustomDiagID(DiagnosticsEngine::Error,
|
||||
"cannot mangle thunk for this method yet");
|
||||
getDiags().Report(MD->getLocation(), DiagID);
|
||||
raw_ostream &Out) {
|
||||
// FIXME: this is not yet a complete implementation, but merely a
|
||||
// reasonably-working stub to avoid crashing when required to emit a thunk.
|
||||
MicrosoftCXXNameMangler Mangler(*this, Out);
|
||||
Out << "\01?";
|
||||
Mangler.mangleName(MD);
|
||||
if (Thunk.This.NonVirtual != 0) {
|
||||
// FIXME: add support for protected/private or use mangleFunctionClass.
|
||||
Out << "W";
|
||||
llvm::APSInt APSNumber(/*BitWidth=*/32 /*FIXME: check on x64*/,
|
||||
/*isUnsigned=*/true);
|
||||
APSNumber = -Thunk.This.NonVirtual;
|
||||
Mangler.mangleNumber(APSNumber);
|
||||
} else {
|
||||
// FIXME: add support for protected/private or use mangleFunctionClass.
|
||||
Out << "Q";
|
||||
}
|
||||
// FIXME: mangle return adjustment? Most likely includes using an overridee FPT?
|
||||
Mangler.mangleFunctionType(MD->getType()->castAs<FunctionProtoType>(), MD, false, true);
|
||||
}
|
||||
|
||||
void MicrosoftMangleContext::mangleCXXDtorThunk(const CXXDestructorDecl *DD,
|
||||
CXXDtorType Type,
|
||||
const ThisAdjustment &,
|
||||
|
|
|
@ -63,7 +63,7 @@ public:
|
|||
/// Method - The method decl of the overrider.
|
||||
const CXXMethodDecl *Method;
|
||||
|
||||
/// Offset - the base offset of the overrider in the layout class.
|
||||
/// Offset - the base offset of the overrider's parent in the layout class.
|
||||
CharUnits Offset;
|
||||
|
||||
OverriderInfo() : Method(0), Offset(CharUnits::Zero()) { }
|
||||
|
@ -768,6 +768,7 @@ VCallAndVBaseOffsetBuilder::AddVBaseOffsets(const CXXRecordDecl *RD,
|
|||
}
|
||||
|
||||
/// VTableBuilder - Class for building vtable layout information.
|
||||
// FIXME: rename to ItaniumVTableBuilder.
|
||||
class VTableBuilder {
|
||||
public:
|
||||
/// PrimaryBasesSetVectorTy - A set vector of direct and indirect
|
||||
|
@ -1080,23 +1081,44 @@ void VTableBuilder::AddThunk(const CXXMethodDecl *MD, const ThunkInfo &Thunk) {
|
|||
|
||||
typedef llvm::SmallPtrSet<const CXXMethodDecl *, 8> OverriddenMethodsSetTy;
|
||||
|
||||
/// ComputeAllOverriddenMethods - Given a method decl, will return a set of all
|
||||
/// the overridden methods that the function decl overrides.
|
||||
static void
|
||||
ComputeAllOverriddenMethods(const CXXMethodDecl *MD,
|
||||
OverriddenMethodsSetTy& OverriddenMethods) {
|
||||
/// Visit all the methods overridden by the given method recursively,
|
||||
/// in a depth-first pre-order. The Visitor's visitor method returns a bool
|
||||
/// indicating whether to continue the recursion for the given overridden
|
||||
/// method (i.e. returning false stops the iteration).
|
||||
template <class VisitorTy>
|
||||
static void
|
||||
visitAllOverriddenMethods(const CXXMethodDecl *MD, VisitorTy &Visitor) {
|
||||
assert(MD->isVirtual() && "Method is not virtual!");
|
||||
|
||||
for (CXXMethodDecl::method_iterator I = MD->begin_overridden_methods(),
|
||||
E = MD->end_overridden_methods(); I != E; ++I) {
|
||||
const CXXMethodDecl *OverriddenMD = *I;
|
||||
|
||||
OverriddenMethods.insert(OverriddenMD);
|
||||
|
||||
ComputeAllOverriddenMethods(OverriddenMD, OverriddenMethods);
|
||||
if (!Visitor.visit(OverriddenMD))
|
||||
continue;
|
||||
visitAllOverriddenMethods(OverriddenMD, Visitor);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct OverriddenMethodsCollector {
|
||||
OverriddenMethodsSetTy *Methods;
|
||||
|
||||
bool visit(const CXXMethodDecl *MD) {
|
||||
// Don't recurse on this method if we've already collected it.
|
||||
return Methods->insert(MD);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// ComputeAllOverriddenMethods - Given a method decl, will return a set of all
|
||||
/// the overridden methods that the function decl overrides.
|
||||
static void
|
||||
ComputeAllOverriddenMethods(const CXXMethodDecl *MD,
|
||||
OverriddenMethodsSetTy& OverriddenMethods) {
|
||||
OverriddenMethodsCollector Collector = { &OverriddenMethods };
|
||||
visitAllOverriddenMethods(MD, Collector);
|
||||
}
|
||||
|
||||
void VTableBuilder::ComputeThisAdjustments() {
|
||||
// Now go through the method info map and see if any of the methods need
|
||||
// 'this' pointer adjustments.
|
||||
|
@ -1135,7 +1157,7 @@ void VTableBuilder::ComputeThisAdjustments() {
|
|||
// Add it.
|
||||
VTableThunks[VTableIndex].This = ThisAdjustment;
|
||||
|
||||
if (isa<CXXDestructorDecl>(MD)) {
|
||||
if (isa<CXXDestructorDecl>(MD) && !isMicrosoftABI()) {
|
||||
// Add an adjustment for the deleting destructor as well.
|
||||
VTableThunks[VTableIndex + 1].This = ThisAdjustment;
|
||||
}
|
||||
|
@ -1415,18 +1437,21 @@ VTableBuilder::IsOverriderUsed(const CXXMethodDecl *Overrider,
|
|||
return OverridesIndirectMethodInBases(Overrider, PrimaryBases);
|
||||
}
|
||||
|
||||
typedef llvm::SmallSetVector<const CXXRecordDecl *, 8> BasesSetVectorTy;
|
||||
|
||||
/// FindNearestOverriddenMethod - Given a method, returns the overridden method
|
||||
/// from the nearest base. Returns null if no method was found.
|
||||
static const CXXMethodDecl *
|
||||
/// The Bases are expected to be sorted in a base-to-derived order.
|
||||
static const CXXMethodDecl *
|
||||
FindNearestOverriddenMethod(const CXXMethodDecl *MD,
|
||||
VTableBuilder::PrimaryBasesSetVectorTy &Bases) {
|
||||
BasesSetVectorTy &Bases) {
|
||||
OverriddenMethodsSetTy OverriddenMethods;
|
||||
ComputeAllOverriddenMethods(MD, OverriddenMethods);
|
||||
|
||||
for (int I = Bases.size(), E = 0; I != E; --I) {
|
||||
const CXXRecordDecl *PrimaryBase = Bases[I - 1];
|
||||
|
||||
// Now check the overriden methods.
|
||||
// Now check the overridden methods.
|
||||
for (OverriddenMethodsSetTy::const_iterator I = OverriddenMethods.begin(),
|
||||
E = OverriddenMethods.end(); I != E; ++I) {
|
||||
const CXXMethodDecl *OverriddenMD = *I;
|
||||
|
@ -2279,7 +2304,7 @@ uint64_t VTableContext::getMethodVTableIndex(GlobalDecl GD) {
|
|||
|
||||
const CXXRecordDecl *RD = cast<CXXMethodDecl>(GD.getDecl())->getParent();
|
||||
|
||||
ComputeVTableRelatedInformation(RD);
|
||||
computeVTableRelatedInformation(RD);
|
||||
|
||||
I = MethodVTableIndices.find(GD);
|
||||
assert(I != MethodVTableIndices.end() && "Did not find index!");
|
||||
|
@ -2330,7 +2355,7 @@ static VTableLayout *CreateVTableLayout(const VTableBuilder &Builder) {
|
|||
Builder.isMicrosoftABI());
|
||||
}
|
||||
|
||||
void VTableContext::ComputeVTableRelatedInformation(const CXXRecordDecl *RD) {
|
||||
void VTableContext::computeVTableRelatedInformation(const CXXRecordDecl *RD) {
|
||||
const VTableLayout *&Entry = VTableLayouts[RD];
|
||||
|
||||
// Check if we've computed this information before.
|
||||
|
@ -2378,3 +2403,868 @@ VTableLayout *VTableContext::createConstructionVTableLayout(
|
|||
MostDerivedClassIsVirtual, LayoutClass);
|
||||
return CreateVTableLayout(Builder);
|
||||
}
|
||||
|
||||
unsigned clang::GetVBTableIndex(const CXXRecordDecl *Derived,
|
||||
const CXXRecordDecl *VBase) {
|
||||
unsigned VBTableIndex = 1; // Start with one to skip the self entry.
|
||||
for (CXXRecordDecl::base_class_const_iterator I = Derived->vbases_begin(),
|
||||
E = Derived->vbases_end(); I != E; ++I) {
|
||||
if (I->getType()->getAsCXXRecordDecl() == VBase)
|
||||
return VBTableIndex;
|
||||
++VBTableIndex;
|
||||
}
|
||||
llvm_unreachable("VBase must be a vbase of Derived");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Vtables in the Microsoft ABI are different from the Itanium ABI.
|
||||
//
|
||||
// The main differences are:
|
||||
// 1. Separate vftable and vbtable.
|
||||
//
|
||||
// 2. Each subobject with a vfptr gets its own vftable rather than an address
|
||||
// point in a single vtable shared between all the subobjects.
|
||||
// Each vftable is represented by a separate section and virtual calls
|
||||
// must be done using the vftable which has a slot for the function to be
|
||||
// called.
|
||||
//
|
||||
// 3. Virtual method definitions expect their 'this' parameter to point to the
|
||||
// first vfptr whose table provides a compatible overridden method. In many
|
||||
// cases, this permits the original vf-table entry to directly call
|
||||
// the method instead of passing through a thunk.
|
||||
//
|
||||
// A compatible overridden method is one which does not have a non-trivial
|
||||
// covariant-return adjustment.
|
||||
//
|
||||
// The first vfptr is the one with the lowest offset in the complete-object
|
||||
// layout of the defining class, and the method definition will subtract
|
||||
// that constant offset from the parameter value to get the real 'this'
|
||||
// value. Therefore, if the offset isn't really constant (e.g. if a virtual
|
||||
// function defined in a virtual base is overridden in a more derived
|
||||
// virtual base and these bases have a reverse order in the complete
|
||||
// object), the vf-table may require a this-adjustment thunk.
|
||||
//
|
||||
// 4. vftables do not contain new entries for overrides that merely require
|
||||
// this-adjustment. Together with #3, this keeps vf-tables smaller and
|
||||
// eliminates the need for this-adjustment thunks in many cases, at the cost
|
||||
// of often requiring redundant work to adjust the "this" pointer.
|
||||
//
|
||||
// 5. Instead of VTT and constructor vtables, vbtables and vtordisps are used.
|
||||
// Vtordisps are emitted into the class layout if a class has
|
||||
// a) a user-defined ctor/dtor
|
||||
// and
|
||||
// b) a method overriding a method in a virtual base.
|
||||
|
||||
class VFTableBuilder {
|
||||
public:
|
||||
typedef MicrosoftVFTableContext::MethodVFTableLocation MethodVFTableLocation;
|
||||
|
||||
typedef llvm::DenseMap<GlobalDecl, MethodVFTableLocation>
|
||||
MethodVFTableLocationsTy;
|
||||
|
||||
private:
|
||||
/// Context - The ASTContext which we will use for layout information.
|
||||
ASTContext &Context;
|
||||
|
||||
/// MostDerivedClass - The most derived class for which we're building this
|
||||
/// vtable.
|
||||
const CXXRecordDecl *MostDerivedClass;
|
||||
|
||||
const ASTRecordLayout &MostDerivedClassLayout;
|
||||
|
||||
VFPtrInfo WhichVFPtr;
|
||||
|
||||
/// FinalOverriders - The final overriders of the most derived class.
|
||||
const FinalOverriders Overriders;
|
||||
|
||||
/// Components - The components of the vftable being built.
|
||||
SmallVector<VTableComponent, 64> Components;
|
||||
|
||||
MethodVFTableLocationsTy MethodVFTableLocations;
|
||||
|
||||
/// MethodInfo - Contains information about a method in a vtable.
|
||||
/// (Used for computing 'this' pointer adjustment thunks.
|
||||
struct MethodInfo {
|
||||
/// VBTableIndex - The nonzero index in the vbtable that
|
||||
/// this method's base has, or zero.
|
||||
const uint64_t VBTableIndex;
|
||||
|
||||
/// VFTableIndex - The index in the vftable that this method has.
|
||||
const uint64_t VFTableIndex;
|
||||
|
||||
/// Shadowed - Indicates if this vftable slot is shadowed by
|
||||
/// a slot for a covariant-return override. If so, it shouldn't be printed
|
||||
/// or used for vcalls in the most derived class.
|
||||
bool Shadowed;
|
||||
|
||||
MethodInfo(uint64_t VBTableIndex, uint64_t VFTableIndex)
|
||||
: VBTableIndex(VBTableIndex), VFTableIndex(VFTableIndex),
|
||||
Shadowed(false) {}
|
||||
|
||||
MethodInfo() : VBTableIndex(0), VFTableIndex(0), Shadowed(false) {}
|
||||
};
|
||||
|
||||
typedef llvm::DenseMap<const CXXMethodDecl *, MethodInfo> MethodInfoMapTy;
|
||||
|
||||
/// MethodInfoMap - The information for all methods in the vftable we're
|
||||
/// currently building.
|
||||
MethodInfoMapTy MethodInfoMap;
|
||||
|
||||
typedef llvm::DenseMap<uint64_t, ThunkInfo> VTableThunksMapTy;
|
||||
|
||||
/// VTableThunks - The thunks by vftable index in the vftable currently being
|
||||
/// built.
|
||||
VTableThunksMapTy VTableThunks;
|
||||
|
||||
typedef SmallVector<ThunkInfo, 1> ThunkInfoVectorTy;
|
||||
typedef llvm::DenseMap<const CXXMethodDecl *, ThunkInfoVectorTy> ThunksMapTy;
|
||||
|
||||
/// Thunks - A map that contains all the thunks needed for all methods in the
|
||||
/// most derived class for which the vftable is currently being built.
|
||||
ThunksMapTy Thunks;
|
||||
|
||||
/// AddThunk - Add a thunk for the given method.
|
||||
void AddThunk(const CXXMethodDecl *MD, const ThunkInfo &Thunk) {
|
||||
SmallVector<ThunkInfo, 1> &ThunksVector = Thunks[MD];
|
||||
|
||||
// Check if we have this thunk already.
|
||||
if (std::find(ThunksVector.begin(), ThunksVector.end(), Thunk) !=
|
||||
ThunksVector.end())
|
||||
return;
|
||||
|
||||
ThunksVector.push_back(Thunk);
|
||||
}
|
||||
|
||||
/// ComputeThisOffset - Returns the 'this' argument offset for the given
|
||||
/// method in the given subobject, relative to the beginning of the
|
||||
/// MostDerivedClass.
|
||||
CharUnits ComputeThisOffset(const CXXMethodDecl *MD,
|
||||
BaseSubobject Base,
|
||||
FinalOverriders::OverriderInfo Overrider);
|
||||
|
||||
/// AddMethod - Add a single virtual member function to the vftable
|
||||
/// components vector.
|
||||
void AddMethod(const CXXMethodDecl *MD, ThisAdjustment ThisAdjustment,
|
||||
ReturnAdjustment ReturnAdjustment) {
|
||||
if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
|
||||
assert(ReturnAdjustment.isEmpty() &&
|
||||
"Destructor can't have return adjustment!");
|
||||
Components.push_back(VTableComponent::MakeDeletingDtor(DD));
|
||||
} else {
|
||||
// Add the return adjustment if necessary.
|
||||
if (!ReturnAdjustment.isEmpty() || !ThisAdjustment.isEmpty()) {
|
||||
VTableThunks[Components.size()].Return = ReturnAdjustment;
|
||||
VTableThunks[Components.size()].This = ThisAdjustment;
|
||||
}
|
||||
Components.push_back(VTableComponent::MakeFunction(MD));
|
||||
}
|
||||
}
|
||||
|
||||
/// AddMethods - Add the methods of this base subobject and the relevant
|
||||
/// subbases to the vftable we're currently laying out.
|
||||
void AddMethods(BaseSubobject Base, unsigned BaseDepth,
|
||||
const CXXRecordDecl *LastVBase,
|
||||
BasesSetVectorTy &VisitedBases);
|
||||
|
||||
void LayoutVFTable() {
|
||||
// FIXME: add support for RTTI when we have proper LLVM support for symbols
|
||||
// pointing to the middle of a section.
|
||||
|
||||
BasesSetVectorTy VisitedBases;
|
||||
AddMethods(BaseSubobject(MostDerivedClass, CharUnits::Zero()), 0, 0,
|
||||
VisitedBases);
|
||||
|
||||
assert(MethodVFTableLocations.empty());
|
||||
for (MethodInfoMapTy::const_iterator I = MethodInfoMap.begin(),
|
||||
E = MethodInfoMap.end(); I != E; ++I) {
|
||||
const CXXMethodDecl *MD = I->first;
|
||||
const MethodInfo &MI = I->second;
|
||||
// Skip the methods that the MostDerivedClass didn't override
|
||||
// and the entries shadowed by return adjusting thunks.
|
||||
if (MD->getParent() != MostDerivedClass || MI.Shadowed)
|
||||
continue;
|
||||
MethodVFTableLocation Loc(MI.VBTableIndex, WhichVFPtr.VFPtrOffset,
|
||||
MI.VFTableIndex);
|
||||
if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
|
||||
MethodVFTableLocations[GlobalDecl(DD, Dtor_Deleting)] = Loc;
|
||||
} else {
|
||||
MethodVFTableLocations[MD] = Loc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorUnsupported(StringRef Feature, SourceLocation Location) {
|
||||
clang::DiagnosticsEngine &Diags = Context.getDiagnostics();
|
||||
unsigned DiagID = Diags.getCustomDiagID(
|
||||
DiagnosticsEngine::Error, "v-table layout for %0 is not supported yet");
|
||||
Diags.Report(Context.getFullLoc(Location), DiagID) << Feature;
|
||||
}
|
||||
|
||||
public:
|
||||
VFTableBuilder(const CXXRecordDecl *MostDerivedClass, VFPtrInfo Which)
|
||||
: Context(MostDerivedClass->getASTContext()),
|
||||
MostDerivedClass(MostDerivedClass),
|
||||
MostDerivedClassLayout(Context.getASTRecordLayout(MostDerivedClass)),
|
||||
WhichVFPtr(Which),
|
||||
Overriders(MostDerivedClass, CharUnits(), MostDerivedClass) {
|
||||
LayoutVFTable();
|
||||
|
||||
if (Context.getLangOpts().DumpVTableLayouts)
|
||||
dumpLayout(llvm::errs());
|
||||
}
|
||||
|
||||
uint64_t getNumThunks() const { return Thunks.size(); }
|
||||
|
||||
ThunksMapTy::const_iterator thunks_begin() const { return Thunks.begin(); }
|
||||
|
||||
ThunksMapTy::const_iterator thunks_end() const { return Thunks.end(); }
|
||||
|
||||
MethodVFTableLocationsTy::const_iterator vtable_indices_begin() const {
|
||||
return MethodVFTableLocations.begin();
|
||||
}
|
||||
|
||||
MethodVFTableLocationsTy::const_iterator vtable_indices_end() const {
|
||||
return MethodVFTableLocations.end();
|
||||
}
|
||||
|
||||
uint64_t getNumVTableComponents() const { return Components.size(); }
|
||||
|
||||
const VTableComponent *vtable_component_begin() const {
|
||||
return Components.begin();
|
||||
}
|
||||
|
||||
const VTableComponent *vtable_component_end() const {
|
||||
return Components.end();
|
||||
}
|
||||
|
||||
VTableThunksMapTy::const_iterator vtable_thunks_begin() const {
|
||||
return VTableThunks.begin();
|
||||
}
|
||||
|
||||
VTableThunksMapTy::const_iterator vtable_thunks_end() const {
|
||||
return VTableThunks.end();
|
||||
}
|
||||
|
||||
void dumpLayout(raw_ostream &);
|
||||
};
|
||||
|
||||
/// InitialOverriddenDefinitionCollector - Finds the set of least derived bases
|
||||
/// that define the given method.
|
||||
struct InitialOverriddenDefinitionCollector {
|
||||
BasesSetVectorTy Bases;
|
||||
OverriddenMethodsSetTy VisitedOverriddenMethods;
|
||||
|
||||
bool visit(const CXXMethodDecl *OverriddenMD) {
|
||||
if (OverriddenMD->size_overridden_methods() == 0)
|
||||
Bases.insert(OverriddenMD->getParent());
|
||||
// Don't recurse on this method if we've already collected it.
|
||||
return VisitedOverriddenMethods.insert(OverriddenMD);
|
||||
}
|
||||
};
|
||||
|
||||
static bool BaseInSet(const CXXBaseSpecifier *Specifier,
|
||||
CXXBasePath &Path, void *BasesSet) {
|
||||
BasesSetVectorTy *Bases = (BasesSetVectorTy *)BasesSet;
|
||||
return Bases->count(Specifier->getType()->getAsCXXRecordDecl());
|
||||
}
|
||||
|
||||
CharUnits
|
||||
VFTableBuilder::ComputeThisOffset(const CXXMethodDecl *MD,
|
||||
BaseSubobject Base,
|
||||
FinalOverriders::OverriderInfo Overrider) {
|
||||
// Complete object virtual destructors are always emitted in the most derived
|
||||
// class, thus don't have this offset.
|
||||
if (isa<CXXDestructorDecl>(MD))
|
||||
return CharUnits();
|
||||
|
||||
InitialOverriddenDefinitionCollector Collector;
|
||||
visitAllOverriddenMethods(MD, Collector);
|
||||
|
||||
CXXBasePaths Paths;
|
||||
Base.getBase()->lookupInBases(BaseInSet, &Collector.Bases, Paths);
|
||||
|
||||
// This will hold the smallest this offset among overridees of MD.
|
||||
// This implies that an offset of a non-virtual base will dominate an offset
|
||||
// of a virtual base to potentially reduce the number of thunks required
|
||||
// in the derived classes that inherit this method.
|
||||
CharUnits Ret;
|
||||
bool First = true;
|
||||
|
||||
for (CXXBasePaths::paths_iterator I = Paths.begin(), E = Paths.end();
|
||||
I != E; ++I) {
|
||||
const CXXBasePath &Path = (*I);
|
||||
CharUnits ThisOffset = Base.getBaseOffset();
|
||||
|
||||
// For each path from the overrider to the parents of the overridden methods,
|
||||
// traverse the path, calculating the this offset in the most derived class.
|
||||
for (int J = 0, F = Path.size(); J != F; ++J) {
|
||||
const CXXBasePathElement &Element = Path[J];
|
||||
QualType CurTy = Element.Base->getType();
|
||||
const CXXRecordDecl *PrevRD = Element.Class,
|
||||
*CurRD = CurTy->getAsCXXRecordDecl();
|
||||
const ASTRecordLayout &Layout = Context.getASTRecordLayout(PrevRD);
|
||||
|
||||
if (Element.Base->isVirtual()) {
|
||||
if (Overrider.Method->getParent() == PrevRD) {
|
||||
// This one's interesting. If the final overrider is in a vbase B of the
|
||||
// most derived class and it overrides a method of the B's own vbase A,
|
||||
// it uses A* as "this". In its prologue, it can cast A* to B* with
|
||||
// a static offset. This offset is used regardless of the actual
|
||||
// offset of A from B in the most derived class, requiring an
|
||||
// this-adjusting thunk in the vftable if A and B are laid out
|
||||
// differently in the most derived class.
|
||||
ThisOffset += Layout.getVBaseClassOffset(CurRD);
|
||||
} else {
|
||||
ThisOffset = MostDerivedClassLayout.getVBaseClassOffset(CurRD);
|
||||
}
|
||||
} else {
|
||||
ThisOffset += Layout.getBaseClassOffset(CurRD);
|
||||
}
|
||||
}
|
||||
|
||||
if (Ret > ThisOffset || First) {
|
||||
First = false;
|
||||
Ret = ThisOffset;
|
||||
}
|
||||
}
|
||||
|
||||
assert(!First && "Method not found in the given subobject?");
|
||||
return Ret;
|
||||
}
|
||||
|
||||
static const CXXMethodDecl*
|
||||
FindDirectlyOverriddenMethodInBases(const CXXMethodDecl *MD,
|
||||
BasesSetVectorTy &Bases) {
|
||||
// We can't just iterate over the overridden methods and return the first one
|
||||
// which has its parent in Bases, e.g. this doesn't work when we have
|
||||
// multiple subobjects of the same type that have its virtual function
|
||||
// overridden.
|
||||
for (int I = Bases.size(), E = 0; I != E; --I) {
|
||||
const CXXRecordDecl *CurrentBase = Bases[I - 1];
|
||||
|
||||
for (CXXMethodDecl::method_iterator I = MD->begin_overridden_methods(),
|
||||
E = MD->end_overridden_methods(); I != E; ++I) {
|
||||
const CXXMethodDecl *OverriddenMD = *I;
|
||||
|
||||
if (OverriddenMD->getParent() == CurrentBase)
|
||||
return OverriddenMD;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void VFTableBuilder::AddMethods(BaseSubobject Base, unsigned BaseDepth,
|
||||
const CXXRecordDecl *LastVBase,
|
||||
BasesSetVectorTy &VisitedBases) {
|
||||
const CXXRecordDecl *RD = Base.getBase();
|
||||
if (!RD->isPolymorphic())
|
||||
return;
|
||||
|
||||
const ASTRecordLayout &Layout = Context.getASTRecordLayout(RD);
|
||||
|
||||
// See if this class expands a vftable of the base we look at, which is either
|
||||
// the one defined by the vfptr base path or the primary base of the current class.
|
||||
const CXXRecordDecl *NextBase = 0, *NextLastVBase = LastVBase;
|
||||
CharUnits NextBaseOffset;
|
||||
if (BaseDepth < WhichVFPtr.PathToBaseWithVFPtr.size()) {
|
||||
NextBase = WhichVFPtr.PathToBaseWithVFPtr[BaseDepth];
|
||||
if (Layout.getVBaseOffsetsMap().count(NextBase)) {
|
||||
NextLastVBase = NextBase;
|
||||
NextBaseOffset = MostDerivedClassLayout.getVBaseClassOffset(NextBase);
|
||||
} else {
|
||||
NextBaseOffset =
|
||||
Base.getBaseOffset() + Layout.getBaseClassOffset(NextBase);
|
||||
}
|
||||
} else if (const CXXRecordDecl *PrimaryBase = Layout.getPrimaryBase()) {
|
||||
assert(!Layout.isPrimaryBaseVirtual() &&
|
||||
"No primary virtual bases in this ABI");
|
||||
NextBase = PrimaryBase;
|
||||
NextBaseOffset = Base.getBaseOffset();
|
||||
}
|
||||
|
||||
if (NextBase) {
|
||||
AddMethods(BaseSubobject(NextBase, NextBaseOffset), BaseDepth + 1,
|
||||
NextLastVBase, VisitedBases);
|
||||
if (!VisitedBases.insert(NextBase))
|
||||
llvm_unreachable("Found a duplicate primary base!");
|
||||
}
|
||||
|
||||
// Now go through all virtual member functions and add them to the current
|
||||
// vftable. This is done by
|
||||
// - replacing overridden methods in their existing slots, as long as they
|
||||
// don't require return adjustment; calculating This adjustment if needed.
|
||||
// - adding new slots for methods of the current base not present in any
|
||||
// sub-bases;
|
||||
// - adding new slots for methods that require Return adjustment.
|
||||
// We keep track of the methods visited in the sub-bases in MethodInfoMap.
|
||||
for (CXXRecordDecl::method_iterator I = RD->method_begin(),
|
||||
E = RD->method_end(); I != E; ++I) {
|
||||
const CXXMethodDecl *MD = *I;
|
||||
|
||||
if (!MD->isVirtual())
|
||||
continue;
|
||||
|
||||
FinalOverriders::OverriderInfo Overrider =
|
||||
Overriders.getOverrider(MD, Base.getBaseOffset());
|
||||
ThisAdjustment ThisAdjustmentOffset;
|
||||
|
||||
// Check if this virtual member function overrides
|
||||
// a method in one of the visited bases.
|
||||
if (const CXXMethodDecl *OverriddenMD =
|
||||
FindDirectlyOverriddenMethodInBases(MD, VisitedBases)) {
|
||||
MethodInfoMapTy::iterator OverriddenMDIterator =
|
||||
MethodInfoMap.find(OverriddenMD);
|
||||
|
||||
// If the overridden method went to a different vftable, skip it.
|
||||
if (OverriddenMDIterator == MethodInfoMap.end())
|
||||
continue;
|
||||
|
||||
MethodInfo &OverriddenMethodInfo = OverriddenMDIterator->second;
|
||||
|
||||
// Create a this-adjusting thunk if needed.
|
||||
CharUnits TI = ComputeThisOffset(MD, Base, Overrider);
|
||||
if (TI != WhichVFPtr.VFPtrFullOffset) {
|
||||
ThisAdjustmentOffset.NonVirtual =
|
||||
(TI - WhichVFPtr.VFPtrFullOffset).getQuantity();
|
||||
VTableThunks[OverriddenMethodInfo.VFTableIndex].This =
|
||||
ThisAdjustmentOffset;
|
||||
AddThunk(MD, VTableThunks[OverriddenMethodInfo.VFTableIndex]);
|
||||
}
|
||||
|
||||
if (ComputeReturnAdjustmentBaseOffset(Context, MD, OverriddenMD)
|
||||
.isEmpty()) {
|
||||
// No return adjustment needed - just replace the overridden method info
|
||||
// with the current info.
|
||||
MethodInfo MI(OverriddenMethodInfo.VBTableIndex,
|
||||
OverriddenMethodInfo.VFTableIndex);
|
||||
MethodInfoMap.erase(OverriddenMDIterator);
|
||||
|
||||
assert(!MethodInfoMap.count(MD) &&
|
||||
"Should not have method info for this method yet!");
|
||||
MethodInfoMap.insert(std::make_pair(MD, MI));
|
||||
continue;
|
||||
} else {
|
||||
// In case we need a return adjustment, we'll add a new slot for
|
||||
// the overrider and put a return-adjusting thunk where the overridden
|
||||
// method was in the vftable.
|
||||
// For now, just mark the overriden method as shadowed by a new slot.
|
||||
OverriddenMethodInfo.Shadowed = true;
|
||||
|
||||
// Also apply this adjustment to the shadowed slots.
|
||||
if (!ThisAdjustmentOffset.isEmpty()) {
|
||||
// FIXME: this is O(N^2), can be O(N).
|
||||
const CXXMethodDecl *SubOverride = OverriddenMD;
|
||||
while ((SubOverride =
|
||||
FindDirectlyOverriddenMethodInBases(SubOverride, VisitedBases))) {
|
||||
MethodInfoMapTy::iterator SubOverrideIterator =
|
||||
MethodInfoMap.find(SubOverride);
|
||||
if (SubOverrideIterator == MethodInfoMap.end())
|
||||
break;
|
||||
MethodInfo &SubOverrideMI = SubOverrideIterator->second;
|
||||
assert(SubOverrideMI.Shadowed);
|
||||
VTableThunks[SubOverrideMI.VFTableIndex].This =
|
||||
ThisAdjustmentOffset;
|
||||
AddThunk(MD, VTableThunks[SubOverrideMI.VFTableIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (Base.getBaseOffset() != WhichVFPtr.VFPtrFullOffset ||
|
||||
MD->size_overridden_methods()) {
|
||||
// Skip methods that don't belong to the vftable of the current class,
|
||||
// e.g. each method that wasn't seen in any of the visited sub-bases
|
||||
// but overrides multiple methods of other sub-bases.
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we got here, MD is a method not seen in any of the sub-bases or
|
||||
// it requires return adjustment. Insert the method info for this method.
|
||||
unsigned VBIndex =
|
||||
LastVBase ? GetVBTableIndex(MostDerivedClass, LastVBase) : 0;
|
||||
MethodInfo MI(VBIndex, Components.size());
|
||||
|
||||
assert(!MethodInfoMap.count(MD) &&
|
||||
"Should not have method info for this method yet!");
|
||||
MethodInfoMap.insert(std::make_pair(MD, MI));
|
||||
|
||||
const CXXMethodDecl *OverriderMD = Overrider.Method;
|
||||
|
||||
// Check if this overrider needs a return adjustment.
|
||||
// We don't want to do this for pure virtual member functions.
|
||||
BaseOffset ReturnAdjustmentOffset;
|
||||
ReturnAdjustment ReturnAdjustment;
|
||||
if (!OverriderMD->isPure()) {
|
||||
ReturnAdjustmentOffset =
|
||||
ComputeReturnAdjustmentBaseOffset(Context, OverriderMD, MD);
|
||||
}
|
||||
if (!ReturnAdjustmentOffset.isEmpty()) {
|
||||
ReturnAdjustment.NonVirtual =
|
||||
ReturnAdjustmentOffset.NonVirtualOffset.getQuantity();
|
||||
if (ReturnAdjustmentOffset.VirtualBase) {
|
||||
// FIXME: We might want to create a VBIndex alias for VBaseOffsetOffset
|
||||
// in the ReturnAdjustment struct.
|
||||
ReturnAdjustment.VBaseOffsetOffset =
|
||||
GetVBTableIndex(ReturnAdjustmentOffset.DerivedClass,
|
||||
ReturnAdjustmentOffset.VirtualBase);
|
||||
}
|
||||
}
|
||||
|
||||
AddMethod(Overrider.Method, ThisAdjustmentOffset, ReturnAdjustment);
|
||||
}
|
||||
}
|
||||
|
||||
void PrintBasePath(const VFPtrInfo::BasePath &Path, raw_ostream &Out) {
|
||||
for (VFPtrInfo::BasePath::const_reverse_iterator I = Path.rbegin(),
|
||||
E = Path.rend(); I != E; ++I) {
|
||||
Out << "'" << (*I)->getQualifiedNameAsString() << "' in ";
|
||||
}
|
||||
}
|
||||
|
||||
void VFTableBuilder::dumpLayout(raw_ostream &Out) {
|
||||
Out << "VFTable for ";
|
||||
PrintBasePath(WhichVFPtr.PathToBaseWithVFPtr, Out);
|
||||
Out << "'" << MostDerivedClass->getQualifiedNameAsString();
|
||||
Out << "' (" << Components.size() << " entries).\n";
|
||||
|
||||
for (unsigned I = 0, E = Components.size(); I != E; ++I) {
|
||||
Out << llvm::format("%4d | ", I);
|
||||
|
||||
const VTableComponent &Component = Components[I];
|
||||
|
||||
// Dump the component.
|
||||
switch (Component.getKind()) {
|
||||
case VTableComponent::CK_RTTI:
|
||||
Out << Component.getRTTIDecl()->getQualifiedNameAsString() << " RTTI";
|
||||
break;
|
||||
|
||||
case VTableComponent::CK_FunctionPointer: {
|
||||
const CXXMethodDecl *MD = Component.getFunctionDecl();
|
||||
|
||||
std::string Str = PredefinedExpr::ComputeName(
|
||||
PredefinedExpr::PrettyFunctionNoVirtual, MD);
|
||||
Out << Str;
|
||||
if (MD->isPure())
|
||||
Out << " [pure]";
|
||||
|
||||
if (MD->isDeleted()) {
|
||||
ErrorUnsupported("deleted methods", MD->getLocation());
|
||||
Out << " [deleted]";
|
||||
}
|
||||
|
||||
ThunkInfo Thunk = VTableThunks.lookup(I);
|
||||
if (!Thunk.isEmpty()) {
|
||||
// If this function pointer has a return adjustment, dump it.
|
||||
if (!Thunk.Return.isEmpty()) {
|
||||
Out << "\n [return adjustment: ";
|
||||
if (Thunk.Return.VBaseOffsetOffset)
|
||||
Out << "vbase #" << Thunk.Return.VBaseOffsetOffset << ", ";
|
||||
Out << Thunk.Return.NonVirtual << " non-virtual]";
|
||||
}
|
||||
|
||||
// If this function pointer has a 'this' pointer adjustment, dump it.
|
||||
if (!Thunk.This.isEmpty()) {
|
||||
assert(!Thunk.This.VCallOffsetOffset &&
|
||||
"No virtual this adjustment in this ABI");
|
||||
Out << "\n [this adjustment: " << Thunk.This.NonVirtual
|
||||
<< " non-virtual]";
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case VTableComponent::CK_DeletingDtorPointer: {
|
||||
const CXXDestructorDecl *DD = Component.getDestructorDecl();
|
||||
|
||||
Out << DD->getQualifiedNameAsString();
|
||||
Out << "() [scalar deleting]";
|
||||
|
||||
if (DD->isPure())
|
||||
Out << " [pure]";
|
||||
|
||||
ThunkInfo Thunk = VTableThunks.lookup(I);
|
||||
if (!Thunk.isEmpty()) {
|
||||
assert(Thunk.Return.isEmpty() &&
|
||||
"No return adjustment needed for destructors!");
|
||||
// If this destructor has a 'this' pointer adjustment, dump it.
|
||||
if (!Thunk.This.isEmpty()) {
|
||||
assert(!Thunk.This.VCallOffsetOffset &&
|
||||
"No virtual this adjustment in this ABI");
|
||||
Out << "\n [this adjustment: " << Thunk.This.NonVirtual
|
||||
<< " non-virtual]";
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
DiagnosticsEngine &Diags = Context.getDiagnostics();
|
||||
unsigned DiagID = Diags.getCustomDiagID(
|
||||
DiagnosticsEngine::Error,
|
||||
"Unexpected vftable component type %0 for component number %1");
|
||||
Diags.Report(MostDerivedClass->getLocation(), DiagID)
|
||||
<< I << Component.getKind();
|
||||
}
|
||||
|
||||
Out << '\n';
|
||||
}
|
||||
|
||||
Out << '\n';
|
||||
|
||||
if (!Thunks.empty()) {
|
||||
// We store the method names in a map to get a stable order.
|
||||
std::map<std::string, const CXXMethodDecl *> MethodNamesAndDecls;
|
||||
|
||||
for (ThunksMapTy::const_iterator I = Thunks.begin(), E = Thunks.end();
|
||||
I != E; ++I) {
|
||||
const CXXMethodDecl *MD = I->first;
|
||||
std::string MethodName = PredefinedExpr::ComputeName(
|
||||
PredefinedExpr::PrettyFunctionNoVirtual, MD);
|
||||
|
||||
MethodNamesAndDecls.insert(std::make_pair(MethodName, MD));
|
||||
}
|
||||
|
||||
for (std::map<std::string, const CXXMethodDecl *>::const_iterator
|
||||
I = MethodNamesAndDecls.begin(),
|
||||
E = MethodNamesAndDecls.end();
|
||||
I != E; ++I) {
|
||||
const std::string &MethodName = I->first;
|
||||
const CXXMethodDecl *MD = I->second;
|
||||
|
||||
ThunkInfoVectorTy ThunksVector = Thunks[MD];
|
||||
std::sort(ThunksVector.begin(), ThunksVector.end());
|
||||
|
||||
Out << "Thunks for '" << MethodName << "' (" << ThunksVector.size();
|
||||
Out << (ThunksVector.size() == 1 ? " entry" : " entries") << ").\n";
|
||||
|
||||
for (unsigned I = 0, E = ThunksVector.size(); I != E; ++I) {
|
||||
const ThunkInfo &Thunk = ThunksVector[I];
|
||||
|
||||
Out << llvm::format("%4d | ", I);
|
||||
|
||||
// If this function pointer has a return pointer adjustment, dump it.
|
||||
if (!Thunk.Return.isEmpty()) {
|
||||
Out << "return adjustment: ";
|
||||
if (Thunk.Return.VBaseOffsetOffset)
|
||||
Out << "vbase #" << Thunk.Return.VBaseOffsetOffset << ", ";
|
||||
Out << Thunk.Return.NonVirtual << " non-virtual";
|
||||
|
||||
if (!Thunk.This.isEmpty())
|
||||
Out << "\n ";
|
||||
}
|
||||
|
||||
// If this function pointer has a 'this' pointer adjustment, dump it.
|
||||
if (!Thunk.This.isEmpty()) {
|
||||
assert(!Thunk.This.VCallOffsetOffset &&
|
||||
"No virtual this adjustment in this ABI");
|
||||
Out << "this adjustment: ";
|
||||
Out << Thunk.This.NonVirtual << " non-virtual";
|
||||
}
|
||||
|
||||
Out << '\n';
|
||||
}
|
||||
|
||||
Out << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void EnumerateVFPtrs(
|
||||
ASTContext &Context, const CXXRecordDecl *MostDerivedClass,
|
||||
const ASTRecordLayout &MostDerivedClassLayout,
|
||||
BaseSubobject Base, const CXXRecordDecl *LastVBase,
|
||||
const VFPtrInfo::BasePath &PathFromCompleteClass,
|
||||
BasesSetVectorTy &VisitedVBases,
|
||||
MicrosoftVFTableContext::VFPtrListTy &Result) {
|
||||
const CXXRecordDecl *CurrentClass = Base.getBase();
|
||||
CharUnits OffsetInCompleteClass = Base.getBaseOffset();
|
||||
const ASTRecordLayout &CurrentClassLayout =
|
||||
Context.getASTRecordLayout(CurrentClass);
|
||||
|
||||
if (CurrentClassLayout.hasOwnVFPtr()) {
|
||||
if (LastVBase) {
|
||||
uint64_t VBIndex = GetVBTableIndex(MostDerivedClass, LastVBase);
|
||||
assert(VBIndex > 0 && "vbases must have vbindex!");
|
||||
CharUnits VFPtrOffset =
|
||||
OffsetInCompleteClass -
|
||||
MostDerivedClassLayout.getVBaseClassOffset(LastVBase);
|
||||
Result.push_back(VFPtrInfo(VBIndex, LastVBase, VFPtrOffset,
|
||||
PathFromCompleteClass, OffsetInCompleteClass));
|
||||
} else {
|
||||
Result.push_back(VFPtrInfo(OffsetInCompleteClass, PathFromCompleteClass));
|
||||
}
|
||||
}
|
||||
|
||||
for (CXXRecordDecl::base_class_const_iterator I = CurrentClass->bases_begin(),
|
||||
E = CurrentClass->bases_end(); I != E; ++I) {
|
||||
const CXXRecordDecl *BaseDecl = I->getType()->getAsCXXRecordDecl();
|
||||
|
||||
CharUnits NextBaseOffset;
|
||||
const CXXRecordDecl *NextLastVBase;
|
||||
if (I->isVirtual()) {
|
||||
if (VisitedVBases.count(BaseDecl))
|
||||
continue;
|
||||
VisitedVBases.insert(BaseDecl);
|
||||
NextBaseOffset = MostDerivedClassLayout.getVBaseClassOffset(BaseDecl);
|
||||
NextLastVBase = BaseDecl;
|
||||
} else {
|
||||
NextBaseOffset = OffsetInCompleteClass +
|
||||
CurrentClassLayout.getBaseClassOffset(BaseDecl);
|
||||
NextLastVBase = LastVBase;
|
||||
}
|
||||
|
||||
VFPtrInfo::BasePath NewPath = PathFromCompleteClass;
|
||||
NewPath.push_back(BaseDecl);
|
||||
BaseSubobject NextBase(BaseDecl, NextBaseOffset);
|
||||
|
||||
EnumerateVFPtrs(Context, MostDerivedClass, MostDerivedClassLayout, NextBase,
|
||||
NextLastVBase, NewPath, VisitedVBases, Result);
|
||||
}
|
||||
}
|
||||
|
||||
void EnumerateVFPtrs(ASTContext &Context, const CXXRecordDecl *ForClass,
|
||||
MicrosoftVFTableContext::VFPtrListTy &Result) {
|
||||
Result.clear();
|
||||
const ASTRecordLayout &ClassLayout = Context.getASTRecordLayout(ForClass);
|
||||
BasesSetVectorTy VisitedVBases;
|
||||
EnumerateVFPtrs(Context, ForClass, ClassLayout,
|
||||
BaseSubobject(ForClass, CharUnits::Zero()), 0,
|
||||
VFPtrInfo::BasePath(), VisitedVBases, Result);
|
||||
}
|
||||
|
||||
void MicrosoftVFTableContext::computeVTableRelatedInformation(
|
||||
const CXXRecordDecl *RD) {
|
||||
assert(RD->isDynamicClass());
|
||||
|
||||
// Check if we've computed this information before.
|
||||
if (VFPtrLocations.count(RD))
|
||||
return;
|
||||
|
||||
const VTableLayout::AddressPointsMapTy EmptyAddressPointsMap;
|
||||
|
||||
VFPtrListTy &VFPtrs = VFPtrLocations[RD];
|
||||
EnumerateVFPtrs(Context, RD, VFPtrs);
|
||||
|
||||
MethodVFTableLocationsTy NewMethodLocations;
|
||||
for (VFPtrListTy::iterator I = VFPtrs.begin(), E = VFPtrs.end();
|
||||
I != E; ++I) {
|
||||
VFTableBuilder Builder(RD, *I);
|
||||
|
||||
VFTableIdTy id(RD, I->VFPtrFullOffset);
|
||||
assert(VFTableLayouts.count(id) == 0);
|
||||
SmallVector<VTableLayout::VTableThunkTy, 1> VTableThunks(
|
||||
Builder.vtable_thunks_begin(), Builder.vtable_thunks_end());
|
||||
std::sort(VTableThunks.begin(), VTableThunks.end());
|
||||
VFTableLayouts[id] = new VTableLayout(
|
||||
Builder.getNumVTableComponents(), Builder.vtable_component_begin(),
|
||||
VTableThunks.size(), VTableThunks.data(), EmptyAddressPointsMap, true);
|
||||
NewMethodLocations.insert(Builder.vtable_indices_begin(),
|
||||
Builder.vtable_indices_end());
|
||||
Thunks.insert(Builder.thunks_begin(), Builder.thunks_end());
|
||||
}
|
||||
|
||||
MethodVFTableLocations.insert(NewMethodLocations.begin(),
|
||||
NewMethodLocations.end());
|
||||
if (Context.getLangOpts().DumpVTableLayouts)
|
||||
dumpMethodLocations(RD, NewMethodLocations, llvm::errs());
|
||||
}
|
||||
|
||||
void MicrosoftVFTableContext::dumpMethodLocations(
|
||||
const CXXRecordDecl *RD, const MethodVFTableLocationsTy &NewMethods,
|
||||
raw_ostream &Out) {
|
||||
// Compute the vtable indices for all the member functions.
|
||||
// Store them in a map keyed by the location so we'll get a sorted table.
|
||||
std::map<MethodVFTableLocation, std::string> IndicesMap;
|
||||
bool HasNonzeroOffset = false;
|
||||
|
||||
for (MethodVFTableLocationsTy::const_iterator I = NewMethods.begin(),
|
||||
E = NewMethods.end(); I != E; ++I) {
|
||||
const CXXMethodDecl *MD = cast<const CXXMethodDecl>(I->first.getDecl());
|
||||
assert(MD->isVirtual());
|
||||
|
||||
std::string MethodName = PredefinedExpr::ComputeName(
|
||||
PredefinedExpr::PrettyFunctionNoVirtual, MD);
|
||||
|
||||
if (isa<CXXDestructorDecl>(MD)) {
|
||||
IndicesMap[I->second] = MethodName + " [scalar deleting]";
|
||||
} else {
|
||||
IndicesMap[I->second] = MethodName;
|
||||
}
|
||||
|
||||
if (!I->second.VFTableOffset.isZero() || I->second.VBTableIndex != 0)
|
||||
HasNonzeroOffset = true;
|
||||
}
|
||||
|
||||
// Print the vtable indices for all the member functions.
|
||||
if (!IndicesMap.empty()) {
|
||||
Out << "VFTable indices for ";
|
||||
Out << "'" << RD->getQualifiedNameAsString();
|
||||
Out << "' (" << IndicesMap.size() << " entries).\n";
|
||||
|
||||
CharUnits LastVFPtrOffset = CharUnits::fromQuantity(-1);
|
||||
uint64_t LastVBIndex = 0;
|
||||
for (std::map<MethodVFTableLocation, std::string>::const_iterator
|
||||
I = IndicesMap.begin(),
|
||||
E = IndicesMap.end();
|
||||
I != E; ++I) {
|
||||
CharUnits VFPtrOffset = I->first.VFTableOffset;
|
||||
uint64_t VBIndex = I->first.VBTableIndex;
|
||||
if (HasNonzeroOffset &&
|
||||
(VFPtrOffset != LastVFPtrOffset || VBIndex != LastVBIndex)) {
|
||||
assert(VBIndex > LastVBIndex || VFPtrOffset > LastVFPtrOffset);
|
||||
Out << " -- accessible via ";
|
||||
if (VBIndex)
|
||||
Out << "vbtable index " << VBIndex << ", ";
|
||||
Out << "vfptr at offset " << VFPtrOffset.getQuantity() << " --\n";
|
||||
LastVFPtrOffset = VFPtrOffset;
|
||||
LastVBIndex = VBIndex;
|
||||
}
|
||||
|
||||
uint64_t VTableIndex = I->first.Index;
|
||||
const std::string &MethodName = I->second;
|
||||
Out << llvm::format("%4" PRIu64 " | ", VTableIndex) << MethodName << '\n';
|
||||
}
|
||||
Out << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
const MicrosoftVFTableContext::VFPtrListTy &
|
||||
MicrosoftVFTableContext::getVFPtrOffsets(const CXXRecordDecl *RD) {
|
||||
computeVTableRelatedInformation(RD);
|
||||
|
||||
assert(VFPtrLocations.count(RD) && "Couldn't find vfptr locations");
|
||||
return VFPtrLocations[RD];
|
||||
}
|
||||
|
||||
const VTableLayout &
|
||||
MicrosoftVFTableContext::getVFTableLayout(const CXXRecordDecl *RD,
|
||||
CharUnits VFPtrOffset) {
|
||||
computeVTableRelatedInformation(RD);
|
||||
|
||||
VFTableIdTy id(RD, VFPtrOffset);
|
||||
assert(VFTableLayouts.count(id) && "Couldn't find a VFTable at this offset");
|
||||
return *VFTableLayouts[id];
|
||||
}
|
||||
|
||||
const MicrosoftVFTableContext::MethodVFTableLocation &
|
||||
MicrosoftVFTableContext::getMethodVFTableLocation(GlobalDecl GD) {
|
||||
assert(cast<CXXMethodDecl>(GD.getDecl())->isVirtual() &&
|
||||
"Only use this method for virtual methods or dtors");
|
||||
if (isa<CXXDestructorDecl>(GD.getDecl()))
|
||||
assert(GD.getDtorType() == Dtor_Deleting);
|
||||
|
||||
MethodVFTableLocationsTy::iterator I = MethodVFTableLocations.find(GD);
|
||||
if (I != MethodVFTableLocations.end())
|
||||
return I->second;
|
||||
|
||||
const CXXRecordDecl *RD = cast<CXXMethodDecl>(GD.getDecl())->getParent();
|
||||
|
||||
computeVTableRelatedInformation(RD);
|
||||
|
||||
I = MethodVFTableLocations.find(GD);
|
||||
assert(I != MethodVFTableLocations.end() && "Did not find index!");
|
||||
return I->second;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,14 @@ using namespace clang;
|
|||
using namespace CodeGen;
|
||||
|
||||
CodeGenVTables::CodeGenVTables(CodeGenModule &CGM)
|
||||
: CGM(CGM), VTContext(CGM.getContext()) { }
|
||||
: CGM(CGM), VTContext(CGM.getContext()) {
|
||||
if (CGM.getTarget().getCXXABI().isMicrosoft()) {
|
||||
// FIXME: Eventually, we should only have one of V*TContexts available.
|
||||
// Today we use both in the Microsoft ABI as MicrosoftVFTableContext
|
||||
// is not completely supported in CodeGen yet.
|
||||
VFTContext.reset(new MicrosoftVFTableContext(CGM.getContext()));
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Constant *CodeGenModule::GetAddrOfThunk(GlobalDecl GD,
|
||||
const ThunkInfo &Thunk) {
|
||||
|
@ -389,6 +396,11 @@ void CodeGenFunction::GenerateThunk(llvm::Function *Fn,
|
|||
void CodeGenVTables::EmitThunk(GlobalDecl GD, const ThunkInfo &Thunk,
|
||||
bool UseAvailableExternallyLinkage)
|
||||
{
|
||||
if (CGM.getTarget().getCXXABI().isMicrosoft()) {
|
||||
// Emission of thunks is not supported yet in Microsoft ABI.
|
||||
return;
|
||||
}
|
||||
|
||||
const CGFunctionInfo &FnInfo = CGM.getTypes().arrangeGlobalDeclaration(GD);
|
||||
|
||||
// FIXME: re-use FnInfo in this computation.
|
||||
|
@ -485,6 +497,12 @@ void CodeGenVTables::EmitThunks(GlobalDecl GD)
|
|||
if (isa<CXXDestructorDecl>(MD) && GD.getDtorType() == Dtor_Base)
|
||||
return;
|
||||
|
||||
if (VFTContext.isValid()) {
|
||||
// FIXME: This is a temporary solution to force generation of vftables in
|
||||
// Microsoft ABI. Remove when we thread VFTableContext through CodeGen.
|
||||
VFTContext->getVFPtrOffsets(MD->getParent());
|
||||
}
|
||||
|
||||
const VTableContext::ThunkInfoVectorTy *ThunkInfoVector =
|
||||
VTContext.getThunkInfo(MD);
|
||||
if (!ThunkInfoVector)
|
||||
|
@ -804,6 +822,12 @@ void CodeGenModule::EmitVTable(CXXRecordDecl *theClass, bool isRequired) {
|
|||
|
||||
void
|
||||
CodeGenVTables::GenerateClassData(const CXXRecordDecl *RD) {
|
||||
if (VFTContext.isValid()) {
|
||||
// FIXME: This is a temporary solution to force generation of vftables in
|
||||
// Microsoft ABI. Remove when we thread VFTableContext through CodeGen.
|
||||
VFTContext->getVFPtrOffsets(RD);
|
||||
}
|
||||
|
||||
// First off, check whether we've already emitted the v-table and
|
||||
// associated stuff.
|
||||
llvm::GlobalVariable *VTable = GetAddrOfVTable(RD);
|
||||
|
|
|
@ -32,6 +32,7 @@ class CodeGenVTables {
|
|||
CodeGenModule &CGM;
|
||||
|
||||
VTableContext VTContext;
|
||||
OwningPtr<MicrosoftVFTableContext> VFTContext;
|
||||
|
||||
/// VTables - All the vtables which have been defined.
|
||||
llvm::DenseMap<const CXXRecordDecl *, llvm::GlobalVariable *> VTables;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "MicrosoftVBTables.h"
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/AST/DeclCXX.h"
|
||||
#include "clang/AST/VTableBuilder.h"
|
||||
|
||||
using namespace clang;
|
||||
using namespace CodeGen;
|
||||
|
@ -315,24 +316,6 @@ CharUnits MicrosoftCXXABI::GetVBPtrOffsetFromBases(const CXXRecordDecl *RD) {
|
|||
return Total;
|
||||
}
|
||||
|
||||
/// \brief Computes the index of BaseClassDecl in the vbtable of ClassDecl.
|
||||
/// BaseClassDecl must be a morally virtual base of ClassDecl. The vbtable is
|
||||
/// an array of i32 offsets. The first entry is a self entry, and the rest are
|
||||
/// offsets from the vbptr to virtual bases. The bases are ordered the same way
|
||||
/// our vbases are ordered: as they appear in a left-to-right depth-first search
|
||||
/// of the hierarchy.
|
||||
static unsigned GetVBTableIndex(const CXXRecordDecl *ClassDecl,
|
||||
const CXXRecordDecl *BaseClassDecl) {
|
||||
unsigned VBTableIndex = 1; // Start with one to skip the self entry.
|
||||
for (CXXRecordDecl::base_class_const_iterator I = ClassDecl->vbases_begin(),
|
||||
E = ClassDecl->vbases_end(); I != E; ++I) {
|
||||
if (I->getType()->getAsCXXRecordDecl() == BaseClassDecl)
|
||||
return VBTableIndex;
|
||||
VBTableIndex++;
|
||||
}
|
||||
llvm_unreachable("BaseClassDecl must be a vbase of ClassDecl");
|
||||
}
|
||||
|
||||
llvm::Value *
|
||||
MicrosoftCXXABI::GetVirtualBaseClassOffset(CodeGenFunction &CGF,
|
||||
llvm::Value *This,
|
||||
|
|
|
@ -0,0 +1,431 @@
|
|||
// RUN: %clang_cc1 %s -fno-rtti -cxx-abi microsoft -triple=i386-pc-win32 -emit-llvm -fdump-vtable-layouts -o - >%t 2>&1
|
||||
|
||||
// RUN: FileCheck --check-prefix=NO-THUNKS-Test1 %s < %t
|
||||
// RUN: FileCheck --check-prefix=NO-THUNKS-Test2 %s < %t
|
||||
// RUN: FileCheck --check-prefix=NO-THUNKS-Test3 %s < %t
|
||||
// RUN: FileCheck --check-prefix=NO-THUNKS-Test4 %s < %t
|
||||
// RUN: FileCheck --check-prefix=NO-THUNKS-Test5 %s < %t
|
||||
// RUN: FileCheck --check-prefix=NO-THUNKS-Test6 %s < %t
|
||||
// RUN: FileCheck --check-prefix=NO-THUNKS-Test7 %s < %t
|
||||
// RUN: FileCheck --check-prefix=NO-THUNKS-Test8 %s < %t
|
||||
// RUN: FileCheck --check-prefix=NO-THUNKS-Test9 %s < %t
|
||||
// RUN: FileCheck --check-prefix=PURE-VIRTUAL-Test1 %s < %t
|
||||
// RUN: FileCheck --check-prefix=THIS-THUNKS-Test1 %s < %t
|
||||
// RUN: FileCheck --check-prefix=THIS-THUNKS-Test2 %s < %t
|
||||
// RUN: FileCheck --check-prefix=THIS-THUNKS-Test3 %s < %t
|
||||
// RUN: FileCheck --check-prefix=RET-THUNKS-Test1 %s < %t
|
||||
// RUN: FileCheck --check-prefix=RET-THUNKS-Test2 %s < %t
|
||||
// RUN: FileCheck --check-prefix=RET-THUNKS-Test3 %s < %t
|
||||
// RUN: FileCheck --check-prefix=RET-THUNKS-Test4 %s < %t
|
||||
// RUN: FileCheck --check-prefix=RET-THUNKS-Test5 %s < %t
|
||||
|
||||
struct Empty {
|
||||
// Doesn't have a vftable!
|
||||
};
|
||||
|
||||
struct A {
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
struct B {
|
||||
virtual void g();
|
||||
// Add an extra virtual method so it's easier to check for the absence of thunks.
|
||||
virtual void h();
|
||||
};
|
||||
|
||||
struct C {
|
||||
virtual void g(); // Might "collide" with B::g if both are bases of some class.
|
||||
};
|
||||
|
||||
|
||||
namespace no_thunks {
|
||||
|
||||
struct Test1: A, B {
|
||||
// NO-THUNKS-Test1: VFTable for 'A' in 'no_thunks::Test1' (1 entries)
|
||||
// NO-THUNKS-Test1-NEXT: 0 | void no_thunks::Test1::f()
|
||||
|
||||
// NO-THUNKS-Test1: VFTable for 'B' in 'no_thunks::Test1' (2 entries)
|
||||
// NO-THUNKS-Test1-NEXT: 0 | void B::g()
|
||||
// NO-THUNKS-Test1-NEXT: 1 | void B::h()
|
||||
|
||||
// NO-THUNKS-Test1: VFTable indices for 'no_thunks::Test1' (1 entries)
|
||||
// NO-THUNKS-Test1-NEXT: 0 | void no_thunks::Test1::f()
|
||||
|
||||
// Overrides only the left child's method (A::f), needs no thunks.
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
Test1 t1;
|
||||
|
||||
struct Test2: A, B {
|
||||
// NO-THUNKS-Test2: VFTable for 'A' in 'no_thunks::Test2' (1 entries)
|
||||
// NO-THUNKS-Test2-NEXT: 0 | void A::f()
|
||||
|
||||
// NO-THUNKS-Test2: VFTable for 'B' in 'no_thunks::Test2' (2 entries)
|
||||
// NO-THUNKS-Test2-NEXT: 0 | void no_thunks::Test2::g()
|
||||
// NO-THUNKS-Test2-NEXT: 1 | void B::h()
|
||||
|
||||
// NO-THUNKS-Test2: VFTable indices for 'no_thunks::Test2' (1 entries).
|
||||
// NO-THUNKS-Test2-NEXT: via vfptr at offset 4
|
||||
// NO-THUNKS-Test2-NEXT: 0 | void no_thunks::Test2::g()
|
||||
|
||||
// Overrides only the right child's method (B::g), needs this adjustment but
|
||||
// not thunks.
|
||||
virtual void g();
|
||||
};
|
||||
|
||||
Test2 t2;
|
||||
|
||||
struct Test3: A, B {
|
||||
// NO-THUNKS-Test3: VFTable for 'A' in 'no_thunks::Test3' (2 entries)
|
||||
// NO-THUNKS-Test3-NEXT: 0 | void A::f()
|
||||
// NO-THUNKS-Test3-NEXT: 1 | void no_thunks::Test3::i()
|
||||
|
||||
// NO-THUNKS-Test3: VFTable for 'B' in 'no_thunks::Test3' (2 entries)
|
||||
// NO-THUNKS-Test3-NEXT: 0 | void B::g()
|
||||
// NO-THUNKS-Test3-NEXT: 1 | void B::h()
|
||||
|
||||
// NO-THUNKS-Test3: VFTable indices for 'no_thunks::Test3' (1 entries).
|
||||
// NO-THUNKS-Test3-NEXT: 1 | void no_thunks::Test3::i()
|
||||
|
||||
// Only adds a new method.
|
||||
virtual void i();
|
||||
};
|
||||
|
||||
Test3 t3;
|
||||
|
||||
// Only the right base has a vftable, so it's laid out before the left one!
|
||||
struct Test4 : Empty, A {
|
||||
// NO-THUNKS-Test4: VFTable for 'A' in 'no_thunks::Test4' (1 entries)
|
||||
// NO-THUNKS-Test4-NEXT: 0 | void no_thunks::Test4::f()
|
||||
|
||||
// NO-THUNKS-Test4: VFTable indices for 'no_thunks::Test4' (1 entries).
|
||||
// NO-THUNKS-Test4-NEXT: 0 | void no_thunks::Test4::f()
|
||||
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
Test4 t4;
|
||||
|
||||
// 2-level structure with repeating subobject types, but no thunks needed.
|
||||
struct Test5: Test1, Test2 {
|
||||
// NO-THUNKS-Test5: VFTable for 'A' in 'no_thunks::Test1' in 'no_thunks::Test5' (2 entries)
|
||||
// NO-THUNKS-Test5-NEXT: 0 | void no_thunks::Test1::f()
|
||||
// NO-THUNKS-Test5-NEXT: 1 | void no_thunks::Test5::z()
|
||||
|
||||
// NO-THUNKS-Test5: VFTable for 'B' in 'no_thunks::Test1' in 'no_thunks::Test5' (2 entries)
|
||||
// NO-THUNKS-Test5-NEXT: 0 | void B::g()
|
||||
// NO-THUNKS-Test5-NEXT: 1 | void B::h()
|
||||
|
||||
// NO-THUNKS-Test5: VFTable for 'A' in 'no_thunks::Test2' in 'no_thunks::Test5' (1 entries)
|
||||
// NO-THUNKS-Test5-NEXT: 0 | void A::f()
|
||||
|
||||
// NO-THUNKS-Test5: VFTable for 'B' in 'no_thunks::Test2' in 'no_thunks::Test5' (2 entries)
|
||||
// NO-THUNKS-Test5-NEXT: 0 | void no_thunks::Test2::g()
|
||||
// NO-THUNKS-Test5-NEXT: 1 | void B::h()
|
||||
|
||||
// NO-THUNKS-Test5: VFTable indices for 'no_thunks::Test5' (1 entries).
|
||||
// NO-THUNKS-Test5-NEXT: 1 | void no_thunks::Test5::z()
|
||||
|
||||
virtual void z();
|
||||
};
|
||||
|
||||
Test5 t5;
|
||||
|
||||
struct Test6: Test1 {
|
||||
// NO-THUNKS-Test6: VFTable for 'A' in 'no_thunks::Test1' in 'no_thunks::Test6' (1 entries).
|
||||
// NO-THUNKS-Test6-NEXT: 0 | void no_thunks::Test6::f()
|
||||
|
||||
// NO-THUNKS-Test6: VFTable for 'B' in 'no_thunks::Test1' in 'no_thunks::Test6' (2 entries).
|
||||
// NO-THUNKS-Test6-NEXT: 0 | void B::g()
|
||||
// NO-THUNKS-Test6-NEXT: 1 | void B::h()
|
||||
|
||||
// NO-THUNKS-Test6: VFTable indices for 'no_thunks::Test6' (1 entries).
|
||||
// NO-THUNKS-Test6-NEXT: 0 | void no_thunks::Test6::f()
|
||||
|
||||
// Overrides both no_thunks::Test1::f and A::f.
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
Test6 t6;
|
||||
|
||||
struct Test7: Test2 {
|
||||
// NO-THUNKS-Test7: VFTable for 'A' in 'no_thunks::Test2' in 'no_thunks::Test7' (1 entries).
|
||||
// NO-THUNKS-Test7-NEXT: 0 | void A::f()
|
||||
|
||||
// NO-THUNKS-Test7: VFTable for 'B' in 'no_thunks::Test2' in 'no_thunks::Test7' (2 entries).
|
||||
// NO-THUNKS-Test7-NEXT: 0 | void no_thunks::Test7::g()
|
||||
// NO-THUNKS-Test7-NEXT: 1 | void B::h()
|
||||
|
||||
// NO-THUNKS-Test7: VFTable indices for 'no_thunks::Test7' (1 entries).
|
||||
// NO-THUNKS-Test7-NEXT: via vfptr at offset 4
|
||||
// NO-THUNKS-Test7-NEXT: 0 | void no_thunks::Test7::g()
|
||||
|
||||
// Overrides both no_thunks::Test2::g and B::g.
|
||||
virtual void g();
|
||||
};
|
||||
|
||||
Test7 t7;
|
||||
|
||||
struct Test8: Test3 {
|
||||
// NO-THUNKS-Test8: VFTable for 'A' in 'no_thunks::Test3' in 'no_thunks::Test8' (2 entries).
|
||||
// NO-THUNKS-Test8-NEXT: 0 | void A::f()
|
||||
// NO-THUNKS-Test8-NEXT: 1 | void no_thunks::Test3::i()
|
||||
|
||||
// NO-THUNKS-Test8: VFTable for 'B' in 'no_thunks::Test3' in 'no_thunks::Test8' (2 entries).
|
||||
// NO-THUNKS-Test8-NEXT: 0 | void no_thunks::Test8::g()
|
||||
// NO-THUNKS-Test8-NEXT: 1 | void B::h()
|
||||
|
||||
// NO-THUNKS-Test8: VFTable indices for 'no_thunks::Test8' (1 entries).
|
||||
// NO-THUNKS-Test8-NEXT: via vfptr at offset 4
|
||||
// NO-THUNKS-Test8-NEXT: 0 | void no_thunks::Test8::g()
|
||||
|
||||
// Overrides grandparent's B::g.
|
||||
virtual void g();
|
||||
};
|
||||
|
||||
Test8 t8;
|
||||
|
||||
struct D : A {
|
||||
virtual void g();
|
||||
};
|
||||
|
||||
// Repeating subobject.
|
||||
struct Test9: A, D {
|
||||
// NO-THUNKS-Test9: VFTable for 'A' in 'no_thunks::Test9' (2 entries).
|
||||
// NO-THUNKS-Test9-NEXT: 0 | void A::f()
|
||||
// NO-THUNKS-Test9-NEXT: 1 | void no_thunks::Test9::h()
|
||||
|
||||
// NO-THUNKS-Test9: VFTable for 'A' in 'no_thunks::D' in 'no_thunks::Test9' (2 entries).
|
||||
// NO-THUNKS-Test9-NEXT: 0 | void A::f()
|
||||
// NO-THUNKS-Test9-NEXT: 1 | void no_thunks::D::g()
|
||||
|
||||
// NO-THUNKS-Test9: VFTable indices for 'no_thunks::Test9' (1 entries).
|
||||
// NO-THUNKS-Test9-NEXT: 1 | void no_thunks::Test9::h()
|
||||
|
||||
virtual void h();
|
||||
};
|
||||
|
||||
Test9 t9;
|
||||
}
|
||||
|
||||
namespace pure_virtual {
|
||||
struct D {
|
||||
virtual void g() = 0;
|
||||
virtual void h();
|
||||
};
|
||||
|
||||
|
||||
struct Test1: A, D {
|
||||
// PURE-VIRTUAL-Test1: VFTable for 'A' in 'pure_virtual::Test1' (1 entries)
|
||||
// PURE-VIRTUAL-Test1-NEXT: 0 | void A::f()
|
||||
|
||||
// PURE-VIRTUAL-Test1: VFTable for 'pure_virtual::D' in 'pure_virtual::Test1' (2 entries)
|
||||
// PURE-VIRTUAL-Test1-NEXT: 0 | void pure_virtual::Test1::g()
|
||||
// PURE-VIRTUAL-Test1-NEXT: 1 | void pure_virtual::D::h()
|
||||
|
||||
// PURE-VIRTUAL-Test1: VFTable indices for 'pure_virtual::Test1' (1 entries).
|
||||
// PURE-VIRTUAL-Test1-NEXT: via vfptr at offset 4
|
||||
// PURE-VIRTUAL-Test1-NEXT: 0 | void pure_virtual::Test1::g()
|
||||
|
||||
// Overrides only the right child's method (pure_virtual::D::g), needs this adjustment but
|
||||
// not thunks.
|
||||
virtual void g();
|
||||
};
|
||||
|
||||
Test1 t1;
|
||||
}
|
||||
|
||||
namespace this_adjustment {
|
||||
|
||||
// Overrides methods of two bases at the same time, thus needing thunks.
|
||||
struct Test1 : B, C {
|
||||
// THIS-THUNKS-Test1: VFTable for 'B' in 'this_adjustment::Test1' (2 entries).
|
||||
// THIS-THUNKS-Test1-NEXT: 0 | void this_adjustment::Test1::g()
|
||||
// THIS-THUNKS-Test1-NEXT: 1 | void B::h()
|
||||
|
||||
// THIS-THUNKS-Test1: VFTable for 'C' in 'this_adjustment::Test1' (1 entries).
|
||||
// THIS-THUNKS-Test1-NEXT: 0 | void this_adjustment::Test1::g()
|
||||
// THIS-THUNKS-Test1-NEXT: [this adjustment: -4 non-virtual]
|
||||
|
||||
// THIS-THUNKS-Test1: Thunks for 'void this_adjustment::Test1::g()' (1 entry).
|
||||
// THIS-THUNKS-Test1-NEXT: 0 | this adjustment: -4 non-virtual
|
||||
|
||||
// THIS-THUNKS-Test1: VFTable indices for 'this_adjustment::Test1' (1 entries).
|
||||
// THIS-THUNKS-Test1-NEXT: 0 | void this_adjustment::Test1::g()
|
||||
|
||||
virtual void g();
|
||||
};
|
||||
|
||||
Test1 t1;
|
||||
|
||||
struct Test2 : A, B, C {
|
||||
// THIS-THUNKS-Test2: VFTable for 'A' in 'this_adjustment::Test2' (1 entries).
|
||||
// THIS-THUNKS-Test2-NEXT: 0 | void A::f()
|
||||
|
||||
// THIS-THUNKS-Test2: VFTable for 'B' in 'this_adjustment::Test2' (2 entries).
|
||||
// THIS-THUNKS-Test2-NEXT: 0 | void this_adjustment::Test2::g()
|
||||
// THIS-THUNKS-Test2-NEXT: 1 | void B::h()
|
||||
|
||||
// THIS-THUNKS-Test2: VFTable for 'C' in 'this_adjustment::Test2' (1 entries).
|
||||
// THIS-THUNKS-Test2-NEXT: 0 | void this_adjustment::Test2::g()
|
||||
// THIS-THUNKS-Test2-NEXT: [this adjustment: -4 non-virtual]
|
||||
|
||||
// THIS-THUNKS-Test2: Thunks for 'void this_adjustment::Test2::g()' (1 entry).
|
||||
// THIS-THUNKS-Test2-NEXT: 0 | this adjustment: -4 non-virtual
|
||||
|
||||
// THIS-THUNKS-Test2: VFTable indices for 'this_adjustment::Test2' (1 entries).
|
||||
// THIS-THUNKS-Test2-NEXT: via vfptr at offset 4
|
||||
// THIS-THUNKS-Test2-NEXT: 0 | void this_adjustment::Test2::g()
|
||||
|
||||
virtual void g();
|
||||
};
|
||||
|
||||
Test2 t2;
|
||||
|
||||
// Overrides methods of two bases at the same time, thus needing thunks.
|
||||
struct Test3: no_thunks::Test1, no_thunks::Test2 {
|
||||
// THIS-THUNKS-Test3: VFTable for 'A' in 'no_thunks::Test1' in 'this_adjustment::Test3' (1 entries).
|
||||
// THIS-THUNKS-Test3-NEXT: 0 | void this_adjustment::Test3::f()
|
||||
|
||||
// THIS-THUNKS-Test3: VFTable for 'B' in 'no_thunks::Test1' in 'this_adjustment::Test3' (2 entries).
|
||||
// THIS-THUNKS-Test3-NEXT: 0 | void this_adjustment::Test3::g()
|
||||
// THIS-THUNKS-Test3-NEXT: 1 | void B::h()
|
||||
|
||||
// THIS-THUNKS-Test3: VFTable for 'A' in 'no_thunks::Test2' in 'this_adjustment::Test3' (1 entries).
|
||||
// THIS-THUNKS-Test3-NEXT: 0 | void this_adjustment::Test3::f()
|
||||
// THIS-THUNKS-Test3-NEXT: [this adjustment: -8 non-virtual]
|
||||
|
||||
// THIS-THUNKS-Test3: Thunks for 'void this_adjustment::Test3::f()' (1 entry).
|
||||
// THIS-THUNKS-Test3-NEXT: 0 | this adjustment: -8 non-virtual
|
||||
|
||||
// THIS-THUNKS-Test3: VFTable for 'B' in 'no_thunks::Test2' in 'this_adjustment::Test3' (2 entries).
|
||||
// THIS-THUNKS-Test3-NEXT: 0 | void this_adjustment::Test3::g()
|
||||
// THIS-THUNKS-Test3-NEXT: [this adjustment: -8 non-virtual]
|
||||
// THIS-THUNKS-Test3-NEXT: 1 | void B::h()
|
||||
|
||||
// THIS-THUNKS-Test3: Thunks for 'void this_adjustment::Test3::g()' (1 entry).
|
||||
// THIS-THUNKS-Test3-NEXT: 0 | this adjustment: -8 non-virtual
|
||||
|
||||
// THIS-THUNKS-Test3: VFTable indices for 'this_adjustment::Test3' (2 entries).
|
||||
// THIS-THUNKS-Test3-NEXT: via vfptr at offset 0
|
||||
// THIS-THUNKS-Test3-NEXT: 0 | void this_adjustment::Test3::f()
|
||||
// THIS-THUNKS-Test3-NEXT: via vfptr at offset 4
|
||||
// THIS-THUNKS-Test3-NEXT: 0 | void this_adjustment::Test3::g()
|
||||
|
||||
virtual void f();
|
||||
virtual void g();
|
||||
};
|
||||
|
||||
Test3 t3;
|
||||
}
|
||||
|
||||
namespace return_adjustment {
|
||||
|
||||
struct Ret1 {
|
||||
virtual C* foo();
|
||||
virtual void z();
|
||||
};
|
||||
|
||||
struct Test1 : Ret1 {
|
||||
// RET-THUNKS-Test1: VFTable for 'return_adjustment::Ret1' in 'return_adjustment::Test1' (3 entries).
|
||||
// RET-THUNKS-Test1-NEXT: 0 | this_adjustment::Test1 *return_adjustment::Test1::foo()
|
||||
// RET-THUNKS-Test1-NEXT: [return adjustment: 4 non-virtual]
|
||||
// RET-THUNKS-Test1-NEXT: 1 | void return_adjustment::Ret1::z()
|
||||
// RET-THUNKS-Test1-NEXT: 2 | this_adjustment::Test1 *return_adjustment::Test1::foo()
|
||||
|
||||
// RET-THUNKS-Test1: VFTable indices for 'return_adjustment::Test1' (1 entries).
|
||||
// RET-THUNKS-Test1-NEXT: 2 | this_adjustment::Test1 *return_adjustment::Test1::foo()
|
||||
|
||||
virtual this_adjustment::Test1* foo();
|
||||
};
|
||||
|
||||
Test1 t1;
|
||||
|
||||
struct Ret2 : B, this_adjustment::Test1 { };
|
||||
|
||||
struct Test2 : Test1 {
|
||||
// RET-THUNKS-Test2: VFTable for 'return_adjustment::Ret1' in 'return_adjustment::Test1' in 'return_adjustment::Test2' (4 entries).
|
||||
// RET-THUNKS-Test2-NEXT: 0 | return_adjustment::Ret2 *return_adjustment::Test2::foo()
|
||||
// RET-THUNKS-Test2-NEXT: [return adjustment: 8 non-virtual]
|
||||
// RET-THUNKS-Test2-NEXT: 1 | void return_adjustment::Ret1::z()
|
||||
// RET-THUNKS-Test2-NEXT: 2 | return_adjustment::Ret2 *return_adjustment::Test2::foo()
|
||||
// RET-THUNKS-Test2-NEXT: [return adjustment: 4 non-virtual]
|
||||
// RET-THUNKS-Test2-NEXT: 3 | return_adjustment::Ret2 *return_adjustment::Test2::foo()
|
||||
|
||||
// RET-THUNKS-Test2: VFTable indices for 'return_adjustment::Test2' (1 entries).
|
||||
// RET-THUNKS-Test2-NEXT: 3 | return_adjustment::Ret2 *return_adjustment::Test2::foo()
|
||||
|
||||
virtual Ret2* foo();
|
||||
};
|
||||
|
||||
Test2 t2;
|
||||
|
||||
struct Test3: B, Ret1 {
|
||||
// RET-THUNKS-Test3: VFTable for 'B' in 'return_adjustment::Test3' (2 entries).
|
||||
// RET-THUNKS-Test3-NEXT: 0 | void B::g()
|
||||
// RET-THUNKS-Test3-NEXT: 1 | void B::h()
|
||||
|
||||
// RET-THUNKS-Test3: VFTable for 'return_adjustment::Ret1' in 'return_adjustment::Test3' (3 entries).
|
||||
// RET-THUNKS-Test3-NEXT: 0 | this_adjustment::Test1 *return_adjustment::Test3::foo()
|
||||
// RET-THUNKS-Test3-NEXT: [return adjustment: 4 non-virtual]
|
||||
// RET-THUNKS-Test3-NEXT: 1 | void return_adjustment::Ret1::z()
|
||||
// RET-THUNKS-Test3-NEXT: 2 | this_adjustment::Test1 *return_adjustment::Test3::foo()
|
||||
|
||||
// RET-THUNKS-Test3: VFTable indices for 'return_adjustment::Test3' (1 entries).
|
||||
// RET-THUNKS-Test3-NEXT: via vfptr at offset 4
|
||||
// RET-THUNKS-Test3-NEXT: 2 | this_adjustment::Test1 *return_adjustment::Test3::foo()
|
||||
|
||||
virtual this_adjustment::Test1* foo();
|
||||
};
|
||||
|
||||
Test3 t3;
|
||||
|
||||
struct Test4 : Test3 {
|
||||
// RET-THUNKS-Test4: VFTable for 'B' in 'return_adjustment::Test3' in 'return_adjustment::Test4' (2 entries).
|
||||
// RET-THUNKS-Test4-NEXT: 0 | void B::g()
|
||||
// RET-THUNKS-Test4-NEXT: 1 | void B::h()
|
||||
|
||||
// RET-THUNKS-Test4: VFTable for 'return_adjustment::Ret1' in 'return_adjustment::Test3' in 'return_adjustment::Test4' (4 entries).
|
||||
// RET-THUNKS-Test4-NEXT: 0 | return_adjustment::Ret2 *return_adjustment::Test4::foo()
|
||||
// RET-THUNKS-Test4-NEXT: [return adjustment: 8 non-virtual]
|
||||
// RET-THUNKS-Test4-NEXT: 1 | void return_adjustment::Ret1::z()
|
||||
// RET-THUNKS-Test4-NEXT: 2 | return_adjustment::Ret2 *return_adjustment::Test4::foo()
|
||||
// RET-THUNKS-Test4-NEXT: [return adjustment: 4 non-virtual]
|
||||
// RET-THUNKS-Test4-NEXT: 3 | return_adjustment::Ret2 *return_adjustment::Test4::foo()
|
||||
|
||||
// RET-THUNKS-Test4: VFTable indices for 'return_adjustment::Test4' (1 entries).
|
||||
// RET-THUNKS-Test4-NEXT: -- accessible via vfptr at offset 4 --
|
||||
// RET-THUNKS-Test4-NEXT: 3 | return_adjustment::Ret2 *return_adjustment::Test4::foo()
|
||||
|
||||
virtual Ret2* foo();
|
||||
};
|
||||
|
||||
Test4 t4;
|
||||
|
||||
struct Test5 : Ret1, Test1 {
|
||||
// RET-THUNKS-Test5: VFTable for 'return_adjustment::Ret1' in 'return_adjustment::Test5' (3 entries).
|
||||
// RET-THUNKS-Test5-NEXT: 0 | return_adjustment::Ret2 *return_adjustment::Test5::foo()
|
||||
// RET-THUNKS-Test5-NEXT: [return adjustment: 8 non-virtual]
|
||||
// RET-THUNKS-Test5-NEXT: 1 | void return_adjustment::Ret1::z()
|
||||
// RET-THUNKS-Test5-NEXT: 2 | return_adjustment::Ret2 *return_adjustment::Test5::foo()
|
||||
|
||||
// RET-THUNKS-Test5: VFTable for 'return_adjustment::Ret1' in 'return_adjustment::Test1' in 'return_adjustment::Test5' (4 entries).
|
||||
// RET-THUNKS-Test5-NEXT: 0 | return_adjustment::Ret2 *return_adjustment::Test5::foo()
|
||||
// RET-THUNKS-Test5-NEXT: [return adjustment: 8 non-virtual]
|
||||
// RET-THUNKS-Test5-NEXT: [this adjustment: -4 non-virtual]
|
||||
// RET-THUNKS-Test5-NEXT: 1 | void return_adjustment::Ret1::z()
|
||||
// RET-THUNKS-Test5-NEXT: 2 | return_adjustment::Ret2 *return_adjustment::Test5::foo()
|
||||
// RET-THUNKS-Test5-NEXT: [return adjustment: 4 non-virtual]
|
||||
// RET-THUNKS-Test5-NEXT: [this adjustment: -4 non-virtual]
|
||||
// RET-THUNKS-Test5-NEXT: 3 | return_adjustment::Ret2 *return_adjustment::Test5::foo()
|
||||
// RET-THUNKS-Test5-NEXT: [this adjustment: -4 non-virtual]
|
||||
|
||||
// RET-THUNKS-Test5: VFTable indices for 'return_adjustment::Test5' (1 entries).
|
||||
// RET-THUNKS-Test5-NEXT: 2 | return_adjustment::Ret2 *return_adjustment::Test5::foo()
|
||||
|
||||
virtual Ret2* foo();
|
||||
};
|
||||
|
||||
Test5 t5;
|
||||
}
|
|
@ -8,12 +8,25 @@
|
|||
// RUN: FileCheck --check-prefix=CHECK-E %s < %t
|
||||
// RUN: FileCheck --check-prefix=CHECK-F %s < %t
|
||||
// RUN: FileCheck --check-prefix=CHECK-G %s < %t
|
||||
// RUN: FileCheck --check-prefix=CHECK-I %s < %t
|
||||
|
||||
// FIXME: Currently, we only test VFTableContext in the AST, but still use
|
||||
// VTableContext for CodeGen. We should remove the "Vtable" checks below when we
|
||||
// completely switch from VTableContext to VFTableContext.
|
||||
// Currently, the order of Vtable vs VFTable output depends on whether the
|
||||
// v*table info was required by a constructor or a method definition.
|
||||
|
||||
struct A {
|
||||
// CHECK-A: Vtable for 'A' (3 entries)
|
||||
// CHECK-A-NEXT: 0 | void A::f()
|
||||
// CHECK-A-NEXT: 1 | void A::g()
|
||||
// CHECK-A-NEXT: 2 | void A::h()
|
||||
|
||||
// CHECK-A: VFTable for 'A' (3 entries)
|
||||
// CHECK-A-NEXT: 0 | void A::f()
|
||||
// CHECK-A-NEXT: 1 | void A::g()
|
||||
// CHECK-A-NEXT: 2 | void A::h()
|
||||
|
||||
virtual void f();
|
||||
virtual void g();
|
||||
virtual void h();
|
||||
|
@ -29,6 +42,14 @@ struct B : A {
|
|||
// CHECK-B-NEXT: 2 | void A::h()
|
||||
// CHECK-B-NEXT: 3 | void B::i()
|
||||
// CHECK-B-NEXT: 4 | void B::j()
|
||||
|
||||
// CHECK-B: VFTable for 'A' in 'B' (5 entries)
|
||||
// CHECK-B-NEXT: 0 | void B::f()
|
||||
// CHECK-B-NEXT: 1 | void A::g()
|
||||
// CHECK-B-NEXT: 2 | void A::h()
|
||||
// CHECK-B-NEXT: 3 | void B::i()
|
||||
// CHECK-B-NEXT: 4 | void B::j()
|
||||
|
||||
virtual void f(); // overrides A::f()
|
||||
virtual void i();
|
||||
virtual void j();
|
||||
|
@ -37,14 +58,21 @@ B b;
|
|||
// EMITS-VTABLE-DAG: @"\01??_7B@@6B@" = linkonce_odr unnamed_addr constant [5 x i8*]
|
||||
|
||||
struct C {
|
||||
// CHECK-C: VFTable for 'C' (2 entries)
|
||||
// CHECK-C-NEXT: 0 | C::~C() [scalar deleting]
|
||||
// CHECK-C-NEXT: 1 | void C::f()
|
||||
// CHECK-C: VFTable indices for 'C' (2 entries).
|
||||
// CHECK-C-NEXT: 0 | C::~C() [scalar deleting]
|
||||
// CHECK-C-NEXT: 1 | void C::f()
|
||||
|
||||
// CHECK-C: Vtable for 'C' (2 entries)
|
||||
// CHECK-C-NEXT: 0 | C::~C() [scalar deleting]
|
||||
// CHECK-C-NEXT: 1 | void C::f()
|
||||
// CHECK-C: VTable indices for 'C' (2 entries).
|
||||
// CHECK-C-NEXT: 0 | C::~C() [scalar deleting]
|
||||
// CHECK-C-NEXT: 1 | void C::f()
|
||||
virtual ~C();
|
||||
|
||||
virtual ~C();
|
||||
virtual void f();
|
||||
};
|
||||
void C::f() {}
|
||||
|
@ -54,14 +82,28 @@ struct D {
|
|||
// CHECK-D: Vtable for 'D' (2 entries)
|
||||
// CHECK-D-NEXT: 0 | void D::f()
|
||||
// CHECK-D-NEXT: 1 | D::~D() [scalar deleting]
|
||||
virtual void f();
|
||||
|
||||
// CHECK-D: VFTable for 'D' (2 entries)
|
||||
// CHECK-D-NEXT: 0 | void D::f()
|
||||
// CHECK-D-NEXT: 1 | D::~D() [scalar deleting]
|
||||
|
||||
virtual void f();
|
||||
virtual ~D();
|
||||
};
|
||||
D d;
|
||||
// EMITS-VTABLE-DAG: @"\01??_7D@@6B@" = linkonce_odr unnamed_addr constant [2 x i8*]
|
||||
|
||||
struct E : A {
|
||||
// CHECK-E: VFTable for 'A' in 'E' (5 entries)
|
||||
// CHECK-E-NEXT: 0 | void A::f()
|
||||
// CHECK-E-NEXT: 1 | void A::g()
|
||||
// CHECK-E-NEXT: 2 | void A::h()
|
||||
// CHECK-E-NEXT: 3 | E::~E() [scalar deleting]
|
||||
// CHECK-E-NEXT: 4 | void E::i()
|
||||
// CHECK-E: VFTable indices for 'E' (2 entries).
|
||||
// CHECK-E-NEXT: 3 | E::~E() [scalar deleting]
|
||||
// CHECK-E-NEXT: 4 | void E::i()
|
||||
|
||||
// CHECK-E: Vtable for 'E' (5 entries)
|
||||
// CHECK-E-NEXT: 0 | void A::f()
|
||||
// CHECK-E-NEXT: 1 | void A::g()
|
||||
|
@ -90,6 +132,17 @@ struct F : A {
|
|||
// CHECK-F: VTable indices for 'F' (2 entries).
|
||||
// CHECK-F-NEXT: 3 | void F::i()
|
||||
// CHECK-F-NEXT: 4 | F::~F() [scalar deleting]
|
||||
|
||||
// CHECK-F: VFTable for 'A' in 'F' (5 entries)
|
||||
// CHECK-F-NEXT: 0 | void A::f()
|
||||
// CHECK-F-NEXT: 1 | void A::g()
|
||||
// CHECK-F-NEXT: 2 | void A::h()
|
||||
// CHECK-F-NEXT: 3 | void F::i()
|
||||
// CHECK-F-NEXT: 4 | F::~F() [scalar deleting]
|
||||
// CHECK-F: VFTable indices for 'F' (2 entries).
|
||||
// CHECK-F-NEXT: 3 | void F::i()
|
||||
// CHECK-F-NEXT: 4 | F::~F() [scalar deleting]
|
||||
|
||||
virtual void i();
|
||||
virtual ~F();
|
||||
};
|
||||
|
@ -97,6 +150,18 @@ F f;
|
|||
// EMITS-VTABLE-DAG: @"\01??_7F@@6B@" = linkonce_odr unnamed_addr constant [5 x i8*]
|
||||
|
||||
struct G : E {
|
||||
// CHECK-G: VFTable for 'A' in 'E' in 'G' (6 entries)
|
||||
// CHECK-G-NEXT: 0 | void G::f()
|
||||
// CHECK-G-NEXT: 1 | void A::g()
|
||||
// CHECK-G-NEXT: 2 | void A::h()
|
||||
// CHECK-G-NEXT: 3 | G::~G() [scalar deleting]
|
||||
// CHECK-G-NEXT: 4 | void E::i()
|
||||
// CHECK-G-NEXT: 5 | void G::j()
|
||||
// CHECK-G: VFTable indices for 'G' (3 entries).
|
||||
// CHECK-G-NEXT: 0 | void G::f()
|
||||
// CHECK-G-NEXT: 3 | G::~G() [scalar deleting]
|
||||
// CHECK-G-NEXT: 5 | void G::j()
|
||||
|
||||
// CHECK-G: Vtable for 'G' (6 entries)
|
||||
// CHECK-G-NEXT: 0 | void G::f()
|
||||
// CHECK-G-NEXT: 1 | void A::g()
|
||||
|
@ -108,6 +173,7 @@ struct G : E {
|
|||
// CHECK-G-NEXT: 0 | void G::f()
|
||||
// CHECK-G-NEXT: 3 | G::~G() [scalar deleting]
|
||||
// CHECK-G-NEXT: 5 | void G::j()
|
||||
|
||||
virtual void f(); // overrides A::f()
|
||||
virtual ~G();
|
||||
virtual void j();
|
||||
|
@ -121,3 +187,15 @@ struct H {
|
|||
};
|
||||
void H::f() {}
|
||||
// NO-VTABLE-NOT: @"\01??_7H@@6B@"
|
||||
|
||||
struct Empty { };
|
||||
|
||||
struct I : Empty {
|
||||
// CHECK-I: VFTable for 'I' (2 entries)
|
||||
// CHECK-I-NEXT: 0 | void I::f()
|
||||
// CHECK-I-NEXT: 1 | void I::g()
|
||||
virtual void f();
|
||||
virtual void g();
|
||||
};
|
||||
|
||||
I i;
|
||||
|
|
|
@ -0,0 +1,391 @@
|
|||
// RUN: %clang_cc1 -fno-rtti -emit-llvm -fdump-vtable-layouts %s -o - -cxx-abi microsoft -triple=i386-pc-win32 >%t 2>&1
|
||||
|
||||
// RUN: FileCheck --check-prefix=VTABLE-C %s < %t
|
||||
// RUN: FileCheck --check-prefix=VTABLE-D %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST1 %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST2 %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST3 %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST4 %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST5 %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST6 %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST7 %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST8 %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST9-Y %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST9-Z %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST9-W %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST9-T %s < %t
|
||||
// RUN: FileCheck --check-prefix=TEST10 %s < %t
|
||||
// RUN: FileCheck --check-prefix=RET-W %s < %t
|
||||
// RUN: FileCheck --check-prefix=RET-T %s < %t
|
||||
|
||||
struct Empty { };
|
||||
|
||||
struct A {
|
||||
virtual void f();
|
||||
virtual void z(); // Useful to check there are no thunks for f() when appropriate.
|
||||
};
|
||||
|
||||
struct B {
|
||||
virtual void g();
|
||||
};
|
||||
|
||||
struct C: virtual A {
|
||||
// VTABLE-C: VFTable for 'A' in 'C' (2 entries)
|
||||
// VTABLE-C-NEXT: 0 | void C::f()
|
||||
// VTABLE-C-NEXT: 1 | void A::z()
|
||||
|
||||
// VTABLE-C: VFTable indices for 'C' (1 entries)
|
||||
// VTABLE-C-NEXT: vbtable index 1, vfptr at offset 0
|
||||
// VTABLE-C-NEXT: 0 | void C::f()
|
||||
|
||||
~C(); // Currently required to have correct record layout, see PR16406
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
C c;
|
||||
|
||||
struct D: virtual A {
|
||||
// VTABLE-D: VFTable for 'D' (1 entries).
|
||||
// VTABLE-D-NEXT: 0 | void D::h()
|
||||
|
||||
// VTABLE-D: VFTable for 'A' in 'D' (2 entries).
|
||||
// VTABLE-D-NEXT: 0 | void D::f()
|
||||
// VTABLE-D-NEXT: 1 | void A::z()
|
||||
|
||||
// VTABLE-D: VFTable indices for 'D' (2 entries).
|
||||
// VTABLE-D-NEXT: via vfptr at offset 0
|
||||
// VTABLE-D-NEXT: 0 | void D::h()
|
||||
// VTABLE-D-NEXT: via vbtable index 1, vfptr at offset 0
|
||||
// VTABLE-D-NEXT: 0 | void D::f()
|
||||
|
||||
virtual void f();
|
||||
virtual void h();
|
||||
};
|
||||
|
||||
void D::h() {}
|
||||
D d;
|
||||
|
||||
namespace Test1 {
|
||||
|
||||
struct X { int x; };
|
||||
|
||||
// X and A get reordered in the layout since X doesn't have a vfptr while A has.
|
||||
struct Y : X, A { };
|
||||
|
||||
struct Z : virtual Y {
|
||||
// TEST1: VFTable for 'A' in 'Test1::Y' in 'Test1::Z' (2 entries).
|
||||
// TEST1-NEXT: 0 | void A::f()
|
||||
// TEST1-NEXT: 1 | void A::z()
|
||||
|
||||
// TEST1-NOT: VFTable indices for 'Test1::Z'
|
||||
};
|
||||
|
||||
Z z;
|
||||
}
|
||||
|
||||
namespace Test2 {
|
||||
|
||||
struct X: virtual A, virtual B {
|
||||
// TEST2: VFTable for 'Test2::X' (1 entries).
|
||||
// TEST2-NEXT: 0 | void Test2::X::h()
|
||||
|
||||
// TEST2: VFTable for 'A' in 'Test2::X' (2 entries).
|
||||
// TEST2-NEXT: 0 | void A::f()
|
||||
// TEST2-NEXT: 1 | void A::z()
|
||||
|
||||
// TEST2: VFTable for 'B' in 'Test2::X' (1 entries).
|
||||
// TEST2-NEXT: 0 | void B::g()
|
||||
|
||||
// TEST2: VFTable indices for 'Test2::X' (1 entries).
|
||||
// TEST2-NEXT: 0 | void Test2::X::h()
|
||||
|
||||
virtual void h();
|
||||
};
|
||||
|
||||
X x;
|
||||
}
|
||||
|
||||
namespace Test3 {
|
||||
|
||||
struct X : virtual A { };
|
||||
|
||||
struct Y: virtual X {
|
||||
// TEST3: VFTable for 'A' in 'Test3::X' in 'Test3::Y' (2 entries).
|
||||
// TEST3-NEXT: 0 | void A::f()
|
||||
// TEST3-NEXT: 1 | void A::z()
|
||||
|
||||
// TEST3-NOT: VFTable indices for 'Test3::Y'
|
||||
};
|
||||
|
||||
Y y;
|
||||
}
|
||||
|
||||
namespace Test4 {
|
||||
|
||||
struct X: virtual C {
|
||||
// This one's interesting. C::f expects (A*) to be passed as 'this' and does
|
||||
// ECX-=4 to cast to (C*). In X, C and A vbases are reordered, so the thunk
|
||||
// should pass a pointer to the end of X in order
|
||||
// for ECX-=4 to point at the C part.
|
||||
|
||||
// TEST4: VFTable for 'A' in 'C' in 'Test4::X' (2 entries).
|
||||
// TEST4-NEXT: 0 | void C::f()
|
||||
// TEST4-NEXT: [this adjustment: 12 non-virtual]
|
||||
// TEST4-NEXT: 1 | void A::z()
|
||||
|
||||
// TEST4-NOT: VFTable indices for 'Test4::X'
|
||||
};
|
||||
|
||||
X x;
|
||||
}
|
||||
|
||||
namespace Test5 {
|
||||
|
||||
// New methods are added to the base's vftable.
|
||||
struct X : A {
|
||||
virtual void g();
|
||||
};
|
||||
|
||||
struct Y : virtual X {
|
||||
// TEST5: VFTable for 'Test5::Y' (1 entries).
|
||||
// TEST5-NEXT: 0 | void Test5::Y::h()
|
||||
|
||||
// TEST5: VFTable for 'A' in 'Test5::X' in 'Test5::Y' (3 entries).
|
||||
// TEST5-NEXT: 0 | void A::f()
|
||||
// TEST5-NEXT: 1 | void A::z()
|
||||
// TEST5-NEXT: 2 | void Test5::X::g()
|
||||
|
||||
// TEST5: VFTable indices for 'Test5::Y' (1 entries).
|
||||
// TEST5-NEXT: 0 | void Test5::Y::h()
|
||||
|
||||
virtual void h();
|
||||
};
|
||||
|
||||
Y y;
|
||||
}
|
||||
|
||||
namespace Test6 {
|
||||
|
||||
struct X : A, virtual Empty {
|
||||
// TEST6: VFTable for 'A' in 'Test6::X' (2 entries).
|
||||
// TEST6-NEXT: 0 | void A::f()
|
||||
// TEST6-NEXT: 1 | void A::z()
|
||||
|
||||
// TEST6-NOT: VFTable indices for 'Test6::X'
|
||||
};
|
||||
|
||||
X x;
|
||||
}
|
||||
|
||||
namespace Test7 {
|
||||
|
||||
struct X : C { };
|
||||
|
||||
struct Y : virtual X {
|
||||
// TEST7: VFTable for 'A' in 'C' in 'Test7::X' in 'Test7::Y' (2 entries).
|
||||
// TEST7-NEXT: 0 | void C::f()
|
||||
// TEST7-NEXT: [this adjustment: 12 non-virtual]
|
||||
// TEST7-NEXT: 1 | void A::z()
|
||||
|
||||
// TEST7: Thunks for 'void C::f()' (1 entry).
|
||||
// TEST7-NEXT: 0 | this adjustment: 12 non-virtual
|
||||
|
||||
// TEST7-NOT: VFTable indices for 'Test7::Y'
|
||||
};
|
||||
|
||||
Y y;
|
||||
}
|
||||
|
||||
namespace Test8 {
|
||||
|
||||
// This is a typical diamond inheritance with a shared 'A' vbase.
|
||||
struct X : D, C {
|
||||
// TEST8: VFTable for 'D' in 'Test8::X' (1 entries).
|
||||
// TEST8-NEXT: 0 | void D::h()
|
||||
|
||||
// TEST8: VFTable for 'A' in 'D' in 'Test8::X' (2 entries).
|
||||
// TEST8-NEXT: 0 | void Test8::X::f()
|
||||
// TEST8-NEXT: 1 | void A::z()
|
||||
|
||||
// TEST8: VFTable indices for 'Test8::X' (1 entries).
|
||||
// TEST8-NEXT: via vbtable index 1, vfptr at offset 0
|
||||
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
X x;
|
||||
}
|
||||
|
||||
namespace Test9 {
|
||||
|
||||
struct X : A { };
|
||||
|
||||
struct Y : virtual X {
|
||||
// TEST9-Y: VFTable for 'Test9::Y' (1 entries).
|
||||
// TEST9-Y-NEXT: 0 | void Test9::Y::h()
|
||||
|
||||
// TEST9-Y: VFTable for 'A' in 'Test9::X' in 'Test9::Y' (2 entries).
|
||||
// TEST9-Y-NEXT: 0 | void A::f()
|
||||
// TEST9-Y-NEXT: 1 | void A::z()
|
||||
|
||||
// TEST9-Y: VFTable indices for 'Test9::Y' (1 entries).
|
||||
// TEST9-Y-NEXT: 0 | void Test9::Y::h()
|
||||
|
||||
virtual void h();
|
||||
};
|
||||
|
||||
Y y;
|
||||
|
||||
struct Z : Y, virtual B {
|
||||
// TEST9-Z: VFTable for 'Test9::Y' in 'Test9::Z' (1 entries).
|
||||
// TEST9-Z-NEXT: 0 | void Test9::Y::h()
|
||||
|
||||
// TEST9-Z: VFTable for 'A' in 'Test9::X' in 'Test9::Y' in 'Test9::Z' (2 entries).
|
||||
// TEST9-Z-NEXT: 0 | void A::f()
|
||||
// TEST9-Z-NEXT: 1 | void A::z()
|
||||
|
||||
// TEST9-Z: VFTable for 'B' in 'Test9::Z' (1 entries).
|
||||
// TEST9-Z-NEXT: 0 | void B::g()
|
||||
|
||||
// TEST9-Z-NOT: VFTable indices for 'Test9::Z'
|
||||
};
|
||||
|
||||
Z z;
|
||||
|
||||
struct W : Z, D, virtual A, virtual B {
|
||||
// TEST9-W: VFTable for 'Test9::Y' in 'Test9::Z' in 'Test9::W' (1 entries).
|
||||
// TEST9-W-NEXT: 0 | void Test9::Y::h()
|
||||
|
||||
// TEST9-W: VFTable for 'A' in 'Test9::X' in 'Test9::Y' in 'Test9::Z' in 'Test9::W' (2 entries).
|
||||
// TEST9-W-NEXT: 0 | void A::f()
|
||||
// TEST9-W-NEXT: 1 | void A::z()
|
||||
|
||||
// TEST9-W: VFTable for 'B' in 'Test9::Z' in 'Test9::W' (1 entries).
|
||||
// TEST9-W-NEXT: 0 | void B::g()
|
||||
|
||||
// TEST9-W: VFTable for 'D' in 'Test9::W' (1 entries).
|
||||
// TEST9-W-NEXT: 0 | void D::h()
|
||||
|
||||
// TEST9-W: VFTable for 'A' in 'D' in 'Test9::W' (2 entries).
|
||||
// TEST9-W-NEXT: 0 | void D::f()
|
||||
// TEST9-W-NEXT: [this adjustment: -8 non-virtual]
|
||||
// TEST9-W-NEXT: 1 | void A::z()
|
||||
|
||||
// TEST9-W: Thunks for 'void D::f()' (1 entry).
|
||||
// TEST9-W-NEXT: 0 | this adjustment: -8 non-virtual
|
||||
|
||||
// TEST9-W-NOT: VFTable indices for 'Test9::W'
|
||||
};
|
||||
|
||||
W w;
|
||||
|
||||
struct T : Z, D, virtual A, virtual B {
|
||||
~T(); // Currently required to have correct record layout, see PR16406
|
||||
|
||||
// TEST9-T: VFTable for 'Test9::Y' in 'Test9::Z' in 'Test9::T' (1 entries).
|
||||
// TEST9-T-NEXT: 0 | void Test9::T::h()
|
||||
|
||||
// TEST9-T: VFTable for 'A' in 'Test9::X' in 'Test9::Y' in 'Test9::Z' in 'Test9::T' (2 entries).
|
||||
// TEST9-T-NEXT: 0 | void Test9::T::f()
|
||||
// TEST9-T-NEXT: 1 | void Test9::T::z()
|
||||
|
||||
// TEST9-T: VFTable for 'B' in 'Test9::Z' in 'Test9::T' (1 entries).
|
||||
// TEST9-T-NEXT: 0 | void Test9::T::g()
|
||||
|
||||
// TEST9-T: VFTable for 'D' in 'Test9::T' (1 entries).
|
||||
// TEST9-T-NEXT: 0 | void Test9::T::h()
|
||||
// TEST9-T-NEXT: [this adjustment: -8 non-virtual]
|
||||
|
||||
// TEST9-T: Thunks for 'void Test9::T::h()' (1 entry).
|
||||
// TEST9-T-NEXT: 0 | this adjustment: -8 non-virtual
|
||||
|
||||
// TEST9-T: VFTable for 'A' in 'D' in 'Test9::T' (2 entries).
|
||||
// TEST9-T-NEXT: 0 | void Test9::T::f()
|
||||
// TEST9-T-NEXT: [this adjustment: -16 non-virtual]
|
||||
// TEST9-T-NEXT: 1 | void Test9::T::z()
|
||||
// TEST9-T-NEXT: [this adjustment: -16 non-virtual]
|
||||
|
||||
// TEST9-T: Thunks for 'void Test9::T::f()' (1 entry).
|
||||
// TEST9-T-NEXT: 0 | this adjustment: -16 non-virtual
|
||||
|
||||
// TEST9-T: Thunks for 'void Test9::T::z()' (1 entry).
|
||||
// TEST9-T-NEXT: 0 | this adjustment: -16 non-virtual
|
||||
|
||||
// TEST9-T: VFTable indices for 'Test9::T' (4 entries).
|
||||
// TEST9-T-NEXT: via vfptr at offset 0
|
||||
// TEST9-T-NEXT: 0 | void Test9::T::h()
|
||||
// TEST9-T-NEXT: via vbtable index 1, vfptr at offset 0
|
||||
// TEST9-T-NEXT: 0 | void Test9::T::f()
|
||||
// TEST9-T-NEXT: 1 | void Test9::T::z()
|
||||
// TEST9-T-NEXT: via vbtable index 2, vfptr at offset 0
|
||||
// TEST9-T-NEXT: 0 | void Test9::T::g()
|
||||
|
||||
virtual void f();
|
||||
virtual void g();
|
||||
virtual void h();
|
||||
virtual void z();
|
||||
};
|
||||
|
||||
T t;
|
||||
}
|
||||
|
||||
namespace Test10 {
|
||||
struct X : virtual C, virtual A {
|
||||
// TEST10: VFTable for 'A' in 'C' in 'Test10::X' (2 entries).
|
||||
// TEST10-NEXT: 0 | void Test10::X::f()
|
||||
// TEST10-NEXT: 1 | void A::z()
|
||||
|
||||
// TEST10: VFTable indices for 'Test10::X' (1 entries).
|
||||
// TEST10-NEXT: via vbtable index 1, vfptr at offset 0
|
||||
// TEST10-NEXT: 0 | void Test10::X::f()
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
void X::f() {}
|
||||
X x;
|
||||
}
|
||||
|
||||
namespace return_adjustment {
|
||||
|
||||
struct X : virtual A {
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
struct Y : virtual A, virtual X {
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
struct Z {
|
||||
virtual A* foo();
|
||||
};
|
||||
|
||||
struct W : Z {
|
||||
// RET-W: VFTable for 'return_adjustment::Z' in 'return_adjustment::W' (2 entries).
|
||||
// RET-W-NEXT: 0 | return_adjustment::X *return_adjustment::W::foo()
|
||||
// RET-W-NEXT: [return adjustment: vbase #1, 0 non-virtual]
|
||||
// RET-W-NEXT: 1 | return_adjustment::X *return_adjustment::W::foo()
|
||||
|
||||
// RET-W: VFTable indices for 'return_adjustment::W' (1 entries).
|
||||
// RET-W-NEXT: 1 | return_adjustment::X *return_adjustment::W::foo()
|
||||
|
||||
virtual X* foo();
|
||||
};
|
||||
|
||||
W y;
|
||||
|
||||
struct T : W {
|
||||
// RET-T: VFTable for 'return_adjustment::Z' in 'return_adjustment::W' in 'return_adjustment::T' (3 entries).
|
||||
// RET-T-NEXT: 0 | return_adjustment::Y *return_adjustment::T::foo()
|
||||
// RET-T-NEXT: [return adjustment: vbase #1, 0 non-virtual]
|
||||
// RET-T-NEXT: 1 | return_adjustment::Y *return_adjustment::T::foo()
|
||||
// RET-T-NEXT: [return adjustment: vbase #2, 0 non-virtual]
|
||||
// RET-T-NEXT: 2 | return_adjustment::Y *return_adjustment::T::foo()
|
||||
|
||||
// RET-T: VFTable indices for 'return_adjustment::T' (1 entries).
|
||||
// RET-T-NEXT: 2 | return_adjustment::Y *return_adjustment::T::foo()
|
||||
|
||||
virtual Y* foo();
|
||||
};
|
||||
|
||||
T t;
|
||||
}
|
Loading…
Reference in New Issue