[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;