tint/utils: Add support for unsafe pointer downcasts

Bug: tint:1779
Change-Id: Icfd27680edf7dfaedbfb70f25641dc762d23f42a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/113020
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
Ben Clayton 2022-12-06 15:43:40 +00:00 committed by Dawn LUCI CQ
parent 4c5a9c1ef7
commit a31d89d6a3
2 changed files with 94 additions and 38 deletions

View File

@ -106,12 +106,20 @@ struct Slice {
auto rend() const { return std::reverse_iterator<const T*>(begin()); } auto rend() const { return std::reverse_iterator<const T*>(begin()); }
}; };
/// Mode enumerator for ReinterpretSlice
enum class ReinterpretMode {
/// Only upcasts of pointers are permitted
kSafe,
/// Potentially unsafe downcasts of pointers are also permitted
kUnsafe,
};
namespace detail { namespace detail {
/// Private implementation of tint::utils::CanReinterpretSlice. /// Private implementation of tint::utils::CanReinterpretSlice.
/// Specialized for the case of TO equal to FROM, which is the common case, and avoids inspection of /// Specialized for the case of TO equal to FROM, which is the common case, and avoids inspection of
/// the base classes, which can be troublesome if the slice is of an incomplete type. /// the base classes, which can be troublesome if the slice is of an incomplete type.
template <typename TO, typename FROM> template <ReinterpretMode MODE, typename TO, typename FROM>
struct CanReinterpretSlice { struct CanReinterpretSlice {
/// True if a slice of FROM can be reinterpreted as a slice of TO /// True if a slice of FROM can be reinterpreted as a slice of TO
static constexpr bool value = static constexpr bool value =
@ -122,13 +130,14 @@ struct CanReinterpretSlice {
!std::is_const_v<std::remove_pointer_t<FROM>>)&& // !std::is_const_v<std::remove_pointer_t<FROM>>)&& //
// TO and FROM are both Castable // TO and FROM are both Castable
IsCastable<std::remove_pointer_t<FROM>, std::remove_pointer_t<TO>> && // IsCastable<std::remove_pointer_t<FROM>, std::remove_pointer_t<TO>> && //
// FROM is of, or derives from TO // MODE is kUnsafe, or FROM is of, or derives from TO
traits::IsTypeOrDerived<std::remove_pointer_t<FROM>, std::remove_pointer_t<TO>>; (MODE == ReinterpretMode::kUnsafe ||
traits::IsTypeOrDerived<std::remove_pointer_t<FROM>, std::remove_pointer_t<TO>>);
}; };
/// Specialization of 'CanReinterpretSlice' for when TO and FROM are equal types. /// Specialization of 'CanReinterpretSlice' for when TO and FROM are equal types.
template <typename T> template <typename T, ReinterpretMode MODE>
struct CanReinterpretSlice<T, T> { struct CanReinterpretSlice<MODE, T, T> {
/// Always `true` as TO and FROM are the same type. /// Always `true` as TO and FROM are the same type.
static constexpr bool value = true; static constexpr bool value = true;
}; };
@ -140,16 +149,16 @@ struct CanReinterpretSlice<T, T> {
/// CastableBase, and the pointee type of `TO` is of the same type as, or is an ancestor of the /// 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 /// pointee type of `FROM`. Vectors of non-`const` Castable pointers can be converted to a vector of
/// `const` Castable pointers. /// `const` Castable pointers.
template <typename TO, typename FROM> template <ReinterpretMode MODE, typename TO, typename FROM>
static constexpr bool CanReinterpretSlice = detail::CanReinterpretSlice<TO, FROM>::value; static constexpr bool CanReinterpretSlice = detail::CanReinterpretSlice<MODE, TO, FROM>::value;
/// Reinterprets `const Slice<FROM>*` as `const Slice<TO>*` /// Reinterprets `const Slice<FROM>*` as `const Slice<TO>*`
/// @param slice a pointer to the slice to reinterpret /// @param slice a pointer to the slice to reinterpret
/// @returns the reinterpreted slice /// @returns the reinterpreted slice
/// @see CanReinterpretSlice /// @see CanReinterpretSlice
template <typename TO, typename FROM> template <ReinterpretMode MODE, typename TO, typename FROM>
const Slice<TO>* ReinterpretSlice(const Slice<FROM>* slice) { const Slice<TO>* ReinterpretSlice(const Slice<FROM>* slice) {
static_assert(CanReinterpretSlice<TO, FROM>); static_assert(CanReinterpretSlice<MODE, TO, FROM>);
return Bitcast<const Slice<TO>*>(slice); return Bitcast<const Slice<TO>*>(slice);
} }
@ -157,9 +166,9 @@ const Slice<TO>* ReinterpretSlice(const Slice<FROM>* slice) {
/// @param slice a pointer to the slice to reinterpret /// @param slice a pointer to the slice to reinterpret
/// @returns the reinterpreted slice /// @returns the reinterpreted slice
/// @see CanReinterpretSlice /// @see CanReinterpretSlice
template <typename TO, typename FROM> template <ReinterpretMode MODE, typename TO, typename FROM>
Slice<TO>* ReinterpretSlice(Slice<FROM>* slice) { Slice<TO>* ReinterpretSlice(Slice<FROM>* slice) {
static_assert(CanReinterpretSlice<TO, FROM>); static_assert(CanReinterpretSlice<MODE, TO, FROM>);
return Bitcast<Slice<TO>*>(slice); return Bitcast<Slice<TO>*>(slice);
} }
@ -230,15 +239,21 @@ class Vector {
/// Copy constructor with covariance / const conversion /// Copy constructor with covariance / const conversion
/// @param other the vector to copy /// @param other the vector to copy
/// @see CanReinterpretSlice for rules about conversion /// @see CanReinterpretSlice for rules about conversion
template <typename U, size_t N2, typename = std::enable_if_t<CanReinterpretSlice<T, U>>> template <typename U,
size_t N2,
ReinterpretMode MODE,
typename = std::enable_if_t<CanReinterpretSlice<MODE, T, U>>>
Vector(const Vector<U, N2>& other) { // NOLINT(runtime/explicit) Vector(const Vector<U, N2>& other) { // NOLINT(runtime/explicit)
Copy(*ReinterpretSlice<T>(&other.impl_.slice)); Copy(*ReinterpretSlice<MODE, T>(&other.impl_.slice));
} }
/// Move constructor with covariance / const conversion /// Move constructor with covariance / const conversion
/// @param other the vector to move /// @param other the vector to move
/// @see CanReinterpretSlice for rules about conversion /// @see CanReinterpretSlice for rules about conversion
template <typename U, size_t N2, typename = std::enable_if_t<CanReinterpretSlice<T, U>>> template <typename U,
size_t N2,
ReinterpretMode MODE,
typename = std::enable_if_t<CanReinterpretSlice<MODE, T, U>>>
Vector(Vector<U, N2>&& other) { // NOLINT(runtime/explicit) Vector(Vector<U, N2>&& other) { // NOLINT(runtime/explicit)
MoveOrCopy(VectorRef<T>(std::move(other))); MoveOrCopy(VectorRef<T>(std::move(other)));
} }
@ -701,6 +716,11 @@ class VectorRef {
/// Constructor /// Constructor
VectorRef(EmptyType) : slice_(EmptySlice()) {} // NOLINT(runtime/explicit) VectorRef(EmptyType) : slice_(EmptySlice()) {} // NOLINT(runtime/explicit)
/// Constructor from a Slice
/// @param slice the slice
VectorRef(Slice& slice) // NOLINT(runtime/explicit)
: slice_(slice) {}
/// Constructor from a Vector /// Constructor from a Vector
/// @param vector the vector to create a reference of /// @param vector the vector to create a reference of
template <size_t N> template <size_t N>
@ -729,29 +749,37 @@ class VectorRef {
/// Copy constructor with covariance / const conversion /// Copy constructor with covariance / const conversion
/// @param other the other vector reference /// @param other the other vector reference
template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>> template <typename U,
typename = std::enable_if_t<CanReinterpretSlice<ReinterpretMode::kSafe, T, U>>>
VectorRef(const VectorRef<U>& other) // NOLINT(runtime/explicit) VectorRef(const VectorRef<U>& other) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&other.slice_)) {} : slice_(*ReinterpretSlice<ReinterpretMode::kSafe, T>(&other.slice_)) {}
/// Move constructor with covariance / const conversion /// Move constructor with covariance / const conversion
/// @param other the vector reference /// @param other the vector reference
template <typename U, typename = std::enable_if_t<CanReinterpretSlice<T, U>>> template <typename U,
typename = std::enable_if_t<CanReinterpretSlice<ReinterpretMode::kSafe, T, U>>>
VectorRef(VectorRef<U>&& other) // NOLINT(runtime/explicit) VectorRef(VectorRef<U>&& other) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&other.slice_)), can_move_(other.can_move_) {} : slice_(*ReinterpretSlice<ReinterpretMode::kSafe, T>(&other.slice_)),
can_move_(other.can_move_) {}
/// Constructor from a Vector with covariance / const conversion /// Constructor from a Vector with covariance / const conversion
/// @param vector the vector to create a reference of /// @param vector the vector to create a reference of
/// @see CanReinterpretSlice for rules about conversion /// @see CanReinterpretSlice for rules about conversion
template <typename U, size_t N, typename = std::enable_if_t<CanReinterpretSlice<T, U>>> template <typename U,
size_t N,
typename = std::enable_if_t<CanReinterpretSlice<ReinterpretMode::kSafe, T, U>>>
VectorRef(Vector<U, N>& vector) // NOLINT(runtime/explicit) VectorRef(Vector<U, N>& vector) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&vector.impl_.slice)) {} : slice_(*ReinterpretSlice<ReinterpretMode::kSafe, T>(&vector.impl_.slice)) {}
/// Constructor from a moved Vector with covariance / const conversion /// Constructor from a moved Vector with covariance / const conversion
/// @param vector the vector to create a reference of /// @param vector the vector to create a reference of
/// @see CanReinterpretSlice for rules about conversion /// @see CanReinterpretSlice for rules about conversion
template <typename U, size_t N, typename = std::enable_if_t<CanReinterpretSlice<T, U>>> template <typename U,
size_t N,
typename = std::enable_if_t<CanReinterpretSlice<ReinterpretMode::kSafe, T, U>>>
VectorRef(Vector<U, N>&& vector) // NOLINT(runtime/explicit) VectorRef(Vector<U, N>&& vector) // NOLINT(runtime/explicit)
: slice_(*ReinterpretSlice<T>(&vector.impl_.slice)), can_move_(vector.impl_.CanMove()) {} : slice_(*ReinterpretSlice<ReinterpretMode::kSafe, T>(&vector.impl_.slice)),
can_move_(vector.impl_.CanMove()) {}
/// Index operator /// Index operator
/// @param i the element index. Must be less than `len`. /// @param i the element index. Must be less than `len`.
@ -765,6 +793,14 @@ class VectorRef {
/// be made /// be made
size_t Capacity() const { return slice_.cap; } size_t Capacity() const { return slice_.cap; }
/// @return a reinterpretation of this VectorRef as elements of type U.
/// @note this is doing a reinterpret_cast of elements. It is up to the caller to ensure that
/// this is a safe operation.
template <typename U>
VectorRef<U> ReinterpretCast() const {
return {*ReinterpretSlice<ReinterpretMode::kUnsafe, U>(&slice_)};
}
/// @returns true if the vector is empty. /// @returns true if the vector is empty.
bool IsEmpty() const { return slice_.len == 0; } bool IsEmpty() const { return slice_.len == 0; }

View File

@ -79,22 +79,30 @@ static_assert(std::is_same_v<VectorCommonType<const C2a*, C2b*>, const C1*>);
static_assert(std::is_same_v<VectorCommonType<C2a*, const C2b*>, const C1*>); static_assert(std::is_same_v<VectorCommonType<C2a*, const C2b*>, const C1*>);
static_assert(std::is_same_v<VectorCommonType<const C2a*, const C2b*>, const C1*>); static_assert(std::is_same_v<VectorCommonType<const C2a*, const C2b*>, const C1*>);
static_assert(CanReinterpretSlice<const C0*, C0*>, "apply const"); static_assert(CanReinterpretSlice<ReinterpretMode::kSafe, const C0*, C0*>, "apply const");
static_assert(!CanReinterpretSlice<C0*, const C0*>, "remove const"); static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C0*, const C0*>, "remove const");
static_assert(CanReinterpretSlice<C0*, C1*>, "up cast"); static_assert(CanReinterpretSlice<ReinterpretMode::kSafe, C0*, C1*>, "up cast");
static_assert(CanReinterpretSlice<const C0*, const C1*>, "up cast"); static_assert(CanReinterpretSlice<ReinterpretMode::kSafe, const C0*, const C1*>, "up cast");
static_assert(CanReinterpretSlice<const C0*, C1*>, "up cast, apply const"); static_assert(CanReinterpretSlice<ReinterpretMode::kSafe, const C0*, C1*>, "up cast, apply const");
static_assert(!CanReinterpretSlice<C0*, const C1*>, "up cast, remove const"); static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C0*, const C1*>,
static_assert(!CanReinterpretSlice<C1*, C0*>, "down cast"); "up cast, remove const");
static_assert(!CanReinterpretSlice<const C1*, const C0*>, "down cast"); static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C1*, C0*>, "down cast");
static_assert(!CanReinterpretSlice<const C1*, C0*>, "down cast, apply const"); static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, const C1*, const C0*>, "down cast");
static_assert(!CanReinterpretSlice<C1*, const C0*>, "down cast, remove const"); static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, const C1*, C0*>,
static_assert(!CanReinterpretSlice<const C1*, C0*>, "down cast, apply const"); "down cast, apply const");
static_assert(!CanReinterpretSlice<C1*, const C0*>, "down cast, remove const"); static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C1*, const C0*>,
static_assert(!CanReinterpretSlice<C2a*, C2b*>, "sideways cast"); "down cast, remove const");
static_assert(!CanReinterpretSlice<const C2a*, const C2b*>, "sideways cast"); static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, const C1*, C0*>,
static_assert(!CanReinterpretSlice<const C2a*, C2b*>, "sideways cast, apply const"); "down cast, apply const");
static_assert(!CanReinterpretSlice<C2a*, const C2b*>, "sideways cast, remove const"); static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C1*, const C0*>,
"down cast, remove const");
static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C2a*, C2b*>, "sideways cast");
static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, const C2a*, const C2b*>,
"sideways cast");
static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, const C2a*, C2b*>,
"sideways cast, apply const");
static_assert(!CanReinterpretSlice<ReinterpretMode::kSafe, C2a*, const C2b*>,
"sideways cast, remove const");
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// TintVectorTest // TintVectorTest
@ -2001,6 +2009,18 @@ TEST(TintVectorRefTest, MoveVector_UpcastAndAddConst) {
EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied EXPECT_TRUE(AllExternallyHeld(vec_b)); // Moved, not copied
} }
TEST(TintVectorRefTest, MoveVector_ReinterpretCast) {
C2a c2a;
C2b c2b;
Vector<C0*, 1> vec_a{&c2a, &c2b};
VectorRef<const C0*> vec_ref(std::move(vec_a)); // Move
EXPECT_EQ(vec_ref[0], &c2a);
EXPECT_EQ(vec_ref[1], &c2b);
VectorRef<const C1*> reinterpret = vec_ref.ReinterpretCast<const C1*>();
EXPECT_EQ(reinterpret[0], &c2a);
EXPECT_EQ(reinterpret[1], &c2b);
}
TEST(TintVectorRefTest, Index) { TEST(TintVectorRefTest, Index) {
Vector<std::string, 2> vec{"one", "two"}; Vector<std::string, 2> vec{"one", "two"};
VectorRef<std::string> vec_ref(vec); VectorRef<std::string> vec_ref(vec);