Skip to content

Commit 5226c92

Browse files
committed
Add cache_ptr and unaligned_ptr
1 parent 14a94fb commit 5226c92

4 files changed

Lines changed: 78 additions & 50 deletions

File tree

‎docs/guides/cache_modifiers.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ Calling `read_aligned`/`write_aligned` without explicitly specifying a modifier
4343

4444
## Using `vector_ptr`/`access_policy`
4545

46-
The `cache_modifier` can also be combined with `vector_ptr` by using the alias `cache_vec_ptr`:
46+
The `cache_modifier` can also be combined with `vector_ptr` by using the alias `cache_ptr`:
4747

4848
```cpp
4949
float* pointer = ...;
5050

5151
// Create the vector pointer with streaming access
52-
kf::cache_vec_ptr<kf::cache_modifier::streaming, float, 4> a = kf::make_vec_ptr<4>(pointer);
52+
kf::cache_ptr<kf::cache_modifier::streaming, float, 4> a = kf::make_vec_ptr<4>(pointer);
5353

5454
// Read data using the stream access policy
5555
kf::vec<float, 4> v = a[0];

‎include/kernel_float/memory.h‎

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -151,17 +151,14 @@ constexpr size_t gcd(size_t a, size_t b) {
151151
}
152152

153153
/**
154-
* Returns true if a value of `bytes` bytes can be reinterpreted as one of the built-in types supported by
155-
* the `__ldXX`/`__stXX` cache-modifier intrinsics (this covers exactly the sizes of `char`, `short`,
156-
* `int`/`float`, `long long`/`double` and `int4`/`double2`/`ulonglong2`).
154+
* Maps a size in bytes to the built-in type that a value of that size can be reinterpreted as for the
155+
* `__ldXX`/`__stXX` cache-modifier intrinsics. Only the sizes of `char`, `short`, `int`/`float`,
156+
* `long long`/`double` and `int4`/`double2`/`ulonglong2` are supported; every other size maps to `void`.
157157
*/
158-
KERNEL_FLOAT_INLINE
159-
constexpr bool cache_intrinsic_supported(size_t bytes) {
160-
return bytes == 1 || bytes == 2 || bytes == 4 || bytes == 8 || bytes == 16;
161-
}
162-
163158
template<size_t Bytes>
164-
struct cache_intrinsic_type;
159+
struct cache_intrinsic_type {
160+
using type = void;
161+
};
165162

166163
template<>
167164
struct cache_intrinsic_type<1> {
@@ -188,6 +185,14 @@ struct cache_intrinsic_type<16> {
188185
using type = ulonglong2;
189186
};
190187

188+
/**
189+
* Returns true if a value of `Bytes` bytes can be reinterpreted as one of the built-in types supported by
190+
* the cache-modifier intrinsics.
191+
*/
192+
template<size_t Bytes>
193+
static constexpr bool cache_intrinsic_supported =
194+
!is_same_type<typename cache_intrinsic_type<Bytes>::type, void>;
195+
191196
/**
192197
* Loads a value of type `S` from `ptr`, applying the given cache-eviction hint. Falls back to a plain load
193198
* if `Modifier` is `normal`, if `S` does not match one of the sizes supported by the cache intrinsics, or if
@@ -196,7 +201,7 @@ struct cache_intrinsic_type<16> {
196201
template<typename S, cache_modifier Modifier>
197202
KERNEL_FLOAT_INLINE S cache_load(const S* ptr) {
198203
#if KERNEL_FLOAT_IS_CUDA && KERNEL_FLOAT_IS_DEVICE
199-
if constexpr (!cache_intrinsic_supported(sizeof(S)) || Modifier == cache_modifier::normal) {
204+
if constexpr (!cache_intrinsic_supported<sizeof(S)> || Modifier == cache_modifier::normal) {
200205
return *ptr;
201206
} else {
202207
using R = typename cache_intrinsic_type<sizeof(S)>::type;
@@ -235,7 +240,7 @@ KERNEL_FLOAT_INLINE S cache_load(const S* ptr) {
235240
template<typename S, cache_modifier Modifier>
236241
KERNEL_FLOAT_INLINE void cache_store(S* ptr, const S& value) {
237242
#if KERNEL_FLOAT_IS_CUDA && KERNEL_FLOAT_IS_DEVICE
238-
if constexpr (!cache_intrinsic_supported(sizeof(S)) || Modifier == cache_modifier::normal) {
243+
if constexpr (!cache_intrinsic_supported<sizeof(S)> || Modifier == cache_modifier::normal) {
239244
*ptr = value;
240245
} else {
241246
using R = typename cache_intrinsic_type<sizeof(S)>::type;
@@ -486,11 +491,6 @@ struct access_policy {
486491
KERNEL_FLOAT_INLINE access_policy(access_policy<U, M, RP, WP>) {}
487492
access_policy() = default;
488493

489-
/**
490-
* Returns the policy to use after the pointer has been offset by a multiple of `N` elements. This stateless
491-
* policy simply constructs the offset policy type; a stateful policy should override this to carry its state
492-
* forward so that derived `vector_ref`s observe the same state.
493-
*/
494494
template<size_t N>
495495
KERNEL_FLOAT_INLINE with_offset<N> offset_impl(size_t index) const {
496496
return with_offset<N> {};
@@ -831,15 +831,20 @@ operator+(size_t i, vector_ptr<T, N, P> p) {
831831
return p.offset(i);
832832
}
833833

834+
/**
835+
* Offsets `p` in place by `i * N` elements.
836+
*
837+
* This is only available when offsetting the pointer does not lose alignment, i.e. when the policy after
838+
* offsetting is convertible back to the original policy. Otherwise `p + i` has a different (less aligned)
839+
* type than `p` and cannot be assigned back; capture it in a new variable instead (e.g. `auto q = p + i`).
840+
*/
834841
template<
835842
typename T,
836843
size_t N,
837-
typename U,
838-
size_t A,
839-
typename = enable_if_t<
840-
detail::is_policy_convertible<access_policy<U, A>, access_policy<U, N * sizeof(U)>>::value>>
841-
KERNEL_FLOAT_INLINE vector_ptr<T, N, access_policy<U, A>>&
842-
operator+=(vector_ptr<T, N, access_policy<U, A>>& p, size_t i) {
844+
typename P,
845+
typename =
846+
enable_if_t<detail::is_policy_convertible<P, typename P::template with_offset<N>>::value>>
847+
KERNEL_FLOAT_INLINE vector_ptr<T, N, P>& operator+=(vector_ptr<T, N, P>& p, size_t i) {
843848
return p = p + i;
844849
}
845850

@@ -1070,8 +1075,11 @@ make_vec_ptr(T* ptr) {
10701075
template<typename T, size_t N = 1, typename U = T, size_t Align = N>
10711076
using vec_ptr = vector_ptr<T, N, access_policy<U, Align * sizeof(U)>>;
10721077

1078+
template<typename T, size_t N = 1, typename U = T>
1079+
using unaligned_ptr = vector_ptr<T, N, access_policy<U, alignof(U)>>;
1080+
10731081
template<cache_modifier modifier, typename T, size_t N = 1, typename U = T, size_t Align = N>
1074-
using cache_vec_ptr = vector_ptr<T, N, access_policy<U, Align * sizeof(U), modifier>>;
1082+
using cache_ptr = vector_ptr<T, N, access_policy<U, Align * sizeof(U), modifier>>;
10751083

10761084
template<typename T, typename U = T>
10771085
using scalar_ptr = vector_ptr<T, 1, access_policy<U>>;

‎single_include/kernel_float.h‎

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
//================================================================================
1818
// this file has been auto-generated, do not modify its contents!
19-
// date: 2026-08-25 17:05:35.335482
20-
// git hash: f4e2d65be6e0f28156781eb766bae0fbcbe48b42
19+
// date: 2026-08-28 13:42:54.102829
20+
// git hash: 14a94fbc188ca3285e7351e5bb79184ce89c75d7
2121
//================================================================================
2222

2323
#ifndef KERNEL_FLOAT_MACROS_H
@@ -2771,17 +2771,14 @@ constexpr size_t gcd(size_t a, size_t b) {
27712771
}
27722772

27732773
/**
2774-
* Returns true if a value of `bytes` bytes can be reinterpreted as one of the built-in types supported by
2775-
* the `__ldXX`/`__stXX` cache-modifier intrinsics (this covers exactly the sizes of `char`, `short`,
2776-
* `int`/`float`, `long long`/`double` and `int4`/`double2`/`ulonglong2`).
2774+
* Maps a size in bytes to the built-in type that a value of that size can be reinterpreted as for the
2775+
* `__ldXX`/`__stXX` cache-modifier intrinsics. Only the sizes of `char`, `short`, `int`/`float`,
2776+
* `long long`/`double` and `int4`/`double2`/`ulonglong2` are supported; every other size maps to `void`.
27772777
*/
2778-
KERNEL_FLOAT_INLINE
2779-
constexpr bool cache_intrinsic_supported(size_t bytes) {
2780-
return bytes == 1 || bytes == 2 || bytes == 4 || bytes == 8 || bytes == 16;
2781-
}
2782-
27832778
template<size_t Bytes>
2784-
struct cache_intrinsic_type;
2779+
struct cache_intrinsic_type {
2780+
using type = void;
2781+
};
27852782

27862783
template<>
27872784
struct cache_intrinsic_type<1> {
@@ -2808,6 +2805,14 @@ struct cache_intrinsic_type<16> {
28082805
using type = ulonglong2;
28092806
};
28102807

2808+
/**
2809+
* Returns true if a value of `Bytes` bytes can be reinterpreted as one of the built-in types supported by
2810+
* the cache-modifier intrinsics.
2811+
*/
2812+
template<size_t Bytes>
2813+
static constexpr bool cache_intrinsic_supported =
2814+
!is_same_type<typename cache_intrinsic_type<Bytes>::type, void>;
2815+
28112816
/**
28122817
* Loads a value of type `S` from `ptr`, applying the given cache-eviction hint. Falls back to a plain load
28132818
* if `Modifier` is `normal`, if `S` does not match one of the sizes supported by the cache intrinsics, or if
@@ -2816,7 +2821,7 @@ struct cache_intrinsic_type<16> {
28162821
template<typename S, cache_modifier Modifier>
28172822
KERNEL_FLOAT_INLINE S cache_load(const S* ptr) {
28182823
#if KERNEL_FLOAT_IS_CUDA && KERNEL_FLOAT_IS_DEVICE
2819-
if constexpr (!cache_intrinsic_supported(sizeof(S)) || Modifier == cache_modifier::normal) {
2824+
if constexpr (!cache_intrinsic_supported<sizeof(S)> || Modifier == cache_modifier::normal) {
28202825
return *ptr;
28212826
} else {
28222827
using R = typename cache_intrinsic_type<sizeof(S)>::type;
@@ -2855,7 +2860,7 @@ KERNEL_FLOAT_INLINE S cache_load(const S* ptr) {
28552860
template<typename S, cache_modifier Modifier>
28562861
KERNEL_FLOAT_INLINE void cache_store(S* ptr, const S& value) {
28572862
#if KERNEL_FLOAT_IS_CUDA && KERNEL_FLOAT_IS_DEVICE
2858-
if constexpr (!cache_intrinsic_supported(sizeof(S)) || Modifier == cache_modifier::normal) {
2863+
if constexpr (!cache_intrinsic_supported<sizeof(S)> || Modifier == cache_modifier::normal) {
28592864
*ptr = value;
28602865
} else {
28612866
using R = typename cache_intrinsic_type<sizeof(S)>::type;
@@ -3106,11 +3111,6 @@ struct access_policy {
31063111
KERNEL_FLOAT_INLINE access_policy(access_policy<U, M, RP, WP>) {}
31073112
access_policy() = default;
31083113

3109-
/**
3110-
* Returns the policy to use after the pointer has been offset by a multiple of `N` elements. This stateless
3111-
* policy simply constructs the offset policy type; a stateful policy should override this to carry its state
3112-
* forward so that derived `vector_ref`s observe the same state.
3113-
*/
31143114
template<size_t N>
31153115
KERNEL_FLOAT_INLINE with_offset<N> offset_impl(size_t index) const {
31163116
return with_offset<N> {};
@@ -3451,15 +3451,20 @@ operator+(size_t i, vector_ptr<T, N, P> p) {
34513451
return p.offset(i);
34523452
}
34533453

3454+
/**
3455+
* Offsets `p` in place by `i * N` elements.
3456+
*
3457+
* This is only available when offsetting the pointer does not lose alignment, i.e. when the policy after
3458+
* offsetting is convertible back to the original policy. Otherwise `p + i` has a different (less aligned)
3459+
* type than `p` and cannot be assigned back; capture it in a new variable instead (e.g. `auto q = p + i`).
3460+
*/
34543461
template<
34553462
typename T,
34563463
size_t N,
3457-
typename U,
3458-
size_t A,
3459-
typename = enable_if_t<
3460-
detail::is_policy_convertible<access_policy<U, A>, access_policy<U, N * sizeof(U)>>::value>>
3461-
KERNEL_FLOAT_INLINE vector_ptr<T, N, access_policy<U, A>>&
3462-
operator+=(vector_ptr<T, N, access_policy<U, A>>& p, size_t i) {
3464+
typename P,
3465+
typename =
3466+
enable_if_t<detail::is_policy_convertible<P, typename P::template with_offset<N>>::value>>
3467+
KERNEL_FLOAT_INLINE vector_ptr<T, N, P>& operator+=(vector_ptr<T, N, P>& p, size_t i) {
34633468
return p = p + i;
34643469
}
34653470

@@ -3690,8 +3695,11 @@ make_vec_ptr(T* ptr) {
36903695
template<typename T, size_t N = 1, typename U = T, size_t Align = N>
36913696
using vec_ptr = vector_ptr<T, N, access_policy<U, Align * sizeof(U)>>;
36923697

3698+
template<typename T, size_t N = 1, typename U = T>
3699+
using unaligned_ptr = vector_ptr<T, N, access_policy<U, alignof(U)>>;
3700+
36933701
template<cache_modifier modifier, typename T, size_t N = 1, typename U = T, size_t Align = N>
3694-
using cache_vec_ptr = vector_ptr<T, N, access_policy<U, Align * sizeof(U), modifier>>;
3702+
using cache_ptr = vector_ptr<T, N, access_policy<U, Align * sizeof(U), modifier>>;
36953703

36963704
template<typename T, typename U = T>
36973705
using scalar_ptr = vector_ptr<T, 1, access_policy<U>>;

‎tests/memory.cu‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,18 @@ struct vector_ptr_test {
363363
ASSERT((1 + p).get() == data + 4);
364364
ASSERT((&p[1]).get() == data + 4);
365365

366+
// operator+= advances in place and keeps the same type
367+
p += 1;
368+
ASSERT_TYPE(decltype(p += 1), decltype(p)&);
369+
ASSERT(p.get() == data + 4);
370+
p += 2;
371+
ASSERT(p.get() == data + 12);
372+
373+
// also works for a cache-modified pointer
374+
kf::cache_ptr<kf::cache_modifier::streaming, T, 4> cp = kf::make_vec_ptr(data);
375+
cp += 1;
376+
ASSERT(cp.get() == data + 4);
377+
366378
// vector size = 6 elements, alignment = 4 elements
367379
kf::vec_ptr<T, 6, T, 4> q = kf::make_vec_ptr(data);
368380
ASSERT_TYPE(decltype(q.offset(1)), kf::vec_ptr<T, 6, T, 2>);

0 commit comments

Comments
 (0)