tint/utils: More Vector polish

* Added a class template argument deduction guide (CTAD) to infer the
  `T` and `N` template arguments. This lets you write `Vector{1,2,3}`
  instead of `Vector<int, 3>{1,2,3}`. This is important as a mismatch
  between the number of constructor arguments and the `N` template
  argument can cause silent heap allocations, which we're trying to
  avoid. The `T` deduction uses the same smarts as the return-type
  deduction of `Switch()`, so:
   * `Vector{1, 2.0}` would construct a `Vector<double, 2>`
   * `Vector{i32, u32}` would construct a `Vector<const sem::Type*, 2>`
* Removed the Vector(size_t) and Vector(size_t, const T&) constructors.
  This is a move away from the std::vector style API, but these are
  rarely more efficient than calling Reserve() and Push(), as you remove
  the redundant initialization. The main reason for doing this is to
  remove ambiguity between `Vector{1}` and `Vector(1)`.
* Added support for covariance conversion
  (`Vector<Derived*, N>` -> `Vector<Base*, N>`).
  Only supports pointers to `Castable`, as this can  only safely work
  with single-inheritance.
* Added support for conversion of `Vector<T*, N>` -> `Vector<const T*, N>`.
  This will remove pointless vector copies from the sem package.

Bug: tint:1613
Change-Id: I79b9f82d623f90afa14f8ba1613ee49cccceafeb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/97020
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
Ben Clayton 2022-07-25 23:37:34 +00:00 committed by Dawn LUCI CQ
parent 45db5df803
commit 4d1d143977
4 changed files with 1102 additions and 340 deletions

View File

@ -44,10 +44,10 @@ TEST(HashTests, StdVector) {
}
TEST(HashTests, TintVector) {
EXPECT_EQ(Hash(Vector<int>({})), Hash(Vector<int>({})));
EXPECT_EQ(Hash(Vector<int>({1, 2, 3})), Hash(Vector<int>({1, 2, 3})));
EXPECT_NE(Hash(Vector<int>({1, 2, 3})), Hash(Vector<int>({1, 2, 4})));
EXPECT_NE(Hash(Vector<int>({1, 2, 3})), Hash(Vector<int>({1, 2, 3, 4})));
EXPECT_EQ(Hash(Vector<int, 0>({})), Hash(Vector<int, 0>({})));
EXPECT_EQ(Hash(Vector<int, 0>({1, 2, 3})), Hash(Vector<int, 0>({1, 2, 3})));
EXPECT_NE(Hash(Vector<int, 0>({1, 2, 3})), Hash(Vector<int, 0>({1, 2, 4})));
EXPECT_NE(Hash(Vector<int, 0>({1, 2, 3})), Hash(Vector<int, 0>({1, 2, 3, 4})));
EXPECT_EQ(Hash(Vector<int, 3>({1, 2, 3})), Hash(Vector<int, 4>({1, 2, 3})));
EXPECT_EQ(Hash(Vector<int, 3>({1, 2, 3})), Hash(Vector<int, 2>({1, 2, 3})));
}

View File

