[PATCH] D11272: Add a TrailingObjects template class.
Justin Bogner
mail at justinbogner.com
Fri Jul 24 11:07:10 PDT 2015
James Y Knight <jyknight at google.com> writes:
> jyknight updated this revision to Diff 30009.
> jyknight added a comment.
>
> Update for review comments
This looks pretty good. A couple of stylistic comments below.
>
> http://reviews.llvm.org/D11272
>
> Files:
> include/llvm/Support/TrailingObjects.h
> lib/IR/AttributeImpl.h
> lib/IR/Attributes.cpp
> unittests/Support/CMakeLists.txt
> unittests/Support/TrailingObjectsTest.cpp
>
> Index: unittests/Support/TrailingObjectsTest.cpp
> ===================================================================
> --- /dev/null
> +++ unittests/Support/TrailingObjectsTest.cpp
> @@ -0,0 +1,147 @@
> +//=== - llvm/unittest/Support/TrailingObjectsTest.cpp ---------------------===//
> +//
> +// The LLVM Compiler Infrastructure
> +//
> +// This file is distributed under the University of Illinois Open Source
> +// License. See LICENSE.TXT for details.
> +//
> +//===----------------------------------------------------------------------===//
> +
> +#include "llvm/Support/TrailingObjects.h"
> +#include "gtest/gtest.h"
> +
> +using namespace llvm;
> +
> +namespace {
> +// This class, beyond being used by the test case, a nice
> +// demonstration of the intended usage of TrailingObjects, with a
> +// single trailing array.
> +class Class1 final : TrailingObjects<Class1, short> {
> + friend class TrailingObjects;
> +
> + unsigned NumShorts;
> +
> +protected:
> + size_t numTrailingObjects(OverloadToken<short>) const { return NumShorts; }
> +
> + Class1(int *ShortArray, unsigned NumShorts) : NumShorts(NumShorts) {
> + std::uninitialized_copy(ShortArray, ShortArray + NumShorts,
> + getTrailingObjects<short>());
> + }
> +
> +public:
> + static Class1 *create(int *ShortArray, unsigned NumShorts) {
> + void *Mem = ::operator new(totalSizeToAlloc<short>(NumShorts));
> + return new (Mem) Class1(ShortArray, NumShorts);
> + }
> +
> + short get(unsigned Num) const { return getTrailingObjects<short>()[Num]; }
> +
> + unsigned numShorts() const { return NumShorts; }
> +
> + // Pull some protected members in as public, for testability.
> + using TrailingObjects::totalSizeToAlloc;
> + using TrailingObjects::additionalSizeToAlloc;
> + using TrailingObjects::getTrailingObjects;
> +};
> +
> +// Here, there are two singular optional object types appended.
> +// Note that it fails to compile without the alignment spec.
> +class LLVM_ALIGNAS(8) Class2 final : TrailingObjects<Class2, double, short> {
> + friend class TrailingObjects;
> +
> + bool HasShort, HasDouble;
> +
> +protected:
> + size_t numTrailingObjects(OverloadToken<short>) const {
> + return HasShort ? 1 : 0;
> + }
> + size_t numTrailingObjects(OverloadToken<double>) const {
> + return HasDouble ? 1 : 0;
> + }
> +
> + Class2(bool HasShort, bool HasDouble)
> + : HasShort(HasShort), HasDouble(HasDouble) {}
> +
> +public:
> + static Class2 *create(short S = 0, double D = 0.0) {
> + bool HasShort = S != 0;
> + bool HasDouble = D != 0.0;
> +
> + void *Mem =
> + ::operator new(totalSizeToAlloc<double, short>(HasDouble, HasShort));
> + Class2 *C = new (Mem) Class2(HasShort, HasDouble);
> + if (HasShort)
> + *C->getTrailingObjects<short>() = S;
> + if (HasDouble)
> + *C->getTrailingObjects<double>() = D;
> + return C;
> + }
> +
> + short getShort() const {
> + if (!HasShort)
> + return 0;
> + return *getTrailingObjects<short>();
> + }
> +
> + double getDouble() const {
> + if (!HasDouble)
> + return 0.0;
> + return *getTrailingObjects<double>();
> + }
> +
> + // Pull some protected members in as public, for testability.
> + using TrailingObjects::totalSizeToAlloc;
> + using TrailingObjects::additionalSizeToAlloc;
> + using TrailingObjects::getTrailingObjects;
> +};
> +
> +TEST(TrailingObjects, OneArg) {
> + int arr[] = {1, 2, 3};
> + Class1 *C = Class1::create(arr, 3);
> + EXPECT_EQ(sizeof(Class1), sizeof(unsigned));
> + EXPECT_EQ(Class1::additionalSizeToAlloc<short>(1), sizeof(short));
> + EXPECT_EQ(Class1::additionalSizeToAlloc<short>(3), sizeof(short) * 3);
> +
> + EXPECT_EQ(Class1::totalSizeToAlloc<short>(1), sizeof(Class1) + sizeof(short));
> + EXPECT_EQ(Class1::totalSizeToAlloc<short>(3),
> + sizeof(Class1) + sizeof(short) * 3);
> +
> + EXPECT_EQ(C->getTrailingObjects<short>(), reinterpret_cast<short *>(C + 1));
> + EXPECT_EQ(C->get(0), 1);
> + EXPECT_EQ(C->get(2), 3);
> + delete C;
> +}
> +
> +TEST(TrailingObjects, TwoArg) {
> + Class2 *C1 = Class2::create(4);
> + Class2 *C2 = Class2::create(0, 4.2);
> +
> + EXPECT_EQ(sizeof(Class2), 8u); // due to alignment
> +
> + EXPECT_EQ((Class2::additionalSizeToAlloc<double, short>(1, 0)),
> + sizeof(double));
> + EXPECT_EQ((Class2::additionalSizeToAlloc<double, short>(0, 1)),
> + sizeof(short));
> + EXPECT_EQ((Class2::additionalSizeToAlloc<double, short>(3, 1)),
> + sizeof(double) * 3 + sizeof(short));
> +
> + EXPECT_EQ((Class2::totalSizeToAlloc<double, short>(1, 1)),
> + sizeof(Class2) + sizeof(double) + sizeof(short));
> +
> + EXPECT_EQ(C1->getDouble(), 0);
> + EXPECT_EQ(C1->getShort(), 4);
> + EXPECT_EQ(C1->getTrailingObjects<double>(),
> + reinterpret_cast<double *>(C1 + 1));
> + EXPECT_EQ(C1->getTrailingObjects<short>(), reinterpret_cast<short *>(C1 + 1));
> +
> + EXPECT_EQ(C2->getDouble(), 4.2);
> + EXPECT_EQ(C2->getShort(), 0);
> + EXPECT_EQ(C2->getTrailingObjects<double>(),
> + reinterpret_cast<double *>(C2 + 1));
> + EXPECT_EQ(C2->getTrailingObjects<short>(),
> + reinterpret_cast<short *>(reinterpret_cast<double *>(C2 + 1) + 1));
> + delete C1;
> + delete C2;
> +}
> +}
> Index: unittests/Support/CMakeLists.txt
> ===================================================================
> --- unittests/Support/CMakeLists.txt
> +++ unittests/Support/CMakeLists.txt
> @@ -41,6 +41,7 @@
> TargetRegistry.cpp
> ThreadLocalTest.cpp
> TimeValueTest.cpp
> + TrailingObjectsTest.cpp
> UnicodeTest.cpp
> YAMLIOTest.cpp
> YAMLParserTest.cpp
> Index: lib/IR/Attributes.cpp
> ===================================================================
> --- lib/IR/Attributes.cpp
> +++ lib/IR/Attributes.cpp
> @@ -484,8 +484,7 @@
> // new one and insert it.
> if (!PA) {
> // Coallocate entries after the AttributeSetNode itself.
> - void *Mem = ::operator new(sizeof(AttributeSetNode) +
> - sizeof(Attribute) * SortedAttrs.size());
> + void *Mem = ::operator new(totalSizeToAlloc<Attribute>(SortedAttrs.size()));
> PA = new (Mem) AttributeSetNode(SortedAttrs);
> pImpl->AttrsSetNodes.InsertNode(PA, InsertPoint);
> }
> @@ -617,9 +616,8 @@
> // create a new one and insert it.
> if (!PA) {
> // Coallocate entries after the AttributeSetImpl itself.
> - void *Mem = ::operator new(sizeof(AttributeSetImpl) +
> - sizeof(std::pair<unsigned, AttributeSetNode *>) *
> - Attrs.size());
> + void *Mem = ::operator new(
> + AttributeSetImpl::totalSizeToAlloc<IndexAttrPair>(Attrs.size()));
> PA = new (Mem) AttributeSetImpl(C, Attrs);
> pImpl->AttrsLists.InsertNode(PA, InsertPoint);
> }
> @@ -736,9 +734,8 @@
> if (!AS) continue;
> SmallVector<std::pair<unsigned, AttributeSetNode *>, 8>::iterator
> ANVI = AttrNodeVec.begin(), ANVE;
> - for (const AttributeSetImpl::IndexAttrPair
> - *AI = AS->getNode(0),
> - *AE = AS->getNode(AS->getNumAttributes());
> + for (const IndexAttrPair *AI = AS->getNode(0),
> + *AE = AS->getNode(AS->getNumAttributes());
> AI != AE; ++AI) {
> ANVE = AttrNodeVec.end();
> while (ANVI != ANVE && ANVI->first <= AI->first)
> Index: lib/IR/AttributeImpl.h
> ===================================================================
> --- lib/IR/AttributeImpl.h
> +++ lib/IR/AttributeImpl.h
> @@ -18,6 +18,7 @@
>
> #include "llvm/ADT/FoldingSet.h"
> #include "llvm/IR/Attributes.h"
> +#include "llvm/Support/TrailingObjects.h"
> #include <string>
>
> namespace llvm {
> @@ -141,13 +142,14 @@
> /// \class
> /// \brief This class represents a group of attributes that apply to one
> /// element: function, return type, or parameter.
> -class AttributeSetNode : public FoldingSetNode {
> +class AttributeSetNode final
> + : public FoldingSetNode,
> + public TrailingObjects<AttributeSetNode, Attribute> {
> unsigned NumAttrs; ///< Number of attributes in this node.
>
> AttributeSetNode(ArrayRef<Attribute> Attrs) : NumAttrs(Attrs.size()) {
> // There's memory after the node where we can store the entries in.
> - std::copy(Attrs.begin(), Attrs.end(),
> - reinterpret_cast<Attribute *>(this + 1));
> + std::copy(Attrs.begin(), Attrs.end(), getTrailingObjects<Attribute>());
> }
>
> // AttributesSetNode is uniqued, these should not be publicly available.
> @@ -170,7 +172,7 @@
> std::string getAsString(bool InAttrGrp) const;
>
> typedef const Attribute *iterator;
> - iterator begin() const { return reinterpret_cast<iterator>(this + 1); }
> + iterator begin() const { return getTrailingObjects<Attribute>(); }
> iterator end() const { return begin() + NumAttrs; }
>
> void Profile(FoldingSetNodeID &ID) const {
> @@ -181,27 +183,28 @@
> AttrList[I].Profile(ID);
> }
> };
> -static_assert(
> - AlignOf<AttributeSetNode>::Alignment >= AlignOf<Attribute>::Alignment,
> - "Alignment is insufficient for objects appended to AttributeSetNode");
>
> //===----------------------------------------------------------------------===//
> /// \class
> /// \brief This class represents a set of attributes that apply to the function,
> /// return type, and parameters.
> -class AttributeSetImpl : public FoldingSetNode {
> - friend class AttributeSet;
> +typedef std::pair<unsigned, AttributeSetNode *> IndexAttrPair;
>
> -public:
> - typedef std::pair<unsigned, AttributeSetNode*> IndexAttrPair;
> +class AttributeSetImpl final
> + : public FoldingSetNode,
> + public TrailingObjects<AttributeSetImpl, IndexAttrPair> {
> + friend class AttributeSet;
>
> private:
> LLVMContext &Context;
> unsigned NumAttrs; ///< Number of entries in this set.
>
> + // Helper fn for TrailingObjects class.
> + size_t numTrailingObjects(OverloadToken<IndexAttrPair>) { return NumAttrs; }
> +
> /// \brief Return a pointer to the IndexAttrPair for the specified slot.
> const IndexAttrPair *getNode(unsigned Slot) const {
> - return reinterpret_cast<const IndexAttrPair *>(this + 1) + Slot;
> + return getTrailingObjects<IndexAttrPair>() + Slot;
> }
>
> // AttributesSet is uniqued, these should not be publicly available.
> @@ -222,8 +225,7 @@
> }
> #endif
> // There's memory after the node where we can store the entries in.
> - std::copy(Attrs.begin(), Attrs.end(),
> - reinterpret_cast<IndexAttrPair *>(this + 1));
> + std::copy(Attrs.begin(), Attrs.end(), getTrailingObjects<IndexAttrPair>());
> }
>
> /// \brief Get the context that created this AttributeSetImpl.
> @@ -273,10 +275,6 @@
>
> void dump() const;
> };
> -static_assert(
> - AlignOf<AttributeSetImpl>::Alignment >=
> - AlignOf<AttributeSetImpl::IndexAttrPair>::Alignment,
> - "Alignment is insufficient for objects appended to AttributeSetImpl");
>
> } // end llvm namespace
>
> Index: include/llvm/Support/TrailingObjects.h
> ===================================================================
> --- /dev/null
> +++ include/llvm/Support/TrailingObjects.h
> @@ -0,0 +1,234 @@
> +//===--- TrailingObjects.h - Variable-length classes ------------*- C++ -*-===//
> +//
> +// The LLVM Compiler Infrastructure
> +//
> +// This file is distributed under the University of Illinois Open Source
> +// License. See LICENSE.TXT for details.
> +//
> +//===----------------------------------------------------------------------===//
> +//
> +// This header defines support for implementing classes that have
> +// trailing object (or arrays of objects) appended to them. The main
> +// purpose is to make it obvious where this idiom is being used, and
> +// to make the usage more idiomatic and more difficult to get wrong.
> +//
> +// The template abstracts away the reinterpret_cast, pointer
> +// arithmetic, and size calculations used for the allocation and
> +// access of appended arrays of objects, as well as asserts that the
> +// alignment of the classes involved is appropriate for its usage.
The description of the template might be better as a doxygen comment on
the class itself, so it's a bit easier to find in the online docs. On a
related note, you have a lot of class and function descriptions in this
file that should be spelled with '///' instead of '//' so doxygen picks
them up.
> +//
> +// Users are expected to derive from the TrailingObjects[12]
> +// templates, and provide numTrailingObjects implementations for each
> +// trailing type, e.g. like this:
This comment's out of date.
> +//
> +// class VarLengthObj : TrailingObjects<VarLengthObj, int, double> {
> +// friend class TrailingObjects;
> +//
> +// unsigned NumInts, NumDoubles;
> +// size_t numTrailingObjects(OverloadToken<int>) const { return NumInts; }
> +// size_t numTrailingObjects(OverloadToken<double>) const { return NumDoubles;
> +// }
> +// };
> +//
> +// You can access the appended arrays via 'getTrailingObjects', and
> +// determine the size needed for allocation via 'additionalSizeToAlloc'
> +// and 'totalSizeToAlloc'.
> +//
> +// All the methods implemented by this class are protected -- they are
> +// intended for use by the implementation of the class, not as part of
> +// its interface.
> +//
> +// TODO: Use a variadic template instead of multiple copies of the
> +// TrailingObjects class? [but, variadic template recursive
> +// implementations are annoying...]
This one too.
> +//
> +//===----------------------------------------------------------------------===//
> +
> +#ifndef LLVM_SUPPORT_TRAILINGOBJECTS_H
> +#define LLVM_SUPPORT_TRAILINGOBJECTS_H
> +
> +#include <new>
> +#include <type_traits>
> +#include "llvm/Support/AlignOf.h"
> +#include "llvm/Support/Compiler.h"
> +
> +namespace llvm {
> +
> +// The base class for TrailingObjects* classes.
> +class TrailingObjectsBase {
> +protected:
> + // OverloadToken's only purpose is to allow specifying function
> + // overloads for different types, without actually taking the types
> + // as parameters. (Necessary because member function templates
> + // cannot be specialized, so overloads must be used instead of
> + // specialization.)
> + template <typename T> struct OverloadToken {};
> +};
> +
> +// Indicates the user didn't supply this value, so the
> +// explicit-specialization for fewer args will be used.
> +class NoTrailingTypeArg {};
> +
> +// Right now, for simplicity's sake, I've only implemented a 2-type
> +// version and a 1-type version of this class. This is the 2-type
> +// version. The 1-arg version is implemented below as a partial
> +// specialization.
> +template <typename BaseTy, typename TrailingTy1,
> + typename TrailingTy2 = NoTrailingTypeArg>
> +class TrailingObjects : public TrailingObjectsBase {
> +private:
> + // Contains static_assert statements for the alignment of the
> + // types. Must not be at class-level, because BaseTy isn't complete
> + // at class instantiation time, but will be by the time this
> + // function is instantiated.
> + static void verifyTrailingObjectsAssertions() {
> + static_assert(llvm::AlignOf<BaseTy>::Alignment >=
> + llvm::AlignOf<TrailingTy1>::Alignment,
> + "TrailingTy1 requires more alignment than BaseTy provides");
> + static_assert(
> + llvm::AlignOf<TrailingTy1>::Alignment >=
> + llvm::AlignOf<TrailingTy2>::Alignment,
> + "TrailingTy2 requires more alignment than TrailingTy1 provides");
> +
> +// If possible, check if the type is final.
> +#if __cplusplus >= 201402L
> + static_assert(std::is_final<BaseTy>(), "BaseTy must be final.");
> +#elif __has_feature(is_final) || LLVM_GNUC_PREREQ(4, 7, 0)
> + static_assert(__is_final(BaseTy), "BaseTy must be final.");
> +#endif
We should wrap this up in an LLVM_IS_FINAL in Support/Compiler.h or
Support/type_traits.h - you'll still need to check if that exists in
order to do the static_assert, but it consolidates the c++14/clang/gcc
check more cleanly.
> + }
> +
> + // The next four functions are internal helpers for getTrailingObjects.
> + static const TrailingTy1 *getTrailingObjectsImpl(const BaseTy *Obj,
> + OverloadToken<TrailingTy1>) {
> + return reinterpret_cast<const TrailingTy1 *>(Obj + 1);
> + }
> +
> + static TrailingTy1 *getTrailingObjectsImpl(BaseTy *Obj,
> + OverloadToken<TrailingTy1>) {
> + return reinterpret_cast<TrailingTy1 *>(Obj + 1);
> + }
> +
> + static const TrailingTy2 *getTrailingObjectsImpl(const BaseTy *Obj,
> + OverloadToken<TrailingTy2>) {
> + return reinterpret_cast<const TrailingTy2 *>(
> + getTrailingObjectsImpl(Obj, OverloadToken<TrailingTy1>()) +
> + Obj->numTrailingObjects(OverloadToken<TrailingTy1>()));
> + }
> +
> + static TrailingTy2 *getTrailingObjectsImpl(BaseTy *Obj,
> + OverloadToken<TrailingTy2>) {
> + return reinterpret_cast<TrailingTy2 *>(
> + getTrailingObjectsImpl(Obj, OverloadToken<TrailingTy1>()) +
> + Obj->numTrailingObjects(OverloadToken<TrailingTy1>()));
> + }
> +
> +protected:
> + // Returns a pointer to the trailing object array of the given type
> + // (which must be one of those specified in the class template). The
> + // array may have zero or more elements in it.
> + template <typename T> const T *getTrailingObjects() const {
> + verifyTrailingObjectsAssertions();
> + // Forwards to an impl function with overloads, since member
> + // function templates can't be specialized.
> + return getTrailingObjectsImpl(static_cast<const BaseTy *>(this),
> + OverloadToken<T>());
> + }
> +
> + // Returns a pointer to the trailing object array of the given type
> + // (which must be one of those specified in the class template). The
> + // array may have zero or more elements in it.
> + template <typename T> T *getTrailingObjects() {
> + verifyTrailingObjectsAssertions();
> + // Forwards to an impl function with overloads, since member
> + // function templates can't be specialized.
> + return getTrailingObjectsImpl(static_cast<BaseTy *>(this),
> + OverloadToken<T>());
> + }
> +
> + // Returns the size of the trailing data, if an object were
> + // allocated with the given counts (The counts are in the same order
> + // as the template arguments). This does not include the size of the
> + // base object. The template arguments must be the same as those
> + // used in the class; they are supplied here redundantly only so
> + // that it's clear what the counts are counting in callers.
> + template <typename Ty1, typename Ty2,
> + typename std::enable_if<std::is_same<Ty1, TrailingTy1>::value &&
> + std::is_same<Ty2, TrailingTy2>::value,
> + int>::type = 0>
> + static constexpr size_t additionalSizeToAlloc(size_t Count1, size_t Count2) {
> + return sizeof(TrailingTy1) * Count1 + sizeof(TrailingTy2) * Count2;
> + }
> +
> + // Returns the total size of an object if it were allocated with the
> + // given trailing object counts. This is the same as
> + // additionalSizeToAlloc, except it *does* include the size of the base
> + // object.
> + template <typename Ty1, typename Ty2>
> + static constexpr size_t totalSizeToAlloc(size_t Count1, size_t Count2) {
> + return sizeof(BaseTy) + additionalSizeToAlloc<Ty1, Ty2>(Count1, Count2);
> + }
> +};
> +
> +// Same as the above TrailingObjects class, but only for a single
> +// trailing type, instead of 2.
> +template <typename BaseTy, typename TrailingTy1>
> +class TrailingObjects<BaseTy, TrailingTy1, NoTrailingTypeArg>
> + : public TrailingObjectsBase {
> +private:
> + static void verifyTrailingObjectsAssertions() {
> + static_assert(llvm::AlignOf<BaseTy>::Alignment >=
> + llvm::AlignOf<TrailingTy1>::Alignment,
> + "TrailingTy1 requires more alignment than BaseTy provides");
> +
> +// If possible, check if the type is final.
> +#if __cplusplus >= 201402L
> + static_assert(std::is_final<BaseTy>(), "BaseTy must be final.");
> +#elif __has_feature(is_final) || LLVM_GNUC_PREREQ(4, 7, 0)
> + static_assert(__is_final(BaseTy), "BaseTy must be final.");
> +#endif
> + }
> +
> + static const TrailingTy1 *getTrailingObjectsImpl(const BaseTy *Obj,
> + OverloadToken<TrailingTy1>) {
> + return reinterpret_cast<const TrailingTy1 *>(Obj + 1);
> + }
> +
> + static TrailingTy1 *getTrailingObjectsImpl(BaseTy *Obj,
> + OverloadToken<TrailingTy1>) {
> + return reinterpret_cast<TrailingTy1 *>(Obj + 1);
> + }
> +
> +protected:
> + template <typename T> const T *getTrailingObjects() const {
> + verifyTrailingObjectsAssertions();
> + return getTrailingObjectsImpl(static_cast<const BaseTy *>(this),
> + OverloadToken<T>());
> + }
> +
> + template <typename T> T *getTrailingObjects() {
> + verifyTrailingObjectsAssertions();
> + return getTrailingObjectsImpl(static_cast<BaseTy *>(this),
> + OverloadToken<T>());
> + }
> +
> + template <typename Ty1,
> + typename std::enable_if<std::is_same<Ty1, TrailingTy1>::value,
> + int>::type = 0>
> + static constexpr size_t additionalSizeToAlloc(size_t Count1) {
> + return sizeof(TrailingTy1) * Count1;
> + }
> +
> + template <typename Ty1>
> + static constexpr size_t totalSizeToAlloc(size_t Count1) {
> + return sizeof(BaseTy) + additionalSizeToAlloc<Ty1>(Count1);
> + }
> +};
> +
> +} // end namespace llvm
> +
> +#ifdef LLVM_DEFINED_HAS_FEATURE
> +#undef __has_feature
> +#endif
> +
> +#endif
More information about the cfe-commits
mailing list