Fix __has_unique_object_representations implementation

As rsmith pointed out, the original implementation of this intrinsic
missed a number of important situations.  This patch fixe a bunch of
shortcomings and implementation details to make it work correctly.

Differential Revision: https://reviews.llvm.org/D39347


git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@319446 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Erich Keane 2017-11-30 16:37:02 +00:00
parent 96c9689f47
commit c48afae870
10 changed files with 315 additions and 178 deletions

View File

@ -2149,6 +2149,10 @@ public:
void CollectInheritedProtocols(const Decl *CDecl,
llvm::SmallPtrSet<ObjCProtocolDecl*, 8> &Protocols);
/// \brief Return true if the specified type has unique object representations
/// according to (C++17 [meta.unary.prop]p9)
bool hasUniqueObjectRepresentations(QualType Ty) const;
//===--------------------------------------------------------------------===//
// Type Operators
//===--------------------------------------------------------------------===//

View File

@ -808,10 +808,6 @@ public:
/// Return true if this is a trivially copyable type (C++0x [basic.types]p9)
bool isTriviallyCopyableType(const ASTContext &Context) const;
/// Return true if this has unique object representations according to (C++17
/// [meta.unary.prop]p9)
bool hasUniqueObjectRepresentations(const ASTContext &Context) const;
// Don't promise in the API that anything besides 'const' can be
// easily added.
@ -1164,9 +1160,6 @@ public:
QualType getAtomicUnqualifiedType() const;
private:
bool unionHasUniqueObjectRepresentations(const ASTContext& Context) const;
bool structHasUniqueObjectRepresentations(const ASTContext& Context) const;
// These methods are implemented in a separate translation unit;
// "static"-ize them to avoid creating temporary QualTypes in the
// caller.

View File

