Skip to content

Commit

Permalink
P0840R2: support for [[no_unique_address]] attribute
Browse files Browse the repository at this point in the history
Summary:
Add support for the C++2a [[no_unique_address]] attribute for targets using the Itanium C++ ABI.

This depends on D63371.

Reviewers: rjmccall, aaron.ballman

Subscribers: dschuff, aheejin, cfe-commits

Tags: #clang

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

llvm-svn: 363976
  • Loading branch information
zygoloid committed Jun 20, 2019
1 parent 60ca31a commit 78b239e
Show file tree
Hide file tree
Showing 21 changed files with 704 additions and 98 deletions.
5 changes: 5 additions & 0 deletions clang/include/clang/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2729,6 +2729,11 @@ class FieldDecl : public DeclaratorDecl, public Mergeable<FieldDecl> {
/// bit-fields.
bool isZeroLengthBitField(const ASTContext &Ctx) const;

/// Determine if this field is a subobject of zero size, that is, either a
/// zero-length bit-field or a field of empty class type with the
/// [[no_unique_address]] attribute.
bool isZeroSize(const ASTContext &Ctx) const;

/// Get the kind of (C++11) default member initializer that this field has.
InClassInitStyle getInClassInitStyle() const {
InitStorageKind storageKind = InitStorage.getInt();
Expand Down
10 changes: 6 additions & 4 deletions clang/include/clang/AST/DeclCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,10 +334,12 @@ class CXXRecordDecl : public RecordDecl {
/// True when this class is a POD-type.
unsigned PlainOldData : 1;

/// true when this class is empty for traits purposes,
/// i.e. has no data members other than 0-width bit-fields, has no
/// virtual function/base, and doesn't inherit from a non-empty
/// class. Doesn't take union-ness into account.
/// True when this class is empty for traits purposes, that is:
/// * has no data members other than 0-width bit-fields and empty fields
/// marked [[no_unique_address]]
/// * has no virtual function/base, and
/// * doesn't inherit from a non-empty class.
/// Doesn't take union-ness into account.
unsigned Empty : 1;

/// True when this class is polymorphic, i.e., has at
Expand Down
20 changes: 16 additions & 4 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class DocumentationCategory<string name> {
}
def DocCatFunction : DocumentationCategory<"Function Attributes">;
def DocCatVariable : DocumentationCategory<"Variable Attributes">;
def DocCatField : DocumentationCategory<"Field Attributes">;
def DocCatType : DocumentationCategory<"Type Attributes">;
def DocCatStmt : DocumentationCategory<"Statement Attributes">;
def DocCatDecl : DocumentationCategory<"Declaration Attributes">;
Expand Down Expand Up @@ -315,12 +316,14 @@ class TargetSpec {
// Specifies Operating Systems for which the target applies, based off the
// OSType enumeration in Triple.h
list<string> OSes;
// Specifies the C++ ABIs for which the target applies, based off the
// TargetCXXABI::Kind in TargetCXXABI.h.
list<string> CXXABIs;
// Specifies Object Formats for which the target applies, based off the
// ObjectFormatType enumeration in Triple.h
list<string> ObjectFormats;
// A custom predicate, written as an expression evaluated in a context
// with the following declarations in scope:
// const clang::TargetInfo &Target;
// const llvm::Triple &T = Target.getTriple();
code CustomCode = [{}];
}

class TargetArch<list<string> arches> : TargetSpec {
Expand All @@ -338,8 +341,11 @@ def TargetWebAssembly : TargetArch<["wasm32", "wasm64"]>;
def TargetWindows : TargetArch<["x86", "x86_64", "arm", "thumb", "aarch64"]> {
let OSes = ["Win32"];
}
def TargetItaniumCXXABI : TargetSpec {
let CustomCode = [{ Target.getCXXABI().isItaniumFamily() }];
}
def TargetMicrosoftCXXABI : TargetArch<["x86", "x86_64", "arm", "thumb", "aarch64"]> {
let CXXABIs = ["Microsoft"];
let CustomCode = [{ Target.getCXXABI().isMicrosoft() }];
}
def TargetELF : TargetSpec {
let ObjectFormats = ["ELF"];
Expand Down Expand Up @@ -1408,6 +1414,12 @@ def NeonVectorType : TypeAttr {
let ASTNode = 0;
}

def NoUniqueAddress : InheritableAttr, TargetSpecificAttr<TargetItaniumCXXABI> {
let Spellings = [CXX11<"", "no_unique_address", 201803>];
let Subjects = SubjectList<[NonBitField], ErrorDiag>;
let Documentation = [NoUniqueAddressDocs];
}

def ReturnsTwice : InheritableAttr {
let Spellings = [GCC<"returns_twice">];
let Subjects = SubjectList<[Function]>;
Expand Down
26 changes: 26 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,32 @@ is not specified.
}];
}

def NoUniqueAddressDocs : Documentation {
let Category = DocCatField;
let Content = [{
The ``no_unique_address`` attribute allows tail padding in a non-static data
member to overlap other members of the enclosing class (and in the special
case when the type is empty, permits it to fully overlap other members).
The field is laid out as if a base class were encountered at the corresponding
point within the class (except that it does not share a vptr with the enclosing
object).

Example usage:

.. code-block:: c++

template<typename T, typename Alloc> struct my_vector {
T *p;
[[no_unique_address]] Alloc alloc;
// ...
};
static_assert(sizeof(my_vector<int, std::allocator<int>>) == sizeof(int*));

``[[no_unique_address]]`` is a standard C++20 attribute. Clang supports its use
in C++11 onwards.
}];
}

def ObjCRequiresSuperDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
Expand Down
33 changes: 33 additions & 0 deletions clang/lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3913,6 +3913,39 @@ bool FieldDecl::isZeroLengthBitField(const ASTContext &Ctx) const {
getBitWidthValue(Ctx) == 0;
}

bool FieldDecl::isZeroSize(const ASTContext &Ctx) const {
if (isZeroLengthBitField(Ctx))
return true;

// C++2a [intro.object]p7:
// An object has nonzero size if it
// -- is not a potentially-overlapping subobject, or
if (!hasAttr<NoUniqueAddressAttr>())
return false;

// -- is not of class type, or
const auto *RT = getType()->getAs<RecordType>();
if (!RT)
return false;
const RecordDecl *RD = RT->getDecl()->getDefinition();
if (!RD) {
assert(isInvalidDecl() && "valid field has incomplete type");
return false;
}

// -- [has] virtual member functions or virtual base classes, or
// -- has subobjects of nonzero size or bit-fields of nonzero length
const auto *CXXRD = cast<CXXRecordDecl>(RD);
if (!CXXRD->isEmpty())
return false;

// Otherwise, [...] the circumstances under which the object has zero size
// are implementation-defined.
// FIXME: This might be Itanium ABI specific; we don't yet know what the MS
// ABI will do.
return true;
}

unsigned FieldDecl::getFieldIndex() const {
const FieldDecl *Canonical = getCanonicalDecl();
if (Canonical != this)
Expand Down
22 changes: 17 additions & 5 deletions clang/lib/AST/DeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -605,22 +605,27 @@ bool CXXRecordDecl::hasSubobjectAtOffsetZeroOfEmptyBaseType(
// that sure looks like a wording bug.

// -- If X is a non-union class type with a non-static data member
// [recurse to] the first non-static data member of X
// [recurse to each field] that is either of zero size or is the
// first non-static data member of X
// -- If X is a union type, [recurse to union members]
bool IsFirstField = true;
for (auto *FD : X->fields()) {
// FIXME: Should we really care about the type of the first non-static
// data member of a non-union if there are preceding unnamed bit-fields?
if (FD->isUnnamedBitfield())
continue;

if (!IsFirstField && !FD->isZeroSize(Ctx))
continue;

// -- If X is n array type, [visit the element type]
QualType T = Ctx.getBaseElementType(FD->getType());
if (auto *RD = T->getAsCXXRecordDecl())
if (Visit(RD))
return true;

if (!X->isUnion())
break;
IsFirstField = false;
}
}

Expand Down Expand Up @@ -1068,6 +1073,10 @@ void CXXRecordDecl::addedMember(Decl *D) {
if (T->isReferenceType())
data().DefaultedMoveAssignmentIsDeleted = true;

// Bitfields of length 0 are also zero-sized, but we already bailed out for
// those because they are always unnamed.
bool IsZeroSize = Field->isZeroSize(Context);

if (const auto *RecordTy = T->getAs<RecordType>()) {
auto *FieldRec = cast<CXXRecordDecl>(RecordTy->getDecl());
if (FieldRec->getDefinition()) {
Expand Down Expand Up @@ -1183,7 +1192,8 @@ void CXXRecordDecl::addedMember(Decl *D) {
// A standard-layout class is a class that:
// [...]
// -- has no element of the set M(S) of types as a base class.
if (data().IsStandardLayout && (isUnion() || IsFirstField) &&
if (data().IsStandardLayout &&
(isUnion() || IsFirstField || IsZeroSize) &&
hasSubobjectAtOffsetZeroOfEmptyBaseType(Context, FieldRec))
data().IsStandardLayout = false;

Expand Down Expand Up @@ -1265,8 +1275,10 @@ void CXXRecordDecl::addedMember(Decl *D) {
}

// C++14 [meta.unary.prop]p4:
// T is a class type [...] with [...] no non-static data members
data().Empty = false;
// T is a class type [...] with [...] no non-static data members other
// than subobjects of zero size
if (data().Empty && !IsZeroSize)
data().Empty = false;
}

// Handle using declarations of conversion functions.
Expand Down
Loading

0 comments on commit 78b239e

Please sign in to comment.