| // Copyright 2026 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // 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. | |
| #ifndef SRC_UTILS_SPAN_H_ | |
| #define SRC_UTILS_SPAN_H_ | |
| #include <algorithm> | |
| #include <concepts> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <functional> | |
| #include <limits> | |
| #include <memory> | |
| #include <new> | |
| #include <ranges> | |
| #include <span> | |
| #include <type_traits> | |
| #include <utility> | |
| #include "partition_alloc/pointers/raw_ptr.h" | |
| #include "src/utils/compiler.h" | |
| #include "src/utils/numeric.h" | |
| #include "src/utils/underlying_type.h" | |
| namespace dawn { | |
| // A span is a contiguous view of elements that can be accessed like an array. They are preferred | |
| // over pointers and sizes because they enforce safe usage (in addition to requiring less typing). | |
| // Dawn has its own Span class for multiple reasons: | |
| // | |
| // - It needs a version of std::span that can work with TypedInteger to make it impossible to use a | |
| // span with the wrong kind of index. | |
| // - It needs a version of std::span with a configurable type for the pointer, so that it can be | |
| // changed to a raw_ptr<T> when the span is stored as a member (this is important to keep the | |
| // MiraclePtr refcount exact). | |
| // - It ensures that the layout of Span is exactly (size_t mSize, T* mData) to match webgpu.h | |
| // structures where the count of elements always comes just before the pointer to the elements. | |
| // This is important because conversions between webgpu.h and dawn_platform.h structures is done | |
| // by simply casting the pointer (and checking that the layout matches). dawn_platform.h | |
| // structures use Span so we need to know the exact layout. | |
| // | |
| // The deviations from std::span are marked with DIFF. | |
| namespace detail { | |
| // The equivalent of std::dynamic_extent for the given index type. | |
| template <typename Index> | |
| inline constexpr Index DynamicExtent = std::numeric_limits<Index>::max(); | |
| template <typename Index, Index Extent> | |
| inline constexpr bool IsDynamicExtent = Extent == DynamicExtent<Index>; | |
| template <typename Ptr, typename NewT> | |
| struct ReturnPtrTypeImpl; | |
| template <typename OldT, typename NewT> | |
| struct ReturnPtrTypeImpl<OldT*, NewT> { | |
| using type = NewT*; | |
| }; | |
| template <typename OldT, typename NewT> | |
| struct ReturnPtrTypeImpl<raw_ptr<OldT>, NewT> { | |
| using type = raw_ptr<NewT>; | |
| }; | |
| template <typename Ptr, typename NewT> | |
| using ReturnPtrType = typename ReturnPtrTypeImpl<Ptr, NewT>::type; | |
| template <typename T, | |
| HasUnsignedUnderlyingType Index, | |
| typename PtrType, | |
| Index Extent = DynamicExtent<Index>> | |
| class SpanBase; | |
| } // namespace detail | |
| // A direct replacement for std::span<T> | |
| template <typename T, size_t Extent = detail::DynamicExtent<size_t>> | |
| using Span = detail::SpanBase<T, size_t, T*, Extent>; | |
| template <typename T, size_t Extent = detail::DynamicExtent<size_t>> | |
| using RawSpan = detail::SpanBase<T, size_t, raw_ptr<T>, Extent>; | |
| namespace ityp { | |
| // A replacement for std::span<T> but with a different index type (like a TypedInteger). | |
| template <typename Index, typename T, Index Extent = dawn::detail::DynamicExtent<Index>> | |
| using span = dawn::detail::SpanBase<T, Index, T*, Extent>; | |
| template <typename Index, typename T, Index Extent = dawn::detail::DynamicExtent<Index>> | |
| using raw_span = dawn::detail::SpanBase<T, Index, raw_ptr<T>, Extent>; | |
| } // namespace ityp | |
| } // namespace dawn | |
| // Mark `SpanBase` as satisfying the `view` and `borrowed_range` concepts. | |
| template <typename T, typename Index, typename PtrType, Index Extent> | |
| inline constexpr bool std::ranges::enable_view<dawn::detail::SpanBase<T, Index, PtrType, Extent>> = | |
| true; | |
| template <typename T, typename Index, typename PtrType, Index Extent> | |
| inline constexpr bool | |
| std::ranges::enable_borrowed_range<dawn::detail::SpanBase<T, Index, PtrType, Extent>> = true; | |
| namespace dawn { | |
| namespace detail { | |
| template <typename From, typename To> | |
| concept LegalDataConversion = std::is_convertible_v<From (*)[], To (*)[]>; | |
| template <typename T, typename It> | |
| concept CompatibleIter = std::contiguous_iterator<It> && | |
| LegalDataConversion<std::remove_reference_t<std::iter_reference_t<It>>, T>; | |
| template <typename T, typename Index, typename R> | |
| concept CompatibleRange = | |
| std::ranges::contiguous_range<R> && // | |
| (std::ranges::borrowed_range<R> || std::is_const_v<T>) && | |
| LegalDataConversion<std::remove_reference_t<std::ranges::range_reference_t<R>>, T> && | |
| requires(R r) { | |
| { r.size() } -> std::same_as<Index>; | |
| }; | |
| template <typename T, typename Index, typename R> | |
| concept CompatibleCopySource = | |
| std::ranges::contiguous_range<R> && // | |
| LegalDataConversion< | |
| std::remove_volatile_t<std::remove_reference_t<std::ranges::range_reference_t<R>>>, | |
| T> && | |
| requires(R r) { | |
| { r.size() } -> std::same_as<Index>; | |
| }; | |
| template <typename Index, typename PtrType, Index Extent = DynamicExtent<Index>> | |
| struct SpanStorage; | |
| template <typename Index, typename PtrType, Index Extent> | |
| requires IsDynamicExtent<Index, Extent> | |
| struct SpanStorage<Index, PtrType, Extent> { | |
| constexpr SpanStorage() noexcept = default; | |
| constexpr SpanStorage(const SpanStorage&) noexcept = default; | |
| constexpr SpanStorage& operator=(const SpanStorage&) noexcept = default; | |
| // Explicitly copy on move so that regardless of PtrType, we guarantee that move does not | |
| // invalidate the original value to match std::span. | |
| constexpr SpanStorage(SpanStorage&& other) noexcept : SpanStorage(other) {} | |
| constexpr SpanStorage& operator=(SpanStorage&& other) noexcept { return *this = other; } | |
| constexpr SpanStorage(size_t size, PtrType data) noexcept : mSize(size), mData(data) { | |
| if constexpr (sizeof(size_t) >= sizeof(Index)) { | |
| DAWN_CHECK(size < size_t{UnderlyingType<Index>{DynamicExtent<Index>}}); | |
| } | |
| } | |
| // Keep members in this order as it matches the layout of webgpu.h, see toplevel comment. | |
| size_t mSize = 0; | |
| PtrType mData = {}; | |
| }; | |
| template <typename Index, typename PtrType, Index Extent> | |
| requires(!IsDynamicExtent<Index, Extent>) | |
| struct SpanStorage<Index, PtrType, Extent> { | |
| constexpr SpanStorage() noexcept = default; | |
| constexpr SpanStorage(const SpanStorage&) noexcept = default; | |
| constexpr SpanStorage& operator=(const SpanStorage&) noexcept = default; | |
| // Explicitly copy on move so that regardless of PtrType, we guarantee that move does not | |
| // invalidate the original value to match std::span. | |
| constexpr SpanStorage(SpanStorage&& other) noexcept : SpanStorage(other) {} | |
| constexpr SpanStorage& operator=(SpanStorage&& other) noexcept { return *this = other; } | |
| constexpr explicit SpanStorage(PtrType data) noexcept : mData(data) {} | |
| constexpr SpanStorage(size_t size, PtrType data) noexcept : mData(data) { | |
| if constexpr (Extent != Index{}) { | |
| if (data == nullptr && size == 0) { | |
| return; | |
| } | |
| } | |
| DAWN_CHECK(size == size_t{UnderlyingType<Index>{Extent}}); | |
| } | |
| PtrType mData = {}; | |
| }; | |
| // DIFF: size_t in function signatures is replaced by Index to support typed integers as the index | |
| // type. The only exception is size_bytes(). | |
| template <typename T, HasUnsignedUnderlyingType Index, typename PtrType, Index Extent> | |
| class SpanBase : private SpanStorage<Index, PtrType, Extent> { | |
| using Storage = SpanStorage<Index, PtrType, Extent>; | |
| static constexpr bool kIsDynamicExtent = IsDynamicExtent<Index, Extent>; | |
| static constexpr size_t kStdExtent = | |
| kIsDynamicExtent ? std::dynamic_extent : checked_cast<size_t>(Extent); | |
| public: | |
| // Type aliases and static members. | |
| // DIFF: size_type / difference_type missing because it's not clear what they should be, Index | |
| // or size_t? | |
| // DIFF: const_iterator missing to match LLVM's libc++ | |
| // DIFF: [const_]reverse_iterator missing to avoid including <iterator> until it is needed. | |
| using element_type = T; | |
| using value_type = std::remove_cv<T>::type; | |
| using pointer = T*; | |
| using const_pointer = const T*; | |
| using reference = T&; | |
| using const_reference = const T&; | |
| using iterator = std::span<T, kStdExtent>::iterator; | |
| static constexpr Index extent = Extent; | |
| static constexpr size_t GetOffsetOfSize() | |
| requires(kIsDynamicExtent) | |
| { | |
| return offsetof(SpanBase, mSize); | |
| } | |
| static constexpr size_t GetOffsetOfData() { return offsetof(SpanBase, mData); } | |
| // Constructors | |
| // DIFF: Many constructors are UNSAFE_BUFFER_USAGE. | |
| // DIFF: The constructor from a range is less strict than the spec requires as it would be | |
| // challenging to duplicate the exact logic while keeping support for custom index types. | |
| constexpr SpanBase() noexcept = default; | |
| // Constructor from a C-style array. | |
| template <typename ElementType, size_t N> | |
| requires LegalDataConversion<ElementType, T> | |
| explicit constexpr SpanBase(ElementType (&arr)[N]) noexcept : Storage(N, arr) { | |
| // Use `static_assert` here instead of `requires` so that ALL attempts at construction from | |
| // a C array (with a compatible element type) use this overload, rather than silently | |
| // casting to T* or being treated as a range. | |
| static_assert(std::same_as<Index, size_t>, | |
| "ityp::span cannot be constructed from a C array type. " | |
| "Use the (T*, size) constructor instead."); | |
| static_assert(kIsDynamicExtent || uint64_t{Extent} == N, | |
| "Span cannot be constructed from this C array type. " | |
| "If necessary, explicitly cast the T[N] to a T*."); | |
| } | |
| // Constructor from an initializer list. | |
| // This is needed because until C++26's P3016R6, std::initializer_list does not have a .data() | |
| // member so it cannot use the constructor from a range. | |
| template <typename ElementType> | |
| requires LegalDataConversion<ElementType, T> && std::is_const_v<T> && | |
| std::same_as<Index, size_t> && kIsDynamicExtent | |
| constexpr SpanBase(std::initializer_list<ElementType> l) : Storage(l.size(), l.begin()) {} | |
| // Constructor from a pointer + size. This is UNSAFE_BUFFER_USAGE as other methods should be | |
| // preferred to create spans directly from ranges. Will DAWN_CHECK() if the size doesn't fit in | |
| // a size_t. | |
| template <typename It> | |
| requires CompatibleIter<element_type, It> | |
| DAWN_UNSAFE_BUFFER_USAGE constexpr SpanBase(It first, Index count) | |
| : Storage(checked_cast<size_t>(count), std::to_address(first)) {} | |
| // Constructor from a pointer (fixed extent only). | |
| template <typename It> | |
| requires CompatibleIter<element_type, It> && (!kIsDynamicExtent) | |
| DAWN_UNSAFE_BUFFER_USAGE explicit constexpr SpanBase(It first) | |
| : Storage(std::to_address(first)) {} | |
| // Constructor from two iterators, this is UNSAFE_BUFFER_USAGE as other method should be | |
| // preferred to create spans directly from ranges. Will DAWN_CHECK() if the size doesn't fit in | |
| // a size_t. | |
| template <typename It, typename End> | |
| requires(CompatibleIter<element_type, It> && std::sized_sentinel_for<End, It> && | |
| !std::is_convertible_v<End, size_t>) | |
| DAWN_UNSAFE_BUFFER_USAGE constexpr SpanBase(It first, End last) | |
| : Storage(checked_cast<size_t>(last - first), std::to_address(first)) { | |
| DAWN_CHECK(first <= last); | |
| } | |
| // Constructor from a range that provides `T* data()` and `Index size()`. Note that this | |
| // is quite different from Chromium's base::span constructor from ranges because adapting that | |
| // constructor to support TypedInteger for Index would be extremely challenging. Will | |
| // DAWN_CHECK() if the size doesn't fit in a size_t or match Extent. | |
| template <typename R> | |
| requires CompatibleRange<T, Index, R> | |
| explicit(false) constexpr SpanBase(R&& range) | |
| : Storage(checked_cast<size_t>(range.size()), range.data()) {} | |
| template <typename R> | |
| requires CompatibleRange<T, Index, R> && std::is_const_v<T> | |
| explicit(false) constexpr SpanBase(const R& range) | |
| : Storage(checked_cast<size_t>(range.size()), range.data()) {} | |
| // Constructor converting a dynamic extent Span to a fixed one. | |
| template <typename OtherPtrType> | |
| explicit constexpr SpanBase(SpanBase<T, Index, OtherPtrType, DynamicExtent<Index>>& other) | |
| requires(!kIsDynamicExtent) | |
| : Storage(other.data()) { | |
| DAWN_CHECK(other.size() == Extent); | |
| } | |
| template <typename OtherPtrType> | |
| explicit constexpr SpanBase(const SpanBase<T, Index, OtherPtrType, DynamicExtent<Index>>& other) | |
| requires(!kIsDynamicExtent) | |
| : Storage(other.data()) { | |
| DAWN_CHECK(other.size() == Extent); | |
| } | |
| // Move / copy constructor / assignment operator. | |
| constexpr SpanBase(const SpanBase& other) noexcept = default; | |
| constexpr SpanBase(SpanBase&& other) noexcept = default; | |
| constexpr SpanBase& operator=(const SpanBase& other) noexcept = default; | |
| constexpr SpanBase& operator=(SpanBase&& other) noexcept = default; | |
| // Iterators | |
| // | |
| // Note that iterators use the std::span iterators to take advantage of the hardened stdlib. | |
| // DIFF: cbegin/cend missing to match LLVM's libc++ | |
| // DIFF: r[c]begin/end missing to avoid including <iterator> until it is needed. | |
| constexpr iterator begin() const noexcept { return as_std_span().begin(); } | |
| constexpr iterator end() const noexcept { return as_std_span().end(); } | |
| // Element access | |
| // Returns a reference to the first element of this. When empty, will DAWN_CHECK(). | |
| constexpr reference front() const noexcept { return at(Index{}); } | |
| // Returns a reference to the last element of this. When empty, will DAWN_CHECK(). | |
| constexpr reference back() const noexcept { | |
| // Note that an underflow happens in empty spans, which will make the argument to `at` be | |
| // the maximum value and cause a DAWN_CHECK. | |
| return at(size() - Index{UnderlyingType<Index>(1u)}); | |
| } | |
| // Returns a reference to the element of this at `index`. Will DAWN_CHECK() if OOB. | |
| constexpr reference at(Index index) const { | |
| DAWN_CHECK(index < size()); | |
| // SAFETY: The argument to unchecked_at is already checked in range in the DAWN_CHECK above. | |
| return DAWN_UNSAFE_BUFFERS(unchecked_at(index)); | |
| } | |
| // Returns a reference to the element of this at `index`. Will DAWN_CHECK() if OOB. | |
| constexpr reference operator[](Index index) const noexcept { return at(index); } | |
| // Returns a pointer at the contents of this. | |
| constexpr pointer data() const noexcept { return std::to_address(this->mData); } | |
| // Observers | |
| // Returns the number of elements of this. | |
| // DIFF: For fixed extent Spans, returns 0 if mData is nullptr. | |
| constexpr Index size() const noexcept { | |
| if constexpr (kIsDynamicExtent) { | |
| return Index(static_cast<UnderlyingType<Index>>(this->mSize)); | |
| } else { | |
| if constexpr (Extent != Index{}) { | |
| if (this->mData == nullptr) { | |
| return Index{}; | |
| } | |
| } | |
| return Extent; | |
| } | |
| } | |
| // Returns the size as size_t. This is needed when ityp::span is used in structures of | |
| // dawn_platform.h, to be able to validate that the size passed in the structure fits in Index. | |
| constexpr size_t untyped_size() const | |
| requires(kIsDynamicExtent && !std::is_same_v<Index, size_t>) | |
| { | |
| return this->mSize; | |
| } | |
| // Returns the footprint in bytes of the elements of this. | |
| constexpr size_t size_bytes() const noexcept { | |
| return static_cast<size_t>(static_cast<UnderlyingType<Index>>(size())) * | |
| sizeof(element_type); | |
| } | |
| // Returns true if this contains no elements. | |
| [[nodiscard]] constexpr bool empty() const noexcept { return size() == Index{}; } | |
| // Subviews | |
| // DIFF: subviews with template Offset / Count missing. | |
| // DIFF: dynamic_extent is not supported in the 2-arg version of subspan(), the single argument | |
| // overload must be used instead. | |
| // Returns a span of the first `count` elements of this. Will DAWN_CHECK() if OOB. | |
| constexpr auto first(Index count) const { | |
| DAWN_CHECK(count <= size()); | |
| // SAFETY: data() points at at least size() elements, so the DAWN_CHECK ensure that the | |
| // result span is a subset of this | |
| return DAWN_UNSAFE_BUFFERS(SpanBase<T, Index, PtrType>(data(), count)); | |
| } | |
| // Returns a span of the last `count` elements of this. Will DAWN_CHECK() if OOB. | |
| constexpr auto last(Index count) const { | |
| DAWN_CHECK(count <= size()); | |
| // SAFETY: data() points at at least size() elements, so the DAWN_CHECK ensure that the | |
| // result span is a subset of this. The argument to unchecked_at is already checked in range | |
| // in the DAWN_CHECK above. | |
| return DAWN_UNSAFE_BUFFERS( | |
| SpanBase<T, Index, PtrType>(&unchecked_at(size() - count), count)); | |
| } | |
| // Returns a span starting at `offset` and until the end of this. Will DAWN_CHECK() if OOB. | |
| constexpr auto subspan(Index offset) const { | |
| DAWN_CHECK(offset <= size()); | |
| Index remainingSize = size() - offset; | |
| // SAFETY: data() points at at least size() elements, so the DAWN_CHECK ensure that the | |
| // result span is a subset of this. The argument to unchecked_at is already checked in range | |
| // in the first DAWN_CHECK above. | |
| return DAWN_UNSAFE_BUFFERS( | |
| SpanBase<T, Index, PtrType>(&unchecked_at(offset), remainingSize)); | |
| } | |
| // Returns a span starting at `offset` and of `count` elements inside of this. Will DAWN_CHECK() | |
| // if OOB. | |
| constexpr auto subspan(Index offset, Index count) const { | |
| DAWN_CHECK(offset <= size()); | |
| DAWN_CHECK(count <= size() - offset); | |
| // SAFETY: data() points at at least size() elements, so the DAWN_CHECKs ensure that the | |
| // result span is a subset of this. The argument to unchecked_at is already checked in range | |
| // in the first DAWN_CHECK above. | |
| return DAWN_UNSAFE_BUFFERS(SpanBase<T, Index, PtrType>(&unchecked_at(offset), count)); | |
| } | |
| // Additions not part of std::span. | |
| // Consider adding the following like in Chromium's base::span: | |
| // | |
| // - operator ==, operator <=> | |
| // - get_at | |
| // - to_fixed_extent | |
| // - variants of methods that take offset / indices as template params to return sized spans. | |
| // Splits a span a given offset, returning a pair of spans that cover the ranges strictly before | |
| // the offset and starting at the offset, respectively. Will DAWN_CHECK when the offset is OOB. | |
| constexpr auto SplitAt(Index offset) const { return std::pair(first(offset), subspan(offset)); } | |
| // Returns a span of the first N elements, removing them. Will DAWN_CHECK when the offset is | |
| // OOB. | |
| constexpr auto TakeFirst(Index offset) | |
| requires(kIsDynamicExtent) | |
| { | |
| const auto [first, rest] = SplitAt(offset); | |
| *this = rest; | |
| return first; | |
| } | |
| // Performs a deep copy of the elements referenced by `range` to those referenced by `this`. | |
| // The spans must be the same size. | |
| // Mirrors Chromium's base::span::copy_from but does not implement the constexpr version since | |
| // it is not currently needed. | |
| template <typename R> | |
| requires(CompatibleCopySource<const T, Index, R> && !std::is_const_v<T>) | |
| void CopyFrom(R&& range) { | |
| using RElement = | |
| std::add_const_t<std::remove_reference_t<std::ranges::range_reference_t<R>>>; | |
| SpanBase<RElement, Index, const RElement*> other(std::forward<R>(range)); | |
| DAWN_CHECK(other.size() == size()); | |
| // Using `<=` to compare pointers to different allocations is UB; | |
| // delegating to the STL is the legal workaround: | |
| // https://eel.is/c++draft/defns.order.ptr | |
| std::less_equal{}(data(), other.data()) ? std::ranges::copy(other, begin()) | |
| : std::ranges::copy_backward(other, end()); | |
| } | |
| // Like `CopyFrom()`, but allows the source to be smaller than this span, and will only copy as | |
| // far as the source size, leaving the remaining elements of this span unwritten. Mirrors | |
| // Chromium's base::span::copy_prefix_from but does not implement the constexpr version since it | |
| // is not currently needed. | |
| template <typename R> | |
| requires(CompatibleCopySource<const T, Index, R> && !std::is_const_v<T>) | |
| void CopyPrefixFrom(R&& range) { | |
| using RElement = | |
| std::add_const_t<std::remove_reference_t<std::ranges::range_reference_t<R>>>; | |
| SpanBase<RElement, Index, const RElement*> other(std::forward<R>(range)); | |
| first(other.size()).CopyFrom(other); | |
| } | |
| // Fills the span with the specified byte value. | |
| // This is necessary as a workaround for ASAN which can cause timeouts (especially for fuzzers) | |
| // because std::ranges::fill performs byte-by-byte stores without loop idiom recognition. | |
| void FillBytes(std::byte value) | |
| requires(std::is_same_v<T, std::byte>) | |
| { | |
| if (empty()) { | |
| return; | |
| } | |
| #if DAWN_ASAN_ENABLED() | |
| // SAFETY: `data()` points to at least `size_bytes()` bytes of data. | |
| DAWN_UNSAFE_BUFFERS(std::memset(data(), static_cast<int>(value), size_bytes())); | |
| #else | |
| std::ranges::fill(*this, value); | |
| #endif | |
| } | |
| private: | |
| // Helper function used to avoid doing the DAWN_CHECK inside at() redundantly when the | |
| // precondition is already checked by some other mean. | |
| DAWN_UNSAFE_BUFFER_USAGE constexpr reference unchecked_at(Index typedIndex) const { | |
| size_t index = checked_cast<size_t>(typedIndex); | |
| // SAFETY: The caller must guarantee that the index is in range already. | |
| return DAWN_UNSAFE_BUFFERS(*(data() + index)); | |
| } | |
| // Helper function to return the equivalent std::span. This is necessary to be able to use | |
| // the hardened std::span iterators when the standard library enables them. | |
| constexpr std::span<T, kStdExtent> as_std_span() const { | |
| if constexpr (kIsDynamicExtent) { | |
| // SAFETY: This is the same allocation and size as this. | |
| return DAWN_UNSAFE_BUFFERS(std::span<T, kStdExtent>(data(), this->mSize)); | |
| } else { | |
| if constexpr (Extent != Index{}) { | |
| DAWN_CHECK(this->mData != nullptr); | |
| } | |
| // SAFETY: This is the same allocation and size as this. | |
| return DAWN_UNSAFE_BUFFERS(std::span<T, kStdExtent>(data(), kStdExtent)); | |
| } | |
| } | |
| }; | |
| } // namespace detail | |
| // Converts a `[Raw]Span<[const|volatile] T>` to a `[Raw]Span<[const|volatile] std::byte>`. | |
| // Mirrors Chromium's base::as_[writable_]bytes but with std::byte. | |
| template <typename T, typename Index, typename PtrType, Index Extent> | |
| constexpr auto SpanAsBytes(detail::SpanBase<T, Index, PtrType, Extent> s) { | |
| using ByteType = | |
| std::conditional_t<std::is_volatile_v<T>, const volatile std::byte, const std::byte>; | |
| using ResultPtrType = detail::ReturnPtrType<PtrType, ByteType>; | |
| if constexpr (detail::IsDynamicExtent<Index, Extent>) { | |
| // SAFETY: `s.data()` points to at least `s.size_bytes()` bytes of data. | |
| return DAWN_UNSAFE_BUFFERS(detail::SpanBase<ByteType, size_t, ResultPtrType>{ | |
| reinterpret_cast<ByteType*>(s.data()), s.size_bytes()}); | |
| } else { | |
| constexpr size_t kByteExtent = | |
| static_cast<size_t>(static_cast<UnderlyingType<Index>>(Extent)) * sizeof(T); | |
| // SAFETY: `s.data()` points to at least `s.size_bytes()` bytes of data. | |
| return DAWN_UNSAFE_BUFFERS(detail::SpanBase<ByteType, size_t, ResultPtrType, kByteExtent>{ | |
| reinterpret_cast<ByteType*>(s.data())}); | |
| } | |
| } | |
| template <typename T, typename Index, typename PtrType, Index Extent> | |
| requires(!std::is_const_v<T>) | |
| constexpr auto SpanAsWritableBytes(detail::SpanBase<T, Index, PtrType, Extent> s) { | |
| using ByteType = std::conditional_t<std::is_volatile_v<T>, volatile std::byte, std::byte>; | |
| using ResultPtrType = detail::ReturnPtrType<PtrType, ByteType>; | |
| if constexpr (detail::IsDynamicExtent<Index, Extent>) { | |
| // SAFETY: `s.data()` points to at least `s.size_bytes()` bytes of data. | |
| return DAWN_UNSAFE_BUFFERS(detail::SpanBase<ByteType, size_t, ResultPtrType>{ | |
| reinterpret_cast<ByteType*>(s.data()), s.size_bytes()}); | |
| } else { | |
| constexpr size_t kByteExtent = | |
| static_cast<size_t>(static_cast<UnderlyingType<Index>>(Extent)) * sizeof(T); | |
| // SAFETY: `s.data()` points to at least `s.size_bytes()` bytes of data. | |
| return DAWN_UNSAFE_BUFFERS(detail::SpanBase<ByteType, size_t, ResultPtrType, kByteExtent>{ | |
| reinterpret_cast<ByteType*>(s.data())}); | |
| } | |
| } | |
| namespace detail { | |
| template <typename T, typename Index, typename ByteType, size_t ByteExtent> | |
| concept LegalByteReinterpretAs = | |
| // 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>) && | |
| // For fixed-extent spans, ensure the byte size is a multiple of the target type and fits in the | |
| // Index type. | |
| (IsDynamicExtent<size_t, ByteExtent> || | |
| (ByteExtent % sizeof(T) == 0u && | |
| ByteExtent / sizeof(T) < size_t{UnderlyingType<Index>{DynamicExtent<Index>}})); | |
| template <typename T, typename Index, typename ByteType, typename PtrType, size_t ByteExtent> | |
| requires(LegalByteReinterpretAs<T, Index, ByteType, ByteExtent>) | |
| constexpr auto ReinterpretSpanImpl(SpanBase<ByteType, size_t, PtrType, ByteExtent> s) { | |
| // Check for proper alignment of the target type. | |
| DAWN_CHECK(reinterpret_cast<uintptr_t>(s.data()) % alignof(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())); | |
| using ResultPtrType = ReturnPtrType<PtrType, T>; | |
| if constexpr (IsDynamicExtent<size_t, ByteExtent>) { | |
| // Check that the size is a multiple of the target type. | |
| DAWN_CHECK(s.size_bytes() % sizeof(T) == 0u); | |
| // SAFETY: We checked for proper alignment, size, strict aliasing rules, and started the | |
| // lifetime of the array. | |
| return DAWN_UNSAFE_BUFFERS(SpanBase<T, Index, ResultPtrType, DynamicExtent<Index>>{ | |
| ptr, checked_cast<Index>(s.size_bytes() / sizeof(T))}); | |
| } else { | |
| constexpr Index kTargetExtent = | |
| Index{static_cast<UnderlyingType<Index>>(ByteExtent / sizeof(T))}; | |
| // SAFETY: We checked for proper alignment, size, strict aliasing rules, and started the | |
| // lifetime of the array. | |
| return DAWN_UNSAFE_BUFFERS(SpanBase<T, Index, ResultPtrType, kTargetExtent>{ptr}); | |
| } | |
| } | |
| } // namespace detail | |
| // Converts a `[Raw]Span<[const|volatile] std::byte>` to a | |
| // `[Raw]Span<Index, [const|volatile] T>`. Mirrors Chromium's base::subtle::reinterpret_span | |
| // with some minor differences: | |
| // - Allows for volatile qualifiers as long as they are not cast away. | |
| // - Provides a less strict requirement for std::is_trivially_copyable<T> if using the | |
| // alternative overload along with the UNSAFE_BUFFER suppressions. | |
| template <typename T, | |
| typename Index = size_t, | |
| typename ByteType, | |
| typename PtrType, | |
| size_t ByteExtent = detail::DynamicExtent<size_t>> | |
| requires(detail::LegalByteReinterpretAs<T, Index, ByteType, ByteExtent>) | |
| DAWN_UNSAFE_BUFFER_USAGE constexpr auto ReinterpretSpan( | |
| detail::SpanBase<ByteType, size_t, PtrType, ByteExtent> s) { | |
| return detail::ReinterpretSpanImpl<T, Index>(s); | |
| } | |
| template <typename T, | |
| typename Index = size_t, | |
| typename ByteType, | |
| typename PtrType, | |
| size_t ByteExtent = detail::DynamicExtent<size_t>> | |
| 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. | |
| std::is_trivially_copyable_v<T> && | |
| detail::LegalByteReinterpretAs<T, Index, ByteType, ByteExtent>) | |
| constexpr auto ReinterpretSpan(detail::SpanBase<ByteType, size_t, PtrType, ByteExtent> s) { | |
| // SAFETY: We don't need to worry about ctors/dtors since T is trivially copyable. | |
| return detail::ReinterpretSpanImpl<T, Index>(s); | |
| } | |
| // Converts a `[const] T&` to a `Span<[const] T, 1>`. | |
| // Mirrors Chromium's base::span_from_ref. | |
| template <typename Index = size_t, typename T> | |
| constexpr auto SpanFromRef(const T& t) { | |
| // SAFETY: A reference always points at a valid allocation of one element. | |
| return DAWN_UNSAFE_BUFFERS( | |
| ityp::span<Index, const T, Index{UnderlyingType<Index>(1u)}>{std::addressof(t)}); | |
| } | |
| template <typename Index = size_t, typename T> | |
| constexpr auto SpanFromRef(T& t) { | |
| // SAFETY: A reference always points at a valid allocation of one element. | |
| return DAWN_UNSAFE_BUFFERS( | |
| ityp::span<Index, T, Index{UnderlyingType<Index>(1u)}>{std::addressof(t)}); | |
| } | |
| // Converts a `[const] T&` to a `Span<[const] std::byte, sizeof(T)>`. | |
| // Mirrors Chromium's base::byte_span_from_ref but with std::byte. | |
| template <typename T> | |
| constexpr auto ByteSpanFromRef(const T& t) { | |
| return SpanAsBytes(SpanFromRef(t)); | |
| } | |
| template <typename T> | |
| constexpr auto ByteSpanFromRef(T& t) { | |
| return SpanAsWritableBytes(SpanFromRef(t)); | |
| } | |
| } // namespace dawn | |
| #endif // SRC_UTILS_SPAN_H_ |