Skip to content

Commit 3fdf85b

Browse files
Copilotcrtrott
andauthored
Introduce canonicalize_slices (extracted from PR #408) (#451)
Introduce `canonicalize_slices` progressing to implementing P3663 This was extracted from PR #408. Next step is using canonicalization in submdspan. Signed-off-by: Christian Trott <crtrott@sandia.gov> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Christian Trott <crtrott@sandia.gov> Co-authroed-by: Mark Hoemmen <mhoemmen@nvidia.com>
1 parent 07f7bd9 commit 3fdf85b

4 files changed

Lines changed: 722 additions & 0 deletions

File tree

include/experimental/__p2630_bits/submdspan.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#pragma once
1818

1919
#include "submdspan_extents.hpp"
20+
#include "submdspan_canonicalize_slices.hpp"
2021
#include "submdspan_mapping.hpp"
2122

2223
namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
//@HEADER
2+
// ************************************************************************
3+
//
4+
// Kokkos v. 4.0
5+
// Copyright (2022) National Technology & Engineering
6+
// Solutions of Sandia, LLC (NTESS).
7+
//
8+
// Under the terms of Contract DE-NA0003525 with NTESS,
9+
// the U.S. Government retains certain rights in this software.
10+
//
11+
// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
12+
// See https://kokkos.org/LICENSE for license information.
13+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
14+
//
15+
//@HEADER
16+
17+
#pragma once
18+
19+
#include "submdspan_extents.hpp"
20+
#include <complex>
21+
22+
namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
23+
24+
#if MDSPAN_HAS_CXX_17
25+
26+
namespace detail {
27+
28+
// ============================================================
29+
// de_ice: extract the value of an integral-constant-like type
30+
// ============================================================
31+
32+
MDSPAN_TEMPLATE_REQUIRES(
33+
class T,
34+
/* requires */ (std::is_integral_v<remove_cvref_t<T>>)
35+
)
36+
MDSPAN_INLINE_FUNCTION
37+
constexpr T de_ice(T val) {
38+
return val;
39+
}
40+
41+
MDSPAN_TEMPLATE_REQUIRES(
42+
class T,
43+
/* requires */ (is_integral_constant_like_v<remove_cvref_t<T>>)
44+
)
45+
MDSPAN_INLINE_FUNCTION
46+
constexpr decltype(T::value) de_ice([[maybe_unused]] T) {
47+
return T::value;
48+
}
49+
50+
// ============================================================
51+
// index_cast: cast to IndexType, preserving integral-constant nature
52+
// ============================================================
53+
54+
MDSPAN_TEMPLATE_REQUIRES(
55+
class IndexType,
56+
class OtherIndexType,
57+
/* requires */ (
58+
std::is_signed_v<remove_cvref_t<OtherIndexType>> ||
59+
std::is_unsigned_v<remove_cvref_t<OtherIndexType>>
60+
)
61+
)
62+
MDSPAN_INLINE_FUNCTION
63+
constexpr auto index_cast(OtherIndexType&& i) noexcept {
64+
return i;
65+
}
66+
67+
MDSPAN_TEMPLATE_REQUIRES(
68+
class IndexType,
69+
class OtherIndexType,
70+
/* requires */ (
71+
! std::is_signed_v<remove_cvref_t<OtherIndexType>> &&
72+
! std::is_unsigned_v<remove_cvref_t<OtherIndexType>>
73+
)
74+
)
75+
MDSPAN_INLINE_FUNCTION
76+
constexpr auto index_cast(OtherIndexType&& i) noexcept {
77+
return static_cast<IndexType>(std::forward<OtherIndexType>(i));
78+
}
79+
80+
// ============================================================
81+
// canonical_index: canonicalize a value to IndexType,
82+
// preserving integral-constant nature when possible
83+
// ============================================================
84+
85+
MDSPAN_TEMPLATE_REQUIRES(
86+
class IndexType,
87+
class S,
88+
/* requires */ (std::is_convertible_v<S, IndexType>)
89+
)
90+
MDSPAN_INLINE_FUNCTION
91+
constexpr auto canonical_index([[maybe_unused]] S s) {
92+
// TODO: might move to public semi/public only to get error earlier, and
93+
// don't duplicate check
94+
// TODO: add mandate for integral-constant-like representable as IndexType
95+
// TODO: add precondition check that index-cast is representable as IndexType
96+
static_assert(std::is_signed_v<IndexType> || std::is_unsigned_v<IndexType>);
97+
if constexpr (is_integral_constant_like_v<S>) {
98+
return cw<static_cast<IndexType>(index_cast<IndexType>(S::value))>;
99+
}
100+
else {
101+
return static_cast<IndexType>(index_cast<IndexType>(std::move(s)));
102+
}
103+
}
104+
105+
// ============================================================
106+
// subtract_ice: subtract two values, preserving integral-constant
107+
// nature when both inputs are integral-constant-like
108+
// ============================================================
109+
110+
template<class IndexType, class X, class Y>
111+
MDSPAN_INLINE_FUNCTION
112+
constexpr auto subtract_ice([[maybe_unused]] X x, [[maybe_unused]] Y y) {
113+
if constexpr (
114+
is_integral_constant_like_v<remove_cvref_t<X>> &&
115+
is_integral_constant_like_v<remove_cvref_t<Y>>)
116+
{
117+
return cw<IndexType(canonical_index<IndexType>(Y::value) - canonical_index<IndexType>(X::value))>;
118+
}
119+
else {
120+
return canonical_index<IndexType>(y) - canonical_index<IndexType>(x);
121+
}
122+
}
123+
124+
// ============================================================
125+
// check_static_bounds: compile-time bounds check for a slice
126+
//
127+
// Returns false if the slice is statically out of bounds.
128+
//
129+
// This function is called only in static_assert contexts.
130+
// ============================================================
131+
132+
template<class IndexType, size_t Exts_k, class S_k>
133+
constexpr bool check_static_bounds()
134+
{
135+
if constexpr (std::is_convertible_v<S_k, full_extent_t>) {
136+
return true;
137+
}
138+
else if constexpr (std::is_convertible_v<S_k, IndexType>) {
139+
if constexpr (is_integral_constant_like_v<S_k>) {
140+
if constexpr (de_ice(S_k{}) < 0) {
141+
return false;
142+
}
143+
else if constexpr (
144+
Exts_k != dynamic_extent &&
145+
Exts_k <= static_cast<size_t>(de_ice(S_k{})))
146+
{
147+
return false;
148+
}
149+
else { return true; }
150+
} else {
151+
return true;
152+
}
153+
}
154+
else if constexpr (is_strided_slice<S_k>::value) {
155+
using offset_type = typename S_k::offset_type;
156+
157+
if constexpr (is_integral_constant_like_v<offset_type>) {
158+
if constexpr (de_ice(offset_type{}) < 0) {
159+
return false;
160+
}
161+
else if constexpr (
162+
Exts_k != dynamic_extent &&
163+
Exts_k < static_cast<size_t>(de_ice(offset_type{})))
164+
{
165+
return false;
166+
}
167+
else if constexpr (is_integral_constant_like_v<typename S_k::extent_type>) {
168+
using extent_type = typename S_k::extent_type;
169+
170+
if constexpr (de_ice(offset_type{}) + de_ice(extent_type{}) < 0) {
171+
return false;
172+
}
173+
else if constexpr (
174+
Exts_k != dynamic_extent &&
175+
Exts_k <
176+
static_cast<size_t>(de_ice(offset_type{}) + de_ice(extent_type{})))
177+
{
178+
return false;
179+
}
180+
else if constexpr (
181+
Exts_k != dynamic_extent &&
182+
0 <= de_ice(offset_type{}) &&
183+
de_ice(offset_type{}) <=
184+
de_ice(offset_type{}) + de_ice(extent_type{}) &&
185+
static_cast<size_t>(
186+
de_ice(offset_type{}) + de_ice(extent_type{})) <= Exts_k)
187+
{
188+
return true;
189+
}
190+
else {
191+
return true;
192+
}
193+
}
194+
else {
195+
return true;
196+
}
197+
}
198+
else {
199+
return true;
200+
}
201+
} else {
202+
// General pair-like case: attempt to get the first and second elements.
203+
// If S_k cannot be structured-bound into two elements, this is ill-formed,
204+
// which implements the Mandates clause.
205+
// Doing this via these lambdas since we can do the declval only in a
206+
// non-evaluated context
207+
auto get_first = [] (S_k s_k) {
208+
auto [s_k0, _x] = s_k;
209+
return s_k0;
210+
};
211+
auto get_second = [] (S_k s_k) {
212+
auto [_x, s_k1] = s_k;
213+
return s_k1;
214+
};
215+
using S_k0 = decltype(get_first(std::declval<S_k>()));
216+
using S_k1 = decltype(get_second(std::declval<S_k>()));
217+
218+
if constexpr (is_integral_constant_like_v<S_k0>) {
219+
if constexpr (de_ice(S_k0{}) < 0) {
220+
return false;
221+
}
222+
else if constexpr (
223+
Exts_k != dynamic_extent &&
224+
Exts_k < static_cast<size_t>(de_ice(S_k0{})))
225+
{
226+
return false;
227+
}
228+
else if constexpr (is_integral_constant_like_v<S_k1>) {
229+
if constexpr (de_ice(S_k1{}) < de_ice(S_k0{})) {
230+
return false;
231+
}
232+
else if constexpr (
233+
Exts_k != dynamic_extent &&
234+
Exts_k < static_cast<size_t>(de_ice(S_k1{})))
235+
{
236+
return false;
237+
}
238+
else if constexpr (
239+
Exts_k != dynamic_extent &&
240+
0 <= de_ice(S_k0{}) &&
241+
de_ice(S_k0{}) <= de_ice(S_k1{}) &&
242+
static_cast<size_t>(de_ice(S_k1{})) <= Exts_k)
243+
{
244+
return true;
245+
}
246+
else {
247+
return true;
248+
}
249+
}
250+
else {
251+
return true;
252+
}
253+
}
254+
else {
255+
return true;
256+
}
257+
}
258+
}
259+
260+
// ============================================================
261+
// check_submdspan_slice_mandate: mandate check for the k-th slice
262+
//
263+
// Contains only static_asserts; no actual computation.
264+
// Separated from canonical_slice so that
265+
// mandate checking and canonicalization are distinct concerns.
266+
// ============================================================
267+
268+
template<class IndexType, size_t Extent, class Slice>
269+
MDSPAN_INLINE_FUNCTION
270+
constexpr bool check_submdspan_slice_mandate(
271+
[[maybe_unused]] const Slice&)
272+
{
273+
static_assert(check_static_bounds<IndexType, Extent, Slice>());
274+
return true;
275+
}
276+
277+
// ============================================================
278+
// canonical_slice: canonicalize a single slice
279+
//
280+
// This function performs ONLY the conversion to canonical form.
281+
// Mandate checking (static_asserts) is NOT done here; it is
282+
// done separately by check_submdspan_slice_mandates.
283+
//
284+
// Templated only on IndexType (the extents index type) and Slice.
285+
// Neither k nor the extents are needed for the actual conversion.
286+
// ============================================================
287+
288+
template<class IndexType, class Slice>
289+
MDSPAN_INLINE_FUNCTION
290+
constexpr auto canonical_slice([[maybe_unused]] Slice s)
291+
{
292+
if constexpr (std::is_convertible_v<Slice, full_extent_t>) {
293+
return full_extent; // canonical full-extent slice
294+
}
295+
else if constexpr (std::is_convertible_v<Slice, IndexType>) {
296+
return canonical_index<IndexType>(std::move(s)); // canonical integer index
297+
}
298+
else if constexpr (is_strided_slice<Slice>::value) {
299+
// Canonicalize each component of the strided_slice
300+
auto offset = canonical_index<IndexType>(s.offset);
301+
auto extent = canonical_index<IndexType>(s.extent);
302+
auto stride = canonical_index<IndexType>(s.stride);
303+
return strided_slice<decltype(offset), decltype(extent), decltype(stride)>{
304+
/* .offset = */ offset,
305+
/* .extent = */ extent,
306+
/* .stride = */ stride
307+
};
308+
} else {
309+
// General pair-like case: structured binding into [first, last)
310+
auto [s_k0, s_k1] = std::move(s);
311+
using S_k0 = decltype(s_k0);
312+
using S_k1 = decltype(s_k1);
313+
static_assert(std::is_convertible_v<S_k0, IndexType>);
314+
static_assert(std::is_convertible_v<S_k1, IndexType>);
315+
316+
auto offset = canonical_index<IndexType>(s_k0);
317+
auto extent = subtract_ice<IndexType>(s_k0, s_k1);
318+
auto stride = cw<IndexType(1)>;
319+
return strided_slice<decltype(offset), decltype(extent), decltype(stride)>{
320+
/* .offset = */ offset,
321+
/* .extent = */ extent,
322+
/* .stride = */ stride
323+
};
324+
}
325+
}
326+
327+
// ============================================================
328+
// canonical_slices_impl: implementation helper
329+
//
330+
// First performs mandate checks (static_asserts), then
331+
// returns a detail::tuple of canonical slices.
332+
// Using detail::tuple instead of std::tuple ensures device
333+
// code compatibility (e.g., CUDA).
334+
// ============================================================
335+
336+
MDSPAN_TEMPLATE_REQUIRES(
337+
size_t... Inds,
338+
class Extents,
339+
class... Slices,
340+
/* requires */ (sizeof...(Slices) == Extents::rank())
341+
)
342+
MDSPAN_INLINE_FUNCTION
343+
constexpr auto canonical_slices_impl(
344+
std::index_sequence<Inds...>,
345+
const Extents&,
346+
Slices... slices)
347+
{
348+
// Mandate checks (static_asserts only, no computation).
349+
// Separated from canonicalization for clarity.
350+
(void)(check_submdspan_slice_mandate<typename Extents::index_type, Extents::static_extent(Inds)>(slices) && ... && true);
351+
352+
// Actual canonicalization: returns detail::tuple for device compatibility.
353+
return detail::tuple{
354+
canonical_slice<typename Extents::index_type>(slices)...
355+
};
356+
}
357+
358+
} // namespace detail
359+
360+
// ============================================================
361+
// canonicalize_slices: public API
362+
//
363+
// Given an extents object and a pack of slice specifiers,
364+
// returns a detail::tuple of canonical slice specifiers.
365+
// Each canonical slice is one of:
366+
// - full_extent_t (for full-extent slices)
367+
// - IndexType (for integer index slices)
368+
// - strided_slice<...> (for range and strided-range slices)
369+
// ============================================================
370+
371+
MDSPAN_TEMPLATE_REQUIRES(
372+
class IndexType,
373+
size_t... Extents,
374+
class... Slices,
375+
/* requires */ (sizeof...(Slices) == sizeof...(Extents))
376+
)
377+
MDSPAN_INLINE_FUNCTION
378+
constexpr auto canonical_slices(
379+
const extents<IndexType, Extents...>& exts,
380+
Slices... slices)
381+
{
382+
return detail::canonical_slices_impl(
383+
std::make_index_sequence<sizeof...(Slices)>(), exts, slices...);
384+
}
385+
386+
#endif // MDSPAN_HAS_CXX_17
387+
388+
} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE

0 commit comments

Comments
 (0)