[llvm-branch-commits] [llvm] ae9fd55 - [SmallVector] Allow SmallVector<T>
Sean Silva via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Dec 3 17:26:52 PST 2020
Author: Sean Silva
Date: 2020-12-03T17:21:44-08:00
New Revision: ae9fd5578e8ab59bcffe1fd2b6a91531dff5bde6
URL: https://github.com/llvm/llvm-project/commit/ae9fd5578e8ab59bcffe1fd2b6a91531dff5bde6
DIFF: https://github.com/llvm/llvm-project/commit/ae9fd5578e8ab59bcffe1fd2b6a91531dff5bde6.diff
LOG: [SmallVector] Allow SmallVector<T>
This patch adds a capability to SmallVector to decide a number of
inlined elements automatically. The policy is:
- A minimum of 1 inlined elements, with more as long as
sizeof(SmallVector<T>) <= 64.
- If sizeof(T) is "too big", then trigger a static_assert: this dodges
the more pathological cases
This is expected to systematically improve SmallVector use in the
LLVM codebase, which has historically been plagued by semi-arbitrary /
cargo culted N parameters, often leading to bad outcomes due to
excessive sizeof(SmallVector<T, N>). This default also makes
programming more convenient by avoiding edit/rebuild cycles due to
forgetting to type the N parameter.
Differential Revision: https://reviews.llvm.org/D92522
Added:
Modified:
llvm/docs/ProgrammersManual.rst
llvm/include/llvm/ADT/SmallVector.h
llvm/unittests/ADT/SmallVectorTest.cpp
Removed:
################################################################################
diff --git a/llvm/docs/ProgrammersManual.rst b/llvm/docs/ProgrammersManual.rst
index e303a7a18eba..7713a353ae1d 100644
--- a/llvm/docs/ProgrammersManual.rst
+++ b/llvm/docs/ProgrammersManual.rst
@@ -1521,6 +1521,12 @@ this makes the size of the SmallVector itself large, so you don't want to
allocate lots of them (doing so will waste a lot of space). As such,
SmallVectors are most useful when on the stack.
+In the absence of a well-motivated choice for the number of
+inlined elements ``N``, it is recommended to use ``SmallVector<T>`` (that is,
+omitting the ``N``). This will choose a default number of
+inlined elements reasonable for allocation on the stack (for example, trying
+to keep ``sizeof(SmallVector<T>)`` around 64 bytes).
+
SmallVector also provides a nice portable and efficient replacement for
``alloca``.
diff --git a/llvm/include/llvm/ADT/SmallVector.h b/llvm/include/llvm/ADT/SmallVector.h
index c5bb1ece0667..1b2787b88932 100644
--- a/llvm/include/llvm/ADT/SmallVector.h
+++ b/llvm/include/llvm/ADT/SmallVector.h
@@ -948,6 +948,64 @@ struct SmallVectorStorage {
/// well-defined.
template <typename T> struct alignas(T) SmallVectorStorage<T, 0> {};
+/// Forward declaration of SmallVector so that
+/// calculateSmallVectorDefaultInlinedElements can reference
+/// `sizeof(SmallVector<T, 0>)`.
+template <typename T, unsigned N> class LLVM_GSL_OWNER SmallVector;
+
+/// Helper class for calculating the default number of inline elements for
+/// `SmallVector<T>`.
+///
+/// This should be migrated to a constexpr function when our minimum
+/// compiler support is enough for multi-statement constexpr functions.
+template <typename T> struct CalculateSmallVectorDefaultInlinedElements {
+ // Parameter controlling the default number of inlined elements
+ // for `SmallVector<T>`.
+ //
+ // The default number of inlined elements ensures that
+ // 1. There is at least one inlined element.
+ // 2. `sizeof(SmallVector<T>) <= kPreferredSmallVectorSizeof` unless
+ // it contradicts 1.
+ static constexpr size_t kPreferredSmallVectorSizeof = 64;
+
+ // static_assert that sizeof(T) is not "too big".
+ //
+ // Because our policy guarantees at least one inlined element, it is possible
+ // for an arbitrarily large inlined element to allocate an arbitrarily large
+ // amount of inline storage. We generally consider it an antipattern for a
+ // SmallVector to allocate an excessive amount of inline storage, so we want
+ // to call attention to these cases and make sure that users are making an
+ // intentional decision if they request a lot of inline storage.
+ //
+ // We want this assertion to trigger in pathological cases, but otherwise
+ // not be too easy to hit. To accomplish that, the cutoff is actually somewhat
+ // larger than kPreferredSmallVectorSizeof (otherwise,
+ // `SmallVector<SmallVector<T>>` would be one easy way to trip it, and that
+ // pattern seems useful in practice).
+ //
+ // One wrinkle is that this assertion is in theory non-portable, since
+ // sizeof(T) is in general platform-dependent. However, we don't expect this
+ // to be much of an issue, because most LLVM development happens on 64-bit
+ // hosts, and therefore sizeof(T) is expected to *decrease* when compiled for
+ // 32-bit hosts, dodging the issue. The reverse situation, where development
+ // happens on a 32-bit host and then fails due to sizeof(T) *increasing* on a
+ // 64-bit host, is expected to be very rare.
+ static_assert(
+ sizeof(T) <= 256,
+ "You are trying to use a default number of inlined elements for "
+ "`SmallVector<T>` but `sizeof(T)` is really big! Please use an "
+ "explicit number of inlined elements with `SmallVector<T, N>` to make "
+ "sure you really want that much inline storage.");
+
+ // Discount the size of the header itself when calculating the maximum inline
+ // bytes.
+ static constexpr size_t PreferredInlineBytes =
+ kPreferredSmallVectorSizeof - sizeof(SmallVector<T, 0>);
+ static constexpr size_t NumElementsThatFit = PreferredInlineBytes / sizeof(T);
+ static constexpr size_t value =
+ NumElementsThatFit == 0 ? 1 : NumElementsThatFit;
+};
+
/// This is a 'vector' (really, a variable-sized array), optimized
/// for the case when the array is small. It contains some number of elements
/// in-place, which allows it to avoid heap allocation when the actual number of
@@ -956,7 +1014,8 @@ template <typename T> struct alignas(T) SmallVectorStorage<T, 0> {};
///
/// Note that this does not attempt to be exception safe.
///
-template <typename T, unsigned N>
+template <typename T,
+ unsigned N = CalculateSmallVectorDefaultInlinedElements<T>::value>
class LLVM_GSL_OWNER SmallVector : public SmallVectorImpl<T>,
SmallVectorStorage<T, N> {
public:
diff --git a/llvm/unittests/ADT/SmallVectorTest.cpp b/llvm/unittests/ADT/SmallVectorTest.cpp
index 74b6561db83d..957412f3083f 100644
--- a/llvm/unittests/ADT/SmallVectorTest.cpp
+++ b/llvm/unittests/ADT/SmallVectorTest.cpp
@@ -983,6 +983,22 @@ TEST(SmallVectorTest, EmplaceBack) {
}
}
+TEST(SmallVectorTest, DefaultInlinedElements) {
+ SmallVector<int> V;
+ EXPECT_TRUE(V.empty());
+ V.push_back(7);
+ EXPECT_EQ(V[0], 7);
+
+ // Check that at least a couple layers of nested SmallVector<T>'s are allowed
+ // by the default inline elements policy. This pattern happens in practice
+ // with some frequency, and it seems fairly harmless even though each layer of
+ // SmallVector's will grow the total sizeof by a vector header beyond the
+ // "preferred" maximum sizeof.
+ SmallVector<SmallVector<SmallVector<int>>> NestedV;
+ NestedV.emplace_back().emplace_back().emplace_back(42);
+ EXPECT_EQ(NestedV[0][0][0], 42);
+}
+
TEST(SmallVectorTest, InitializerList) {
SmallVector<int, 2> V1 = {};
EXPECT_TRUE(V1.empty());
More information about the llvm-branch-commits
mailing list