@ -1856,7 +1856,9 @@ TypeInfo ASTContext::getTypeInfoImpl(const Type *T) const {
break;
case Type::MemberPointer: {
const MemberPointerType *MPT = cast<MemberPointerType>(T);
std::tie(Width, Align) = ABI->getMemberPointerWidthAndAlign(MPT);
CXXABI::MemberPointerInfo MPI = ABI->getMemberPointerInfo(MPT);
Width = MPI.Width;
Align = MPI.Align;
break;
}
case Type::Complex: {
@ -2138,6 +2140,168 @@ void ASTContext::CollectInheritedProtocols(const Decl *CDecl,
}
}
static bool unionHasUniqueObjectRepresentations(const ASTContext &Context,
const RecordDecl *RD) {
assert(RD->isUnion() && "Must be union type");
CharUnits UnionSize = Context.getTypeSizeInChars(RD->getTypeForDecl());
for (const auto *Field : RD->fields()) {
if (!Context.hasUniqueObjectRepresentations(Field->getType()))
return false;
CharUnits FieldSize = Context.getTypeSizeInChars(Field->getType());
if (FieldSize != UnionSize)
return false;
}
return true;
}
bool isStructEmpty(QualType Ty) {
const RecordDecl *RD = Ty->castAs<RecordType>()->getDecl();
if (!RD->field_empty())
return false;
if (const auto *ClassDecl = dyn_cast<CXXRecordDecl>(RD))
return ClassDecl->isEmpty();
return true;
}
static llvm::Optional<int64_t>
structHasUniqueObjectRepresentations(const ASTContext &Context,
const RecordDecl *RD) {
assert(!RD->isUnion() && "Must be struct/class type");
const auto &Layout = Context.getASTRecordLayout(RD);
int64_t CurOffsetInBits = 0;
if (const auto *ClassDecl = dyn_cast<CXXRecordDecl>(RD)) {
if (ClassDecl->isDynamicClass())
return llvm::None;
SmallVector<std::pair<QualType, int64_t>, 4> Bases;
for (const auto Base : ClassDecl->bases()) {
// Empty types can be inherited from, and non-empty types can potentially
// have tail padding, so just make sure there isn't an error.
if (!isStructEmpty(Base.getType())) {
llvm::Optional<int64_t> Size = structHasUniqueObjectRepresentations(
Context, Base.getType()->getAs<RecordType>()->getDecl());
if (!Size)
return llvm::None;
Bases.emplace_back(Base.getType(), Size.getValue());
}
}
std::sort(
Bases.begin(), Bases.end(), [&](const std::pair<QualType, int64_t> &L,
const std::pair<QualType, int64_t> &R) {
return Layout.getBaseClassOffset(L.first->getAsCXXRecordDecl()) <
Layout.getBaseClassOffset(R.first->getAsCXXRecordDecl());
});
for (const auto Base : Bases) {
int64_t BaseOffset = Context.toBits(
Layout.getBaseClassOffset(Base.first->getAsCXXRecordDecl()));
int64_t BaseSize = Base.second;
if (BaseOffset != CurOffsetInBits)
return llvm::None;
CurOffsetInBits = BaseOffset + BaseSize;
}
}
for (const auto *Field : RD->fields()) {
if (!Field->getType()->isReferenceType() &&
!Context.hasUniqueObjectRepresentations(Field->getType()))
return llvm::None;
int64_t FieldSizeInBits =
Context.toBits(Context.getTypeSizeInChars(Field->getType()));
if (Field->isBitField()) {
int64_t BitfieldSize = Field->getBitWidthValue(Context);
if (BitfieldSize > FieldSizeInBits)
return llvm::None;
FieldSizeInBits = BitfieldSize;
}
int64_t FieldOffsetInBits = Context.getFieldOffset(Field);
if (FieldOffsetInBits != CurOffsetInBits)
return llvm::None;
CurOffsetInBits = FieldSizeInBits + FieldOffsetInBits;
}
return CurOffsetInBits;
}
bool ASTContext::hasUniqueObjectRepresentations(QualType Ty) const {
// C++17 [meta.unary.prop]:
// The predicate condition for a template specialization
// has_unique_object_representations<T> shall be
// satisfied if and only if:
// (9.1) - T is trivially copyable, and
// (9.2) - any two objects of type T with the same value have the same
// object representation, where two objects
// of array or non-union class type are considered to have the same value
// if their respective sequences of
// direct subobjects have the same values, and two objects of union type
// are considered to have the same
// value if they have the same active member and the corresponding members
// have the same value.
// The set of scalar types for which this condition holds is
// implementation-defined. [ Note: If a type has padding
// bits, the condition does not hold; otherwise, the condition holds true
// for unsigned integral types. -- end note ]
assert(!Ty.isNull() && "Null QualType sent to unique object rep check");
// Arrays are unique only if their element type is unique.
if (Ty->isArrayType())
return hasUniqueObjectRepresentations(getBaseElementType(Ty));
// (9.1) - T is trivially copyable...
if (!Ty.isTriviallyCopyableType(*this))
return false;
// All integrals and enums are unique.
if (Ty->isIntegralOrEnumerationType())
return true;
// All other pointers are unique.
if (Ty->isPointerType())
return true;
if (Ty->isMemberPointerType()) {
const MemberPointerType *MPT = Ty->getAs<MemberPointerType>();
return !ABI->getMemberPointerInfo(MPT).HasPadding;
}
if (Ty->isRecordType()) {
const RecordDecl *Record = Ty->getAs<RecordType>()->getDecl();
if (Record->isUnion())
return unionHasUniqueObjectRepresentations(*this, Record);
Optional<int64_t> StructSize =
structHasUniqueObjectRepresentations(*this, Record);
return StructSize &&
StructSize.getValue() == static_cast<int64_t>(getTypeSize(Ty));
}
// FIXME: More cases to handle here (list by rsmith):
// vectors (careful about, eg, vector of 3 foo)
// _Complex int and friends
// _Atomic T
// Obj-C block pointers
// Obj-C object pointers
// and perhaps OpenCL's various builtin types (pipe, sampler_t, event_t,
// clk_event_t, queue_t, reserve_id_t)
// There're also Obj-C class types and the Obj-C selector type, but I think it
// makes sense for those to return false here.
return false;
}
unsigned ASTContext::CountNonClassIvars(const ObjCInterfaceDecl *OI) const {
unsigned count = 0;
// Count ivars declared in class extension.

View File

@ -31,9 +31,16 @@ class CXXABI {
public:
virtual ~CXXABI();
/// Returns the width and alignment of a member pointer in bits.
virtual std::pair<uint64_t, unsigned>
getMemberPointerWidthAndAlign(const MemberPointerType *MPT) const = 0;
struct MemberPointerInfo {
uint64_t Width;
unsigned Align;
bool HasPadding;
};
/// Returns the width and alignment of a member pointer in bits, as well as
/// whether it has padding.
virtual MemberPointerInfo
getMemberPointerInfo(const MemberPointerType *MPT) const = 0;
/// Returns the default calling convention for C++ methods.
virtual CallingConv getDefaultMethodCallConv(bool isVariadic) const = 0;

View File

@ -101,15 +101,17 @@ protected:
public:
ItaniumCXXABI(ASTContext &Ctx) : Context(Ctx) { }
std::pair<uint64_t, unsigned>
getMemberPointerWidthAndAlign(const MemberPointerType *MPT) const override {
MemberPointerInfo
getMemberPointerInfo(const MemberPointerType *MPT) const override {
const TargetInfo &Target = Context.getTargetInfo();
TargetInfo::IntType PtrDiff = Target.getPtrDiffType(0);
uint64_t Width = Target.getTypeWidth(PtrDiff);
unsigned Align = Target.getTypeAlign(PtrDiff);
MemberPointerInfo MPI;
MPI.Width = Target.getTypeWidth(PtrDiff);
MPI.Align = Target.getTypeAlign(PtrDiff);
MPI.HasPadding = false;
if (MPT->isMemberFunctionPointer())
Width = 2 * Width;
return std::make_pair(Width, Align);
MPI.Width *= 2;
return MPI;
}
CallingConv getDefaultMethodCallConv(bool isVariadic) const override {

View File

@ -76,8 +76,8 @@ class MicrosoftCXXABI : public CXXABI {
public:
MicrosoftCXXABI(ASTContext &Ctx) : Context(Ctx) { }
std::pair<uint64_t, unsigned>
getMemberPointerWidthAndAlign(const MemberPointerType *MPT) const override;
MemberPointerInfo
getMemberPointerInfo(const MemberPointerType *MPT) const override;
CallingConv getDefaultMethodCallConv(bool isVariadic) const override {
if (!isVariadic &&
@ -227,7 +227,7 @@ getMSMemberPointerSlots(const MemberPointerType *MPT) {
return std::make_pair(Ptrs, Ints);
}
std::pair<uint64_t, unsigned> MicrosoftCXXABI::getMemberPointerWidthAndAlign(
CXXABI::MemberPointerInfo MicrosoftCXXABI::getMemberPointerInfo(
const MemberPointerType *MPT) const {
// The nominal struct is laid out with pointers followed by ints and aligned
// to a pointer width if any are present and an int width otherwise.
@ -237,22 +237,25 @@ std::pair<uint64_t, unsigned> MicrosoftCXXABI::getMemberPointerWidthAndAlign(
unsigned Ptrs, Ints;
std::tie(Ptrs, Ints) = getMSMemberPointerSlots(MPT);
uint64_t Width = Ptrs * PtrSize + Ints * IntSize;
unsigned Align;
MemberPointerInfo MPI;
MPI.HasPadding = false;
MPI.Width = Ptrs * PtrSize + Ints * IntSize;
// When MSVC does x86_32 record layout, it aligns aggregate member pointers to
// 8 bytes. However, __alignof usually returns 4 for data memptrs and 8 for
// function memptrs.
if (Ptrs + Ints > 1 && Target.getTriple().isArch32Bit())
Align = 64;
MPI.Align = 64;
else if (Ptrs)
Align = Target.getPointerAlign(0);
MPI.Align = Target.getPointerAlign(0);
else
Align = Target.getIntAlign();
MPI.Align = Target.getIntAlign();
if (Target.getTriple().isArch64Bit())
Width = llvm::alignTo(Width, Align);
return std::make_pair(Width, Align);
if (Target.getTriple().isArch64Bit()) {
MPI.Width = llvm::alignTo(MPI.Width, MPI.Align);
MPI.HasPadding = MPI.Width != (Ptrs * PtrSize + Ints * IntSize);
}
return MPI;
}
CXXABI *clang::CreateMicrosoftCXXABI(ASTContext &Ctx) {

View File

@ -2201,150 +2201,6 @@ bool QualType::isTriviallyCopyableType(const ASTContext &Context) const {
return false;
}
bool QualType::unionHasUniqueObjectRepresentations(
const ASTContext &Context) const {
assert((*this)->isUnionType() && "must be union type");
CharUnits UnionSize = Context.getTypeSizeInChars(*this);
const RecordDecl *Union = getTypePtr()->getAs<RecordType>()->getDecl();
for (const auto *Field : Union->fields()) {
if (!Field->getType().hasUniqueObjectRepresentations(Context))
return false;
CharUnits FieldSize = Context.getTypeSizeInChars(Field->getType());
if (FieldSize != UnionSize)
return false;
}
return true;
}
static bool isStructEmpty(QualType Ty) {
assert(Ty.getTypePtr()->isStructureOrClassType() &&
"Must be struct or class");
const RecordDecl *RD = Ty.getTypePtr()->getAs<RecordType>()->getDecl();
if (!RD->field_empty())
return false;
if (const CXXRecordDecl *ClassDecl = dyn_cast<CXXRecordDecl>(RD)) {
return ClassDecl->isEmpty();
}
return true;
}
bool QualType::structHasUniqueObjectRepresentations(
const ASTContext &Context) const {
assert((*this)->isStructureOrClassType() && "Must be struct or class");
const RecordDecl *RD = getTypePtr()->getAs<RecordType>()->getDecl();
if (isStructEmpty(*this))
return false;
// Check base types.
CharUnits BaseSize{};
if (const CXXRecordDecl *ClassDecl = dyn_cast<CXXRecordDecl>(RD)) {
for (const auto Base : ClassDecl->bases()) {
if (Base.isVirtual())
return false;
// Empty bases are permitted, otherwise ensure base has unique
// representation. Also, Empty Base Optimization means that an
// Empty base takes up 0 size.
if (!isStructEmpty(Base.getType())) {
if (!Base.getType().structHasUniqueObjectRepresentations(Context))
return false;
BaseSize += Context.getTypeSizeInChars(Base.getType());
}
}
}
CharUnits StructSize = Context.getTypeSizeInChars(*this);
// This struct obviously has bases that keep it from being 'empty', so
// checking fields is no longer required. Ensure that the struct size
// is the sum of the bases.
if (RD->field_empty())
return StructSize == BaseSize;
CharUnits CurOffset =
Context.toCharUnitsFromBits(Context.getFieldOffset(*RD->field_begin()));
// If the first field isn't at the sum of the size of the bases, there
// is padding somewhere.
if (BaseSize != CurOffset)
return false;
for (const auto *Field : RD->fields()) {
if (!Field->getType().hasUniqueObjectRepresentations(Context))
return false;
CharUnits FieldSize = Context.getTypeSizeInChars(Field->getType());
CharUnits FieldOffset =
Context.toCharUnitsFromBits(Context.getFieldOffset(Field));
// Has padding between fields.
if (FieldOffset != CurOffset)
return false;
CurOffset += FieldSize;
}
// Check for tail padding.
return CurOffset == StructSize;
}
bool QualType::hasUniqueObjectRepresentations(const ASTContext &Context) const {
// C++17 [meta.unary.prop]:
// The predicate condition for a template specialization
// has_unique_object_representations<T> shall be
// satisfied if and only if:
// (9.1) - T is trivially copyable, and
// (9.2) - any two objects of type T with the same value have the same
// object representation, where two objects
// of array or non-union class type are considered to have the same value
// if their respective sequences of
// direct subobjects have the same values, and two objects of union type
// are considered to have the same
// value if they have the same active member and the corresponding members
// have the same value.
// The set of scalar types for which this condition holds is
// implementation-defined. [ Note: If a type has padding
// bits, the condition does not hold; otherwise, the condition holds true
// for unsigned integral types. -- end note ]
if (isNull())
return false;
// Arrays are unique only if their element type is unique.
if ((*this)->isArrayType())
return Context.getBaseElementType(*this).hasUniqueObjectRepresentations(
Context);
// (9.1) - T is trivially copyable, and
if (!isTriviallyCopyableType(Context))
return false;
// Functions are not unique.
if ((*this)->isFunctionType())
return false;
// All integrals and enums are unique!
if ((*this)->isIntegralOrEnumerationType())
return true;
// All pointers are unique, since they're just integrals.
if ((*this)->isPointerType() || (*this)->isMemberPointerType())
return true;
if ((*this)->isRecordType()) {
const RecordDecl *Record = getTypePtr()->getAs<RecordType>()->getDecl();
// Lambda types are not unique, so exclude them immediately.
if (Record->isLambda())
return false;
if (Record->isUnion())
return unionHasUniqueObjectRepresentations(Context);
return structHasUniqueObjectRepresentations(Context);
}
return false;
}
bool QualType::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const {
return !Context.getLangOpts().ObjCAutoRefCount &&
Context.getLangOpts().ObjCWeak &&

View File

@ -4616,7 +4616,7 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
// function call.
return !T->isIncompleteType();
case UTT_HasUniqueObjectRepresentations:
return T.hasUniqueObjectRepresentations(C);
return C.hasUniqueObjectRepresentations(T);
}
}

View File

@ -0,0 +1,32 @@
// RUN: %clang_cc1 -triple x86_64-linux-pc -DIS64 -fsyntax-only -verify -std=c++17 %s
// RUN: %clang_cc1 -triple x86_64-windows-pc -DIS64 -fsyntax-only -verify -std=c++17 %s
// RUN: %clang_cc1 -triple i386-linux-pc -fsyntax-only -verify -std=c++17 %s
// RUN: %clang_cc1 -triple i386-windows-pc -DW32 -fsyntax-only -verify -std=c++17 %s
// expected-no-diagnostics
struct Base {};
struct A : virtual Base {
virtual void n() {}
};
auto p = &A::n;
static_assert(__has_unique_object_representations(decltype(p)));
struct B {
decltype(p) x;
int b;
#ifdef IS64
// required on 64 bit to fill out the tail padding.
int c;
#endif
};
static_assert(__has_unique_object_representations(B));
struct C { // has padding on Win32, but nothing else.
decltype(p) x;
};
#ifdef W32
static_assert(!__has_unique_object_representations(C));
#else
static_assert(__has_unique_object_representations(C));
#endif

View File

@ -2447,7 +2447,7 @@ struct Padding {
int b;
};
static_assert(!has_unique_object_representations<Padding>::value, "but not with padding");
//static_assert(!has_unique_object_representations<Padding>::value, "but not with padding");
struct InheritsFromPadding : Padding {
int c;
@ -2518,12 +2518,11 @@ enum class LLEnumClass : long long { xLongExample,
static_assert(has_unique_object_representations<ExampleEnumClass>::value, "Enums are integrals, so unique!");
static_assert(has_unique_object_representations<LLEnumClass>::value, "Enums are integrals, so unique!");
// because reference types aren't object types
// because references aren't trivially copyable.
static_assert(!has_unique_object_representations<int &>::value, "No references!");
static_assert(!has_unique_object_representations<const int &>::value, "No references!");
static_assert(!has_unique_object_representations<volatile int &>::value, "No references!");
static_assert(!has_unique_object_representations<const volatile int &>::value, "No references!");
static_assert(!has_unique_object_representations<Empty>::value, "No empty types!");
class Compressed : Empty {
@ -2556,6 +2555,16 @@ static_assert(!has_unique_object_representations<double[42]>::value, "So no arra
static_assert(!has_unique_object_representations<double[]>::value, "So no array of doubles!");
static_assert(!has_unique_object_representations<double[][42]>::value, "So no array of doubles!");
struct __attribute__((aligned(16))) WeirdAlignment {
int i;
};
union __attribute__((aligned(16))) WeirdAlignmentUnion {
int i;
};
static_assert(!has_unique_object_representations<WeirdAlignment>::value, "Alignment causes padding");
static_assert(!has_unique_object_representations<WeirdAlignmentUnion>::value, "Alignment causes padding");
static_assert(!has_unique_object_representations<WeirdAlignment[42]>::value, "Also no arrays that have padding");
static_assert(!has_unique_object_representations<int(int)>::value, "Functions are not unique");
static_assert(!has_unique_object_representations<int(int) const>::value, "Functions are not unique");
static_assert(!has_unique_object_representations<int(int) volatile>::value, "Functions are not unique");
@ -2582,6 +2591,73 @@ static_assert(!has_unique_object_representations<int(int, ...) const &&>::value,
static_assert(!has_unique_object_representations<int(int, ...) volatile &&>::value, "Functions are not unique");
static_assert(!has_unique_object_representations<int(int, ...) const volatile &&>::value, "Functions are not unique");
static auto lambda = []() {};
static_assert(!has_unique_object_representations<decltype(lambda)>::value, "Lambdas are not unique");
void foo(){
static auto lambda = []() {};
static_assert(!has_unique_object_representations<decltype(lambda)>::value, "Lambdas follow struct rules");
int i;
static auto lambda2 = [i]() {};
static_assert(has_unique_object_representations<decltype(lambda2)>::value, "Lambdas follow struct rules");
}
struct PaddedBitfield {
char c : 6;
char d : 1;
};
struct UnPaddedBitfield {
char c : 6;
char d : 2;
};
struct AlignedPaddedBitfield {
char c : 6;
__attribute__((aligned(1)))
char d : 2;
};
static_assert(!has_unique_object_representations<PaddedBitfield>::value, "Bitfield padding");
static_assert(has_unique_object_representations<UnPaddedBitfield>::value, "Bitfield padding");
static_assert(!has_unique_object_representations<AlignedPaddedBitfield>::value, "Bitfield padding");
struct BoolBitfield {
bool b : 8;
};
static_assert(has_unique_object_representations<BoolBitfield>::value, "Bitfield bool");
struct BoolBitfield2 {
bool b : 16;
};
static_assert(!has_unique_object_representations<BoolBitfield2>::value, "Bitfield bool");
struct GreaterSizeBitfield {
//expected-warning@+1 {{width of bit-field 'n'}}
int n : 1024;
};
static_assert(sizeof(GreaterSizeBitfield) == 128, "Bitfield Size");
static_assert(!has_unique_object_representations<GreaterSizeBitfield>::value, "Bitfield padding");
struct StructWithRef {
int &I;
};
static_assert(has_unique_object_representations<StructWithRef>::value, "References are still unique");
struct NotUniqueBecauseTailPadding {
int &r;
char a;
};
struct CanBeUniqueIfNoPadding : NotUniqueBecauseTailPadding {
char b[7];
};
static_assert(!has_unique_object_representations<NotUniqueBecauseTailPadding>::value,
"non trivial");
// Can be unique on Itanium, since the is child class' data is 'folded' into the
// parent's tail padding.
static_assert(sizeof(CanBeUniqueIfNoPadding) != 16 ||
has_unique_object_representations<CanBeUniqueIfNoPadding>::value,
"inherit from std layout");