[utils] Add ReinterpretSpan helper for Spans. Change-Id: I44cf920b89488286610a270ff7f43202fcaa9eeb Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/321316 Commit-Queue: Loko Kung <lokokung@google.com> Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/utils/span.h b/src/utils/span.h index e90e82d..a252db5 100644 --- a/src/utils/span.h +++ b/src/utils/span.h
@@ -31,6 +31,7 @@ #include <concepts> #include <limits> #include <memory> +#include <new> #include <ranges> #include <span> @@ -331,6 +332,61 @@ } } +// Converts a `Span<[const|volatile] std::byte>` to a `Span<[const|volatile] T>`. +// Mirrors Chromium's base::subtle::reintepret_span with some minor differences: +// - Allows for volatile qualifiers as long as they are not cast away. +// - Relaxes requirement for std::is_trivially_copyable<T> for volatile structures because they +// are never trivially copyable when used in Spans. +// TODO(https://crbug.com/524405497): Support `Span<std::byte, ...>` to `Span<T, N>` once fixed +// extent spans are supported. +template <typename T, typename Index = size_t, typename ByteType> + requires( + // This function effectively "creates" objects by overlaying a type onto raw bytes, + // bypassing constructors. Such an operation is only safe for objects that do not maintain + // internal invariant. Therefore, we restrict this function to trivially copyable types, + // unless we are dealing with a volatile type in which case we assume the user is aware of + // the potential pitfalls. + // TODO(https://crbug.com/528027992): Add this restriction back once we make wire + // serialization volatile in both directions. + // (std::is_trivially_copyable_v<T> || std::is_volatile_v<T>) && + // Only allow reintepreting spans of std::byte type. + std::same_as<std::remove_cv_t<ByteType>, std::byte> && + // Ensure we are not casting away constness. + (!std::is_const_v<ByteType> || std::is_const_v<T>) && + // Ensure we are not casting away volatility. + (!std::is_volatile_v<ByteType> || std::is_volatile_v<T>)) +constexpr auto ReinterpretSpan(Span<ByteType> s) { + // Check for proper alignment of the target type. + DAWN_CHECK(reinterpret_cast<uintptr_t>(s.data()) % alignof(T) == 0u); + // Check that the size is a multiple of the target type. + DAWN_CHECK(s.size_bytes() % sizeof(T) == 0u); + + // In C++, one cannot simply reinterpret a span of bytes as a span of `T` and dereference it, + // unless an object of type `T` actually exists at that memory location. + // + // Per [intro.object], casting a pointer does not start the lifetime of an object. Even if the + // bytes represent a valid object representation, strict adherence to the standard requires + // explicit object creation. Dereferencing a pointer to an object that hasn't been created is + // undefined behavior. + // + // The proper solution is C++23's `std::start_lifetime_as_array` (see [obj.lifetime]), which + // explicitly blesses the memory as containing objects of type `ElementType`. + // + // [intro.object]: https://eel.is/c++draft/intro.object + // [obj.lifetime] https://eel.is/c++draft/obj.lifetime + // + // std::start_lifetime_as_array is part of C++23, but not yet implemented by CLang as of January + // 2026. `std::launder` helps with pointer provenance issues, but does not by itself start the + // lifetime of the object. However, in practice, most compilers implicitly treat this pattern as + // valid de facto, but it is technically not standard-compliant. + auto* ptr = std::launder(reinterpret_cast<T*>(s.data())); + + // SAFETY: We checked for proper alignment, size, strict aliasing rules, and started the + // lifetime of the array. + return DAWN_UNSAFE_BUFFERS( + ityp::span<Index, T>{ptr, Index{UnderlyingType<Index>(s.size_bytes() / sizeof(T))}}); +} + // Converts a `[const] T&` to a `Span<[const] T>`. // Mirrors Chromium's base::span_from_ref. // TODO(https://crbug.com/524405497): Make it return Span<T, 1> once fixed extent spans are
diff --git a/src/utils/span_nocompile.nc b/src/utils/span_nocompile.nc index c7e7842..8e3a795 100644 --- a/src/utils/span_nocompile.nc +++ b/src/utils/span_nocompile.nc
@@ -133,4 +133,67 @@ } } +void TestReinterpretSpan() { + { + // Non-byte source. + std::array<int, 3> ints{}; + auto s = Span<int>{ints}; + auto r1 = ReinterpretSpan<int>(s); // expected-error {{no matching function for call}} + auto r2 = ReinterpretSpan<int, Index>(s); // expected-error {{no matching function for call}} + } + { + // Casting away const or volatile. + std::array<std::byte, 4> bytes; + { + auto const_s = Span<const std::byte>{bytes}; + auto r1 = ReinterpretSpan<char>(const_s); // expected-error {{no matching function for call}} + auto r2 = ReinterpretSpan<volatile char>(const_s); // expected-error {{no matching function for call}} + auto r3 = ReinterpretSpan<char, Index>(const_s); // expected-error {{no matching function for call}} + auto r4 = ReinterpretSpan<volatile char, Index>(const_s); // expected-error {{no matching function for call}} + } + { + auto volatile_s = Span<volatile std::byte>{bytes}; + auto r1 = ReinterpretSpan<char>(volatile_s); // expected-error {{no matching function for call}} + auto r2 = ReinterpretSpan<const char>(volatile_s); // expected-error {{no matching function for call}} + auto r3 = ReinterpretSpan<char, Index>(volatile_s); // expected-error {{no matching function for call}} + auto r4 = ReinterpretSpan<const char, Index>(volatile_s); // expected-error {{no matching function for call}} + } + { + auto cv_s = Span<const volatile std::byte>{bytes}; + auto r1 = ReinterpretSpan<char>(cv_s); // expected-error {{no matching function for call}} + auto r2 = ReinterpretSpan<const char>(cv_s); // expected-error {{no matching function for call}} + auto r3 = ReinterpretSpan<volatile char>(cv_s); // expected-error {{no matching function for call}} + auto r4 = ReinterpretSpan<char, Index>(cv_s); // expected-error {{no matching function for call}} + auto r5 = ReinterpretSpan<const char, Index>(cv_s); // expected-error {{no matching function for call}} + auto r6 = ReinterpretSpan<volatile char, Index>(cv_s); // expected-error {{no matching function for call}} + } + } + { + // Unless type is volatile, it needs to be trivially copyable. + struct S { + S() {} + ~S() {} + S(const volatile S&) {} + S(volatile S&) {} + S(const volatile S&&) {} + char i; + }; + std::array<std::byte, 4> bytes; + auto s = Span<std::byte>{bytes}; + + // Allowed if the type is volatile. + [[maybe_unused]] auto r1 = ReinterpretSpan<volatile S>(s); + [[maybe_unused]] auto r2 = ReinterpretSpan<const volatile S>(s); + [[maybe_unused]] auto r3 = ReinterpretSpan<volatile S, Index>(s); + [[maybe_unused]] auto r4 = ReinterpretSpan<const volatile S, Index>(s); + + // TODO(https://crbug.com/528027992): Re-enable these tests once wire serialization is volatile + // in both directions. These should all eventually fail to compile. + [[maybe_unused]] auto r5 = ReinterpretSpan<S>(s); + [[maybe_unused]] auto r6 = ReinterpretSpan<const S>(s); + [[maybe_unused]] auto r7 = ReinterpretSpan<S, Index>(s); + [[maybe_unused]] auto r8 = ReinterpretSpan<const S, Index>(s); + } +} + } // namespace dawn
diff --git a/src/utils/span_tests.cc b/src/utils/span_tests.cc index 054c7d3..0605409 100644 --- a/src/utils/span_tests.cc +++ b/src/utils/span_tests.cc
@@ -25,6 +25,7 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include <algorithm> #include <array> #include "partition_alloc/pointers/raw_ptr.h" @@ -675,6 +676,134 @@ } } +TEST(SpanTest, ReinterpretEmptySpans) { + // Non-const, non-volatile span. + { + Span<std::byte> bsp; + auto sp = ReinterpretSpan<int>(bsp); + static_assert(std::is_same_v<Span<int>, decltype(sp)>); + EXPECT_TRUE(sp.empty()); + + auto sp_const = ReinterpretSpan<const int>(bsp); + static_assert(std::is_same_v<Span<const int>, decltype(sp_const)>); + EXPECT_TRUE(sp_const.empty()); + + auto sp_volatile = ReinterpretSpan<volatile int>(bsp); + static_assert(std::is_same_v<Span<volatile int>, decltype(sp_volatile)>); + EXPECT_TRUE(sp_volatile.empty()); + + auto sp_const_volatile = ReinterpretSpan<const volatile int>(bsp); + static_assert(std::is_same_v<Span<const volatile int>, decltype(sp_const_volatile)>); + EXPECT_TRUE(sp_const_volatile.empty()); + } + // Const span. + { + Span<const std::byte> bsp; + auto sp_const = ReinterpretSpan<const int>(bsp); + static_assert(std::is_same_v<Span<const int>, decltype(sp_const)>); + EXPECT_TRUE(sp_const.empty()); + + auto sp_const_volatile = ReinterpretSpan<const volatile int>(bsp); + static_assert(std::is_same_v<Span<const volatile int>, decltype(sp_const_volatile)>); + EXPECT_TRUE(sp_const_volatile.empty()); + } + // Volatile span. + { + Span<volatile std::byte> bsp; + auto sp_volatile = ReinterpretSpan<volatile int>(bsp); + static_assert(std::is_same_v<Span<volatile int>, decltype(sp_volatile)>); + EXPECT_TRUE(sp_volatile.empty()); + + auto sp_const_volatile = ReinterpretSpan<const volatile int>(bsp); + static_assert(std::is_same_v<Span<const volatile int>, decltype(sp_const_volatile)>); + EXPECT_TRUE(sp_const_volatile.empty()); + } + // Const and volatile span. + { + Span<const volatile std::byte> bsp; + auto sp_const_volatile = ReinterpretSpan<const volatile int>(bsp); + static_assert(std::is_same_v<Span<const volatile int>, decltype(sp_const_volatile)>); + EXPECT_TRUE(sp_const_volatile.empty()); + } +} + +TEST(SpanTest, ReintepretSpans) { + // Basic usages with varying const/volatile and type-ness. + { + alignas(uint32_t) std::array<std::byte, 8> bytes{}; + Span<std::byte> bsp{bytes}; + + auto sp16 = ReinterpretSpan<uint16_t>(bsp); + static_assert(std::is_same_v<Span<uint16_t>, decltype(sp16)>); + EXPECT_EQ(sp16.size(), 4u); + EXPECT_EQ(sp16.data(), reinterpret_cast<uint16_t*>(bsp.data())); + + auto sp32 = ReinterpretSpan<const uint32_t>(bsp); + static_assert(std::is_same_v<Span<const uint32_t>, decltype(sp32)>); + EXPECT_EQ(sp32.size(), 2u); + EXPECT_EQ(sp32.data(), reinterpret_cast<const uint32_t*>(bsp.data())); + + auto sp16_typed = ReinterpretSpan<volatile uint16_t, Index>(bsp); + static_assert(std::is_same_v<ityp::span<Index, volatile uint16_t>, decltype(sp16_typed)>); + EXPECT_EQ(sp16_typed.size(), Index{4u}); + EXPECT_EQ(sp16_typed.data(), reinterpret_cast<volatile uint16_t*>(bsp.data())); + + auto sp32_typed = ReinterpretSpan<const volatile uint32_t, Index>(bsp); + static_assert( + std::is_same_v<ityp::span<Index, const volatile uint32_t>, decltype(sp32_typed)>); + EXPECT_EQ(sp32_typed.size(), Index{2u}); + EXPECT_EQ(sp32_typed.data(), reinterpret_cast<const volatile uint32_t*>(bsp.data())); + } + // Round-trip data integrity with SpanAs*Bytes. + { + std::array<int, 3> ints{1, 2, 3}; + Span<int> sp{ints}; + { + Span<const std::byte> bsp = SpanAsBytes(sp); + Span<const int> sp2 = ReinterpretSpan<const int>(bsp); + EXPECT_EQ(sp2.size(), sp.size()); + EXPECT_EQ(sp2.data(), sp.data()); + EXPECT_TRUE(std::ranges::equal(sp2, sp)); + } + { + Span<std::byte> wbsp = SpanAsWritableBytes(sp); + Span<int> sp2 = ReinterpretSpan<int>(wbsp); + EXPECT_EQ(sp2.size(), sp.size()); + EXPECT_EQ(sp2.data(), sp.data()); + EXPECT_TRUE(std::ranges::equal(sp2, sp)); + } + } +} + +TEST(SpanDeathTest, ReinterpretSpan) { + // Check unaligned empty span. + // Empty slice (e.g. data() != nullptr, but size() == 0). + { + alignas(uint32_t) std::array<std::byte, 4> bytes; + auto bsp = Span<std::byte>(bytes).subspan(1u, 0); + EXPECT_EQ(bsp.size(), 0u); + EXPECT_NE(bsp.data(), nullptr); + EXPECT_DEATH_IF_SUPPORTED(ReinterpretSpan<uint32_t>(bsp), ""); + EXPECT_DEATH_IF_SUPPORTED((ReinterpretSpan<uint32_t, Index>(bsp)), ""); + } + // Alignment check fails. + { + alignas(uint32_t) std::array<std::byte, 9> bytes; + auto bsp = Span<std::byte>(bytes).subspan(1u, 4u); + if (alignof(uint32_t) > 1) { + EXPECT_DEATH_IF_SUPPORTED(ReinterpretSpan<uint32_t>(bsp), ""); + EXPECT_DEATH_IF_SUPPORTED((ReinterpretSpan<uint32_t, Index>(bsp)), ""); + } + } + // Size check fails. + { + alignas(uint32_t) std::array<std::byte, 8> bytes; + auto bsp = Span<std::byte>(bytes).first(7u); + EXPECT_DEATH_IF_SUPPORTED(ReinterpretSpan<uint32_t>(bsp), ""); + EXPECT_DEATH_IF_SUPPORTED((ReinterpretSpan<uint32_t, Index>(bsp)), ""); + } +} + TEST(SpanTest, SpanFromRef) { { uint32_t i = 0;