native 0.0.1
Vectors, masks and wide register packs for C++26
Loading...
Searching...
No Matches
wide_pack.h
1// SPDX-FileCopyrightText: 2026 Edward Kmett <ekmett@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR Apache-2.0
3#pragma once
4#include "native/attributes.h"
5#include "native/value_traits.h"
6#include <array>
7#include <concepts>
8#include <cstddef>
9#include <functional>
10#include <type_traits>
11#include <utility>
12
13namespace wide {
14 namespace detail {
15 enum class family { leaf, std_array, legacy };
16 template<class T> struct shape { static constexpr auto kind=family::leaf; };
17 template<class T,std::size_t N> struct array_shape {
18 static constexpr std::size_t size=N;
19 template<std::size_t> using element_type=T;
20 };
21 template<class T,std::size_t N> struct shape<std::array<T,N>> : array_shape<T,N> {
22 static constexpr auto kind=family::std_array;
23 };
24 template<class T> using shape_t=shape<std::remove_cvref_t<T>>;
25 template<class T> inline constexpr bool is_array=
26 shape_t<T>::kind==family::std_array || shape_t<T>::kind==family::legacy;
27 }
28
30 template<class T> concept pack=detail::shape_t<T>::kind==detail::family::std_array;
31
32 namespace detail {
33 template<std::size_t I,class P>
34 native_inline constexpr decltype(auto) source_get(P&& value) noexcept {
35 if constexpr (shape_t<P>::kind==family::legacy)
36 return std::get<I>(std::forward<P>(value).registers);
37 else return std::get<I>(std::forward<P>(value));
38 }
39
40 // Element adaptation does not add an outer dimension. All construction is
41 // dependent, so this header may precede the complete SIMD definitions.
42 template<class T> struct element_traits {};
43 template<> struct element_traits<float> { using type=::native::simd<float,1,::native::scalar>; };
44 template<class T,std::size_t N,::native::isa<> A>
45 struct element_traits<::native::simd<T,N,A>> { using type=::native::simd<T,N,A>; };
46 template<std::size_t N,::native::isa<> A>
47 struct element_traits<::native::predicate<N,A>> { using type=::native::predicate<N,A>; };
48 template<class T> using adapted_t=typename element_traits<std::remove_cvref_t<T>>::type;
49 template<class T> concept adaptable=requires { typename adapted_t<T>; };
50 template<class P> requires is_array<P> && adaptable<typename shape_t<P>::template element_type<0>>
51 struct element_traits<P> {
52 using type=std::array<adapted_t<typename shape_t<P>::template element_type<0>>,shape_t<P>::size>;
53 };
54 template<class T> struct promotion_traits {};
55 template<adaptable T> struct promotion_traits<T> {
56 using canonical_type=std::conditional_t<shape_t<T>::kind==family::leaf,
57 std::array<adapted_t<T>,1>,adapted_t<T>>;
58 };
59
60 template<adaptable T> native_inline constexpr adapted_t<T> adapt_element(T&& value);
61 template<class P,std::size_t... I>
62 native_inline constexpr adapted_t<P> adapt_pack(P&& value,std::index_sequence<I...>) {
63 return {{adapt_element(source_get<I>(std::forward<P>(value)))...}};
64 }
65 template<adaptable T>
66 native_inline constexpr adapted_t<T> adapt_element(T&& value) {
67 if constexpr (shape_t<T>::kind==family::leaf)
68 return adapted_t<T>(std::forward<T>(value));
69 else return adapt_pack(std::forward<T>(value),std::make_index_sequence<shape_t<T>::size>{});
70 }
71 }
72
73 template<class T> struct traits : detail::promotion_traits<std::remove_cvref_t<T>> {};
74 template<class T> using canonical_t=typename traits<std::remove_cvref_t<T>>::canonical_type;
75 template<class T> concept promotable=requires { typename canonical_t<T>; };
76
78 template<promotable T>
79 native_inline constexpr canonical_t<T> promote(T&& value) {
80 if constexpr (detail::shape_t<T>::kind==detail::family::leaf)
81 return {{detail::adapt_element(std::forward<T>(value))}};
82 else return detail::adapt_element(std::forward<T>(value));
83 }
84
85 namespace detail {
86 template<class T> inline constexpr bool scalar_vector=false;
87 template<class T> inline constexpr bool scalar_vector<::native::simd<T,1,::native::scalar>> = true;
88 template<class T> inline constexpr bool scalar_mask=false;
89 template<class T> requires (std::same_as<T,bool> || ::native::detail::is_mask_lane<T>)
90 inline constexpr bool scalar_mask<::native::simd<T,1,::native::scalar>> = true;
91 template<> inline constexpr bool scalar_mask<::native::predicate<1,::native::scalar>> = true;
92
93 template<class Original,class P> consteval bool compatible_pack() {
94 if constexpr (!pack<P>) return false;
95 else if constexpr (shape_t<Original>::kind==family::leaf) return shape_t<P>::size==1;
96 else if constexpr (shape_t<Original>::size!=shape_t<P>::size) return false;
97 else {
98 using E=typename shape_t<Original>::template element_type<0>;
99 using R=typename shape_t<P>::template element_type<0>;
100 if constexpr (shape_t<E>::kind==family::leaf) return true;
101 else return compatible_pack<E,R>();
102 }
103 }
104
105 template<class Original,class R> native_inline constexpr auto restore_element(R&& value);
106 template<class Original,class P,std::size_t... I>
107 native_inline constexpr auto restore_pack(P&& value,std::index_sequence<I...>) {
108 using S=shape_t<Original>;
109 using E=typename S::template element_type<0>;
110 using V=typename shape_t<P>::template element_type<0>;
111 using R=decltype(restore_element<E>(std::declval<V>()));
112 if constexpr (S::kind==family::std_array)
113 return std::array<R,S::size>{{restore_element<E>(std::get<I>(std::forward<P>(value)))...}};
114 else if constexpr (requires(std::array<R,S::size> a) { S::restore(a); })
115 return S::restore(std::array<R,S::size>{{restore_element<E>(std::get<I>(std::forward<P>(value)))...}});
116 else return typename S::template rebind<R>{std::array<R,S::size>{{
117 restore_element<E>(std::get<I>(std::forward<P>(value)))...}}};
118 }
119 template<class Original,class R>
120 native_inline constexpr auto restore_element(R&& value) {
121 if constexpr (shape_t<Original>::kind!=family::leaf)
122 return restore_pack<Original>(std::forward<R>(value),
123 std::make_index_sequence<shape_t<Original>::size>{});
124 else if constexpr (std::same_as<std::remove_cvref_t<Original>,float> &&
125 scalar_mask<std::remove_cvref_t<R>>)
126 return value.to_native()!=0;
127 else if constexpr (std::same_as<std::remove_cvref_t<Original>,float> &&
128 scalar_vector<std::remove_cvref_t<R>>)
129 return value.to_native();
130 else return std::remove_cvref_t<R>(std::forward<R>(value));
131 }
132 }
133
135 template<class Original,pack P> requires promotable<Original> &&
136 (detail::compatible_pack<Original,P>())
137 native_inline constexpr auto demote(P&& result) {
138 if constexpr (detail::shape_t<Original>::kind==detail::family::leaf)
139 return detail::restore_element<Original>(std::get<0>(std::forward<P>(result)));
140 else return detail::restore_pack<Original>(std::forward<P>(result),
141 std::make_index_sequence<detail::shape_t<Original>::size>{});
142 }
143
144 namespace detail {
145 template<class From,class T> using copy_const_t=
146 std::conditional_t<std::is_const_v<std::remove_reference_t<From>>,std::add_const_t<T>,T>;
147 template<class From,class T> using copy_cv_t=
148 std::conditional_t<std::is_volatile_v<std::remove_reference_t<From>>,
149 std::add_volatile_t<copy_const_t<From,T>>,copy_const_t<From,T>>;
150 template<class P> using element_argument_t=
151 std::conditional_t<std::is_lvalue_reference_v<P>,
152 copy_cv_t<P,typename shape_t<P>::template element_type<0>>&,
153 copy_cv_t<P,typename shape_t<P>::template element_type<0>>&&>;
154 template<class F,class... P> using mapped_t=
155 std::remove_cvref_t<std::invoke_result_t<F&,element_argument_t<P>...>>;
156 template<std::size_t I,class F,class... P>
157 native_inline constexpr decltype(auto) map_element(F& function,P&&... value) {
158 return std::invoke(function,std::get<I>(std::forward<P>(value))...);
159 }
160 template<class F,class P,class... Rest,std::size_t... I>
161 consteval bool map_compatible(std::index_sequence<I...>) {
162 if constexpr (!std::is_invocable_v<F&,element_argument_t<P>,element_argument_t<Rest>...>) return false;
163 else {
164 using R=mapped_t<F,P,Rest...>;
165 if constexpr (!std::is_object_v<R>) return false;
166 else return requires(F& function,P&& first,Rest&&... rest) {
167 std::array<R,sizeof...(I)>{{map_element<I>(function,
168 std::forward<P>(first),std::forward<Rest>(rest)...)...}};
169 };
170 }
171 }
172 template<class F,class P,class... Rest,std::size_t... I>
173 native_inline constexpr auto map_pack(F&& function,std::index_sequence<I...>,P&& first,Rest&&... rest) {
174 return std::array<mapped_t<F,P,Rest...>,sizeof...(I)>{{
175 map_element<I>(function,std::forward<P>(first),std::forward<Rest>(rest)...)...}};
176 }
177 }
178
180 template<class F,pack P,pack... Rest>
181 requires ((detail::shape_t<P>::size==detail::shape_t<Rest>::size) && ...) &&
182 (detail::map_compatible<F,P,Rest...>(std::make_index_sequence<detail::shape_t<P>::size>{}))
183 native_inline constexpr auto map(F&& function,P&& first,Rest&&... rest) {
184 return detail::map_pack(std::forward<F>(function),
185 std::make_index_sequence<detail::shape_t<P>::size>{},
186 std::forward<P>(first),std::forward<Rest>(rest)...);
187 }
188}
Compiler attributes for host code, with shader-safe shared modifiers.
A homogeneous batch stored in a standard array.
Definition wide_pack.h:30
#define native_inline
inline [[always_inline]]
Definition attributes.h:212
Omitted architecture arguments use the native.simd provider's baseline.