@ -59,10 +59,12 @@ auto Transform(const std::vector<IN>& in, TRANSFORMER&& transform)
/// @returns a new vector with each element of the source vector transformed by `transform`.
template <typename IN, size_t N, typename TRANSFORMER>
auto Transform(const Vector<IN, N>& in, TRANSFORMER&& transform)
-> Vector<decltype(transform(in[0]))> {
Vector<decltype(transform(in[0])), N> result(in.Length());
for (size_t i = 0; i < result.Length(); ++i) {
result[i] = transform(in[i]);
-> Vector<decltype(transform(in[0])), N> {
const auto count = in.Length();
Vector<decltype(transform(in[0])), N> result;
result.Reserve(count);
for (size_t i = 0; i < count; ++i) {
result.Push(transform(in[i]));
}
return result;
}
@ -74,9 +76,11 @@ auto Transform(const Vector<IN, N>& in, TRANSFORMER&& transform)
template <typename IN, size_t N, typename TRANSFORMER>
auto Transform(const Vector<IN, N>& in, TRANSFORMER&& transform)
-> Vector<decltype(transform(in[0], 1u)), N> {
Vector<decltype(transform(in[0], 1u)), N> result(in.Length());
for (size_t i = 0; i < result.Length(); ++i) {
result[i] = transform(in[i], i);
const auto count = in.Length();
Vector<decltype(transform(in[0], 1u)), N> result;
result.Reserve(count);
for (size_t i = 0; i < count; ++i) {
result.Push(transform(in[i], i));
}
return result;
}
@ -89,9 +93,11 @@ auto Transform(const Vector<IN, N>& in, TRANSFORMER&& transform)
template <size_t N, typename IN, typename TRANSFORMER>
auto Transform(const VectorRef<IN>& in, TRANSFORMER&& transform)
-> Vector<decltype(transform(in[0])), N> {
Vector<decltype(transform(in[0])), N> result(in.Length());
for (size_t i = 0; i < result.Length(); ++i) {
result[i] = transform(in[i]);
const auto count = in.Length();
Vector<decltype(transform(in[0])), N> result;
result.Reserve(count);
for (size_t i = 0; i < count; ++i) {
result.Push(transform(in[i]));
}
return result;
}
@ -104,9 +110,11 @@ auto Transform(const VectorRef<IN>& in, TRANSFORMER&& transform)
template <size_t N, typename IN, typename TRANSFORMER>
auto Transform(const VectorRef<IN>& in, TRANSFORMER&& transform)
-> Vector<decltype(transform(in[0], 1u)), N> {
Vector<decltype(transform(in[0], 1u)), N> result(in.Length());
for (size_t i = 0; i < result.Length(); ++i) {
result[i] = transform(in[i], i);
const auto count = in.Length();
Vector<decltype(transform(in[0], 1u)), N> result;
result.Reserve(count);
for (size_t i = 0; i < count; ++i) {
result.Push(transform(in[i], i));
}
return result;
}
@ -119,9 +127,11 @@ auto Transform(const VectorRef<IN>& in, TRANSFORMER&& transform)
template <size_t N, typename IN, typename TRANSFORMER>
auto Transform(ConstVectorRef<IN> in, TRANSFORMER&& transform)
-> Vector<decltype(transform(in[0])), N> {
Vector<decltype(transform(in[0])), N> result(in.Length());
for (size_t i = 0; i < result.Length(); ++i) {
result[i] = transform(in[i]);
const auto count = in.Length();
Vector<decltype(transform(in[0])), N> result;
result.Reserve(count);
for (size_t i = 0; i < count; ++i) {
result.Push(transform(in[i]));
}
return result;
}
@ -134,9 +144,11 @@ auto Transform(ConstVectorRef<IN> in, TRANSFORMER&& transform)
template <size_t N, typename IN, typename TRANSFORMER>
auto Transform(ConstVectorRef<IN> in, TRANSFORMER&& transform)
-> Vector<decltype(transform(in[0], 1u)), N> {
Vector<decltype(transform(in[0], 1u)), N> result(in.Length());
for (size_t i = 0; i < result.Length(); ++i) {
result[i] = transform(in[i], i);
const auto count = in.Length();
Vector<decltype(transform(in[0], 1u)), N> result;
result.Reserve(count);
for (size_t i = 0; i < count; ++i) {
result.Push(transform(in[i], i));
}
return result;
}

View File

@ -22,6 +22,8 @@
#include <utility>
#include <vector>
#include "src/tint/castable.h"
#include "src/tint/traits.h"
#include "src/tint/utils/bitcast.h"
namespace tint::utils {
@ -36,8 +38,6 @@ class ConstVectorRef;
namespace tint::utils {
namespace detail {
/// A slice represents a contigious array of elements of type T.
template <typename T>
struct Slice {
@ -97,7 +97,42 @@ struct Slice {
auto rend() const { return std::reverse_iterator<const T*>(begin()); }
};
} // namespace detail
/// Evaluates whether a `vector<FROM>` and be reinterpreted as a `vector<TO>`.
/// Vectors can be reinterpreted if both `FROM` and `TO` are pointers to a type that derives from
/// CastableBase, and the pointee type of `TO` is of the same type as, or is an ancestor of the
/// pointee type of `FROM`. Vectors of non-`const` Castable pointers can be converted to a vector of
/// `const` Castable pointers.
template <typename TO, typename FROM>
static constexpr bool CanReinterpretSlice =
// TO and FROM are both pointer types
std::is_pointer_v<TO> && std::is_pointer_v<FROM> && //
// const can only be applied, not removed
(std::is_const_v<std::remove_pointer_t<TO>> ||
!std::is_const_v<std::remove_pointer_t<FROM>>)&& //
// TO and FROM are both Castable
IsCastable<std::remove_pointer_t<FROM>, std::remove_pointer_t<TO>> &&
// FROM is of, or derives from TO
traits::IsTypeOrDerived<std::remove_pointer_t<FROM>, std::remove_pointer_t<TO>>;
/// Reinterprets `const Slice<FROM>*` as `const Slice<TO>*`
/// @param slice a pointer to the slice to reinterpret
/// @returns the reinterpreted slice
/// @see CanReinterpretSlice
template <typename TO, typename FROM>
const Slice<TO>* ReinterpretSlice(const Slice<FROM>* slice) {
static_assert(CanReinterpretSlice<TO, FROM>);
return Bitcast<const Slice<TO>*>(slice);
}
/// Reinterprets `Slice<FROM>*` as `Slice<TO>*`
/// @param slice a pointer to the slice to reinterpret
/// @returns the reinterpreted slice
/// @see CanReinterpretSlice
template <typename TO, typename FROM>
Slice<TO>* ReinterpretSlice(Slice<FROM>* slice) {
static_assert(CanReinterpretSlice<TO, FROM>);
return Bitcast<Slice<TO>*>(slice);
}
/// Vector is a small-object-optimized, dynamically-sized vector of contigious elements of type T.
///
@ -118,39 +153,20 @@ struct Slice {
/// array'. This reduces memory copying, but may incur additional memory usage.
/// * Resizing, or popping elements from a vector that has spilled to a heap allocation does not
/// revert back to using the 'small array'. Again, this is to reduce memory copying.
template <typename T, size_t N = 0>
template <typename T, size_t N>
class Vector {
public:
/// Type of `T`.
using value_type = T;
/// Value of `N`
static constexpr size_t static_length = N;
/// Constructor
Vector() = default;
/// Constructor
/// @param length the initial length of the vector. Elements will be zero-initialized.
explicit Vector(size_t length) {
Reserve(length);
for (size_t i = 0; i < length; i++) {
new (&impl_.slice.data[i]) T{};
}
impl_.slice.len = length;
}
/// Constructor
/// @param length the initial length of the vector
/// @param value the value to copy into each element of the vector
Vector(size_t length, const T& value) {
Reserve(length);
for (size_t i = 0; i < length; i++) {
new (&impl_.slice.data[i]) T{value};
}
impl_.slice.len = length;
}
/// Constructor
/// @param elements the elements to place into the vector
explicit Vector(std::initializer_list<T> elements) {
Vector(std::initializer_list<T> elements) {
Reserve(elements.size());
for (auto& el : elements) {
new (&impl_.slice.data[impl_.slice.len++]) T{el};
@ -179,17 +195,32 @@ class Vector {
MoveOrCopy(VectorRef<T>(std::move(other)));
}
/// Copy constructor with covariance / const conversion
/// @param other the vector to copy
/// @see CanReinterpretSlice for rules about conversion
template <typename U, size_t N2, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
Vector(const Vector<U, N2>& other) { // NOLINT(runtime/explicit)
Copy(*ReinterpretSlice<T>(&other.impl_.slice));
}
/// Move constructor with covariance / const conversion
/// @param other the vector to move
/// @see CanReinterpretSlice for rules about conversion
template <typename U, size_t N2, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
Vector(Vector<U, N2>&& other) { // NOLINT(runtime/explicit)
MoveOrCopy(VectorRef<T>(std::move(other)));
}
/// Move constructor from a mutable vector reference
/// @param other the vector reference to move
Vector(VectorRef<T>&& other) { // NOLINT(runtime/explicit)
MoveOrCopy(std::move(other));
}
explicit Vector(VectorRef<T>&& other) { MoveOrCopy(std::move(other)); }
/// Copy constructor from an immutable vector reference
/// @param other the vector reference to copy
Vector(const ConstVectorRef<T>& other) { // NOLINT(runtime/explicit)
Copy(other.slice_);
}
explicit Vector(const ConstVectorRef<T>& other) { Copy(other.slice_); }
/// Move constructor from an immutable vector reference (invalid)
Vector(ConstVectorRef<T>&&) = delete; // NOLINT(runtime/explicit)
/// Destructor
~Vector() { ClearAndFree(); }
@ -277,6 +308,20 @@ class Vector {
impl_.slice.len = new_len;
}
/// Resizes the vector to the given length, expanding capacity if necessary.
/// @param new_len the new vector length
/// @param value the value to copy into the new elements
void Resize(size_t new_len, const T& value) {
Reserve(new_len);
for (size_t i = impl_.slice.len; i > new_len; i--) { // Shrink
impl_.slice.data[i - 1].~T();
}
for (size_t i = impl_.slice.len; i < new_len; i++) { // Grow
new (&impl_.slice.data[i]) T{value};
}
impl_.slice.len = new_len;
}
/// Copies all the elements from `other` to this vector, replacing the content of this vector.
/// @param other the
template <typename T2, size_t N2>
@ -317,7 +362,7 @@ class Vector {
if (impl_.slice.len >= impl_.slice.cap) {
Grow();
}
new (&impl_.slice.data[impl_.slice.len++]) T(std::forward<ARGS>(args)...);
new (&impl_.slice.data[impl_.slice.len++]) T{std::forward<ARGS>(args)...};
}
/// Removes and returns the last element from the vector.
@ -377,7 +422,12 @@ class Vector {
friend class ConstVectorRef;
/// The slice type used by this vector
using Slice = detail::Slice<T>;
using Slice = utils::Slice<T>;
template <typename... Ts>
void AppendVariadic(Ts&&... args) {
((new (&impl_.slice.data[impl_.slice.len++]) T(std::forward<Ts>(args))), ...);
}
/// Expands the capacity of the vector
void Grow() { Reserve(impl_.slice.cap * 2); }
@ -484,6 +534,43 @@ class Vector {
std::conditional_t<HasSmallArray, ImplWithSmallArray, ImplWithoutSmallArray> impl_;
};
namespace detail {
/// Helper for determining the Vector element type (`T`) from the vector's constuctor arguments
/// @tparam IS_CASTABLE true if the types of `Ts` derive from CastableBase
/// @tparam Ts the vector constructor argument types to infer the vector element type from.
template <bool IS_CASTABLE, typename... Ts>
struct VectorCommonType;
/// VectorCommonType specialization for non-castable types.
template <typename... Ts>
struct VectorCommonType</*IS_CASTABLE*/ false, Ts...> {
/// The common T type to use for the vector
using type = std::common_type_t<Ts...>;
};
/// VectorCommonType specialization for castable types.
template <typename... Ts>
struct VectorCommonType</*IS_CASTABLE*/ true, Ts...> {
/// The common Castable type (excluding pointer)
using common_ty = CastableCommonBase<std::remove_pointer_t<Ts>...>;
/// The common T type to use for the vector
using type = std::conditional_t<(std::is_const_v<std::remove_pointer_t<Ts>> || ...),
const common_ty*,
common_ty*>;
};
} // namespace detail
/// Helper for determining the Vector element type (`T`) from the vector's constuctor arguments
template <typename... Ts>
using VectorCommonType =
typename detail::VectorCommonType<IsCastable<std::remove_pointer_t<Ts>...>, Ts...>::type;
/// Deduction guide for Vector
template <typename... Ts>
Vector(Ts...) -> Vector<VectorCommonType<Ts...>, sizeof...(Ts)>;
/// VectorRef is a weak reference to a Vector, used to pass vectors as parameters, avoiding copies
/// between the caller and the callee. VectorRef can accept a Vector of any 'N' value, decoupling
/// the caller's vector internal size from the callee's vector size.
@ -507,16 +594,16 @@ class Vector {
template <typename T>
class VectorRef {
/// The slice type used by this vector reference
using Slice = detail::Slice<T>;
using Slice = utils::Slice<T>;
public:
/// Constructor from a Vector.
/// @param vector the vector reference
/// Constructor from a Vector
/// @param vector the vector to create a reference of
template <size_t N>
VectorRef(Vector<T, N>& vector) // NOLINT(runtime/explicit)
: slice_(vector.impl_.slice), can_move_(false) {}
/// Constructor from a std::move()'d Vector
/// Constructor from a moved Vector
/// @param vector the vector being moved
template <size_t N>
VectorRef(Vector<T, N>&& vector) // NOLINT(runtime/explicit)
@ -530,6 +617,32 @@ class VectorRef {
/// @param other the vector reference
VectorRef(VectorRef&& other) = default;
/// Copy constructor with covariance / const conversion
/// @param other the other vector reference
template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
VectorRef(const VectorRef<U>& other) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&other.slice_)), can_move_(false) {}
/// Move constructor with covariance / const conversion
/// @param other the vector reference
template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
VectorRef(VectorRef<U>&& other) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&other.slice_)), can_move_(other.can_move_) {}
/// Constructor from a Vector with covariance / const conversion
/// @param vector the vector to create a reference of
/// @see CanReinterpretSlice for rules about conversion
template <typename U, size_t N, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
VectorRef(Vector<U, N>& vector) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&vector.impl_.slice)), can_move_(false) {}
/// Constructor from a moved Vector with covariance / const conversion
/// @param vector the vector to create a reference of
/// @see CanReinterpretSlice for rules about conversion
template <typename U, size_t N, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
VectorRef(Vector<U, N>&& vector) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&vector.impl_.slice)), can_move_(vector.impl_.CanMove()) {}
/// Index operator
/// @param i the element index. Must be less than `len`.
/// @returns a reference to the i'th element.
@ -587,10 +700,18 @@ class VectorRef {
auto rend() const { return slice_.rend(); }
private:
/// Friend classes
/// Friend class
template <typename, size_t>
friend class Vector;
/// Friend class
template <typename>
friend class VectorRef;
/// Friend class
template <typename>
friend class ConstVectorRef;
/// The slice of the vector being referenced.
Slice& slice_;
/// Whether the slice data is passed by r-value reference, and can be moved.
@ -603,7 +724,7 @@ class VectorRef {
template <typename T>
class ConstVectorRef {
/// The slice type used by this vector reference
using Slice = detail::Slice<T>;
using Slice = utils::Slice<T>;
public:
/// Constructor from a Vector.
@ -616,6 +737,34 @@ class ConstVectorRef {
/// @param other the vector reference
ConstVectorRef(const ConstVectorRef& other) = default;
/// Conversion constructor to convert from a non-const to const vector reference
/// @param other the vector reference
ConstVectorRef(const VectorRef<T>& other) : slice_(other.slice_) {} // NOLINT(runtime/explicit)
/// Move constructor. Deleted as this won't move anything.
ConstVectorRef(ConstVectorRef&&) = delete;
/// Constructor from a Vector with covariance / const conversion
/// @param vector the vector to create a reference of
/// @see CanReinterpretSlice for rules about conversion
template <typename U, size_t N, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
ConstVectorRef(const Vector<U, N>& vector) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&vector.impl_.slice)) {}
/// Constructor from a VectorRef with covariance / const conversion
/// @param other the vector reference
/// @see CanReinterpretSlice for rules about conversion
template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
ConstVectorRef(const VectorRef<U>& other) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&other.slice_)) {}
/// Constructor from a ConstVectorRef with covariance / const conversion
/// @param other the vector reference
/// @see CanReinterpretSlice for rules about conversion
template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>>
ConstVectorRef(const ConstVectorRef<U>& other) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&other.slice_)) {}
/// Index operator
/// @param i the element index. Must be less than `len`.
/// @returns a reference to the i'th element.
@ -650,10 +799,14 @@ class ConstVectorRef {
auto rend() const { return slice_.rend(); }
private:
/// Friend classes
/// Friend class
template <typename, size_t>
friend class Vector;
/// Friend class
template <typename>
friend class ConstVectorRef;
/// The slice of the vector being referenced.
const Slice& slice_;
};
@ -682,14 +835,6 @@ Vector<T, N> ToVector(const std::vector<T>& vector) {
return out;
}
/// Helper for constructing a Vector from a set of elements.
/// The returned Vector's small-array size (`N`) is equal to the number of provided elements.
/// @param elements the elements used to construct the vector.
template <typename T, typename... Ts>
auto MakeVector(Ts&&... elements) {
return Vector<T, sizeof...(Ts)>({std::forward<Ts>(elements)...});
}
} // namespace tint::utils
#endif // SRC_TINT_UTILS_VECTOR_H_

File diff suppressed because it is too large Load Diff