Cuda battery library
Loading...
Searching...
No Matches
unique_ptr.hpp
Go to the documentation of this file.
1// Copyright 2021 Pierre Talbot
2
3#ifndef CUDA_BATTERY_UNIQUE_PTR_HPP
4#define CUDA_BATTERY_UNIQUE_PTR_HPP
5
6#include "utility.hpp"
7#include "allocator.hpp"
8
9/** \file unique_ptr.hpp
10 Similar to std::unique_ptr with small technical differences:
11 * - There is no specialization for arrays (e.g., unique_ptr<T[]>).
12 - We rely on an allocator and provide a function `allocate_unique` to build the pointer in place.
13 - Additional function `make_unique_block` and `make_unique_grid`
14 * Similarly to vector, the allocator is scoped, meaning it is propagated to the underlying type constructor if it takes one.
15*/
16
17namespace battery {
18
19template <class T, class Allocator = standard_allocator>
21public:
22 using element_type = T;
23 using pointer = T*;
24 using allocator_type = Allocator;
26private:
27 Allocator allocator;
28 T* ptr;
29
30 template<class U, class Alloc>
31 friend class unique_ptr;
32public:
34 : allocator(allocator), ptr(nullptr) {}
35 CUDA unique_ptr(std::nullptr_t, const allocator_type& allocator = allocator_type())
36 : allocator(allocator), ptr(nullptr) {}
37
38 // `ptr` must have been allocated using `allocator_type`.
39 CUDA explicit unique_ptr(pointer ptr, const allocator_type& allocator = allocator_type())
40 : allocator(allocator), ptr(ptr) {}
41
42 CUDA unique_ptr(this_type&& from) : ptr(from.ptr), allocator(from.allocator) {
43 from.ptr = nullptr;
44 }
45
46 template<class U>
48 : ptr(static_cast<T*>(from.ptr)), allocator(from.allocator)
49 {
50 from.ptr = nullptr;
51 }
52
53 CUDA unique_ptr(const this_type&) = delete;
54
56 if(ptr != nullptr) {
57 ptr->~T();
58 allocator.deallocate(ptr);
59 ptr = nullptr;
60 }
61 }
62
63 CUDA void swap(unique_ptr& other) {
64 ::battery::swap(ptr, other.ptr);
65 ::battery::swap(allocator, other.allocator);
66 }
67
69 this_type(std::move(r)).swap(*this);
70 return *this;
71 }
72
73 template<class U>
75 this_type(std::move(r)).swap(*this);
76 return *this;
77 }
78
79 CUDA unique_ptr& operator=(std::nullptr_t) {
80 this_type(allocator).swap(*this);
81 return *this;
82 }
83
85 pointer p = ptr;
86 ptr = nullptr;
87 return p;
88 }
89
90 CUDA void reset(pointer ptr = pointer()) {
91 this_type(ptr, allocator).swap(*this);
92 }
93
94 CUDA pointer get() const {
95 return ptr;
96 }
97
99 return allocator;
100 }
101
102 CUDA explicit operator bool() const {
103 return ptr != nullptr;
104 }
105
106 CUDA T& operator*() const {
107 assert(bool(ptr));
108 return *ptr;
109 }
110
112 assert(bool(ptr));
113 return ptr;
114 }
115};
116
117template<class T, class Alloc, class... Args>
118CUDA NI unique_ptr<T, Alloc> allocate_unique(const Alloc& alloc, Args&&... args) {
119 Alloc allocator(alloc);
120 T* ptr = static_cast<T*>(allocator.allocate(sizeof(T)));
121 assert(ptr != nullptr);
122 if constexpr(std::is_constructible<T, Args&&..., const Alloc&>{}) {
123 new(ptr) T(std::forward<Args>(args)..., allocator);
124 }
125 else {
126 new(ptr) T(std::forward<Args>(args)...);
127 }
128 return unique_ptr<T, Alloc>(ptr, allocator);
129}
130
131/** Similar to `allocate_unique` but with an default-constructed allocator. */
132template<class T, class Alloc, class... Args>
134 return allocate_unique<T>(Alloc(), std::forward<Args>(args)...);
135}
136
137#ifdef __CUDACC__
138
139}
140
141#include <cooperative_groups.h>
142
143namespace battery {
144
145/** We construct a `unique_ptr` in the style of `allocate_unique` but the function is allowed to be entered by all threads of a block.
146 * Only one thread of the block will call the function `allocate_unique`.
147 * The created pointer is stored in one of the `unique_ptr` passed as parameter to allow for RAII.
148 * The function returns a reference to the object created.
149 * Usage:
150 * ```
151 * battery::unique_ptr<int, battery::global_allocator> ptr;
152 * int& block_int = battery::make_unique_block(ptr, 10);
153 * // all threads can now use `block_int`.
154 * // ...
155 * // Don't forget to synchronize at the end of the function, to avoid deleting the unique_ptr before all threads are done using it.
156 * auto block = cooperative_groups::this_thread_block();
157 * block.sync(); // or __syncthreads();
158 * ```
159 *
160 * NOTE: this function use the cooperative groups library.
161 */
162template<class T, class Alloc, class... Args>
163__device__ NI T& make_unique_block(unique_ptr<T, Alloc>& ptr, Args&&... args) {
164 __shared__ T* raw_ptr;
165 auto block = cooperative_groups::this_thread_block();
166 invoke_one(block, [&](){
167 ptr = allocate_unique<T, Alloc>(ptr.get_allocator(), std::forward<Args>(args)...);
168 raw_ptr = ptr.get();
169 });
170 block.sync();
171 T* data_ptr = raw_ptr;
172 // This extra synchronization is required in case a thread returns quickly and re-enter the function to allocate another pointer.
173 // Indeed, it seems that shared variable (`raw_ptr` above) are static.
174 block.sync();
175 return *data_ptr;
176}
177
178namespace impl {
179 __device__ void* raw_ptr;
180}
181
182/** Same as `make_unique_block` but for the grid (all blocks).
183 * NOTE: a kernel using this function must be launched using `cudaLaunchCooperativeKernel` instead of the `<<<...>>>` syntax.
184 */
185template<class T, class Alloc, class... Args>
186__device__ NI T& make_unique_grid(unique_ptr<T, Alloc>& ptr, Args&&... args) {
187 auto grid = cooperative_groups::this_grid();
188 invoke_one(grid, [&](){
189 ptr = allocate_unique<T, Alloc>(ptr.get_allocator(), std::forward<Args>(args)...);
190 impl::raw_ptr = static_cast<void*>(ptr.get());
191 });
192 grid.sync();
193 T* data_ptr = static_cast<T*>(impl::raw_ptr);
194 // See comment of make_unique_block for why this extra barrier is required.
195 grid.sync();
196 return *data_ptr;
197}
198
199#endif
200
201} // namespace battery
202
203#endif
Definition unique_ptr.hpp:20
CUDA unique_ptr & operator=(unique_ptr< U, Allocator > &&r)
Definition unique_ptr.hpp:74
Allocator allocator_type
Definition unique_ptr.hpp:24
CUDA unique_ptr & operator=(unique_ptr &&r)
Definition unique_ptr.hpp:68
T * pointer
Definition unique_ptr.hpp:23
CUDA pointer operator->() const
Definition unique_ptr.hpp:111
CUDA unique_ptr(unique_ptr< U, Allocator > &&from)
Definition unique_ptr.hpp:47
CUDA unique_ptr(this_type &&from)
Definition unique_ptr.hpp:42
CUDA unique_ptr(const this_type &)=delete
CUDA unique_ptr & operator=(std::nullptr_t)
Definition unique_ptr.hpp:79
CUDA pointer get() const
Definition unique_ptr.hpp:94
CUDA void reset(pointer ptr=pointer())
Definition unique_ptr.hpp:90
CUDA NI ~unique_ptr()
Definition unique_ptr.hpp:55
CUDA pointer release()
Definition unique_ptr.hpp:84
CUDA allocator_type get_allocator() const
Definition unique_ptr.hpp:98
T element_type
Definition unique_ptr.hpp:22
CUDA unique_ptr(const allocator_type &allocator=allocator_type())
Definition unique_ptr.hpp:33
CUDA T & operator*() const
Definition unique_ptr.hpp:106
CUDA void swap(unique_ptr &other)
Definition unique_ptr.hpp:63
CUDA unique_ptr(std::nullptr_t, const allocator_type &allocator=allocator_type())
Definition unique_ptr.hpp:35
unique_ptr< element_type, allocator_type > this_type
Definition unique_ptr.hpp:25
CUDA unique_ptr(pointer ptr, const allocator_type &allocator=allocator_type())
Definition unique_ptr.hpp:39
Definition algorithm.hpp:10
CUDA unique_ptr< T, Alloc > make_unique(Args &&... args)
Definition unique_ptr.hpp:133
CUDA NI unique_ptr< T, Alloc > allocate_unique(const Alloc &alloc, Args &&... args)
Definition unique_ptr.hpp:118
CUDA constexpr void swap(T &a, T &b)
Definition utility.hpp:91
#define CUDA
Definition utility.hpp:59
#define NI
Definition utility.hpp:62