blob: 90b7f7c39e87cdae04e81b168b8dbaf7032aa73a [file] [edit]
// 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 <concepts>
#include <limits>
#include <memory>
#include <span>
#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 {
template <typename T, HasUnsignedUnderlyingType Index, typename PtrType>
class SpanBase;
}
// A direct replacement for std::span<T>
template <typename T>
using Span = detail::SpanBase<T, size_t, T*>;
namespace ityp {
// A replacement for std::span<T> but with a different index type (like a TypedInteger).
template <typename Index, typename T>
using span = dawn::detail::SpanBase<T, Index, T*>;
} // namespace ityp
// The equivalent of std::dynamic_extent for the given index type.
template <typename Index>
inline constexpr Index DynamicExtent = std::numeric_limits<Index>::max();
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 = requires(R r) {
{ r.data() } -> std::convertible_to<T*>;
{ r.size() } -> std::same_as<Index>;
};
// DIFF: only dynamic_extent is supported at the moment because Dawn might not need spans with
// static extents.
// 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>
class SpanBase {
private:
using Self = SpanBase<T, Index, PtrType>;
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>::iterator;
static inline constexpr Index extent = DynamicExtent<Index>;
// Constructors
// DIFF: The constructor from a C-style array is not present.
// 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 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)
: mSize(checked_cast<size_t>(count)), mData(std::to_address(first)) {
DAWN_CHECK(count != DynamicExtent<Index>);
}
// 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)
: mSize(last - first), mData(std::to_address(first)) {
DAWN_CHECK(first <= last);
if constexpr (sizeof(size_t) > sizeof(Index)) {
DAWN_CHECK(mSize <= size_t{UnderlyingType<Index>{DynamicExtent<Index>}});
}
}
// Constructor from a "range-like" 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.
template <typename R>
requires CompatibleRange<T, Index, R>
// NOLINTNEXTLINE(runtime/explicit)
constexpr SpanBase(R&& range)
: mSize(checked_cast<size_t>(range.size())), mData(range.data()) {}
template <typename R>
requires CompatibleRange<T, Index, R> && std::is_const_v<T>
// NOLINTNEXTLINE(runtime/explicit)
constexpr SpanBase(const R& range)
: mSize(checked_cast<size_t>(range.size())), mData(range.data()) {}
// 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{0u}); }
// 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{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 T& operator[](Index index) const noexcept { return at(index); }
// Returns a pointer at the contents of this.
constexpr pointer data() const noexcept { return mData; }
// Observers
// Returns the number of elements of this.
constexpr Index size() const noexcept {
return Index(static_cast<UnderlyingType<Index>>(mSize));
}
// Returns the footprint in bytes of the elements of this.
constexpr size_t size_bytes() const noexcept { return mSize * sizeof(element_type); }
// Returns true if this contains no elements.
[[nodiscard]] constexpr bool empty() const noexcept { return mSize == 0; }
// 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 Self 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(Self(data(), count));
}
// Returns a span of the last `count` elements of this. Will DAWN_CHECK() if OOB.
constexpr Self 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(Self(&unchecked_at(size() - count), count));
}
// Returns a span starting at `offset` and until the end of this. Will DAWN_CHECK() if OOB.
constexpr Self 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(Self(&unchecked_at(offset), remainingSize));
}
// Returns a span starting at `offset` and of `count` elements inside of this. Will DAWN_CHECK()
// if OOB.
constexpr Self 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(Self(&unchecked_at(offset), count));
}
// Additions not part of std::span.
// None for now, but consider adding the following like in Chromium's base::span:
//
// - constructor from (&T)[N]
// - operator ==, operator <=>
// - copy_from, copy_prefix_from
// - split_at, take_first, take_first_elem
// - get_at
// - to_fixed_extent
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> as_std_span() const {
// SAFETY: This is the same allocation and size as this.
return DAWN_UNSAFE_BUFFERS({mData, mSize});
}
// Keep members in this order as it matches the layout of webgpu.h, see toplevel comment.
size_t mSize = 0;
PtrType mData = {};
};
} // namespace detail
} // namespace dawn
#endif // SRC_UTILS_SPAN_H_