execution 1.0.1
Portable task execution, timers, and process lifecycle
Loading...
Searching...
No Matches
task.hpp
Go to the documentation of this file.
1/*
2 Copyright 2015 Adobe
3 Distributed under the Boost Software License, Version 1.0.
4 (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5*/
6
7/**************************************************************************************************/
8
9#ifndef STLAB_CONCURRENCY_TASK_HPP
10#define STLAB_CONCURRENCY_TASK_HPP
11
33
34/**************************************************************************************************/
35
36#include <algorithm>
37#include <array>
38#include <cstddef>
39#include <exception>
40#include <functional>
41#include <memory>
42#include <type_traits>
43#include <utility>
44
45#include <stlab/execution/config.hpp>
46
47/**************************************************************************************************/
48
49namespace stlab {
50STLAB_EXECUTION_VERSION_NAMESPACE_BEGIN()
51
52
60
61/**************************************************************************************************/
62
63
65 using dtor_t = void (*)(void*) noexcept;
66 using move_ctor_t = void (*)(void*, void*) noexcept;
67 using target_type_t = const std::type_info& (*)() noexcept;
68 using pointer_t = void* (*)(void*) noexcept;
69 using const_pointer_t = const void* (*)(const void*) noexcept;
70
71 dtor_t dtor;
72 move_ctor_t move_ctor;
73 target_type_t target_type;
74 pointer_t pointer;
75 const_pointer_t const_pointer;
76};
77
79inline constexpr std::size_t stlab_v2_task_storage_size =
80 std::max(alignof(std::max_align_t) * 2, sizeof(void*) * 8) -
81 std::max(alignof(std::max_align_t), sizeof(void*) * 2);
82
84inline constexpr std::size_t stlab_v2_task_storage_alignment = alignof(std::max_align_t);
85
86namespace execution_detail {
87
89template <std::size_t Size, std::size_t Alignment>
90struct task_storage_abi_guard {
91 STLAB_CORE_API static const unsigned char value;
92};
93
95using current_task_storage_abi_guard =
96 task_storage_abi_guard<stlab_v2_task_storage_size, stlab_v2_task_storage_alignment>;
97
99template <>
100struct task_storage_abi_guard<stlab_v2_task_storage_size, stlab_v2_task_storage_alignment> {
101 STLAB_CORE_API static const unsigned char value;
102};
103
105constexpr auto stlab_v2_align_offset(std::size_t offset, std::size_t alignment) noexcept
106 -> std::size_t {
107 return (offset + alignment - 1) / alignment * alignment;
108}
109
110inline constexpr auto stlab_v2_task_concept_move_ctor_offset = stlab_v2_align_offset(
111 sizeof(stlab_v2_task_concept::dtor_t), alignof(stlab_v2_task_concept::move_ctor_t));
112inline constexpr auto stlab_v2_task_concept_target_type_offset = stlab_v2_align_offset(
113 stlab_v2_task_concept_move_ctor_offset + sizeof(stlab_v2_task_concept::move_ctor_t),
114 alignof(stlab_v2_task_concept::target_type_t));
115inline constexpr auto stlab_v2_task_concept_pointer_offset = stlab_v2_align_offset(
116 stlab_v2_task_concept_target_type_offset + sizeof(stlab_v2_task_concept::target_type_t),
117 alignof(stlab_v2_task_concept::pointer_t));
118inline constexpr auto stlab_v2_task_concept_const_pointer_offset = stlab_v2_align_offset(
119 stlab_v2_task_concept_pointer_offset + sizeof(stlab_v2_task_concept::pointer_t),
120 alignof(stlab_v2_task_concept::const_pointer_t));
121inline constexpr auto stlab_v2_task_concept_alignment = std::max(
122 {alignof(stlab_v2_task_concept::dtor_t), alignof(stlab_v2_task_concept::move_ctor_t),
123 alignof(stlab_v2_task_concept::target_type_t), alignof(stlab_v2_task_concept::pointer_t),
124 alignof(stlab_v2_task_concept::const_pointer_t)});
125
126} // namespace execution_detail
127
128// An incompatible operation-table change requires a new versioned type instead of relaxing the v2
129// canaries below to accept a different layout.
130static_assert(std::is_standard_layout_v<stlab_v2_task_concept>);
131static_assert(alignof(stlab_v2_task_concept) == execution_detail::stlab_v2_task_concept_alignment);
132static_assert(std::is_same_v<decltype(stlab_v2_task_concept::dtor), stlab_v2_task_concept::dtor_t>);
133static_assert(
134 std::is_same_v<decltype(stlab_v2_task_concept::move_ctor), stlab_v2_task_concept::move_ctor_t>);
135static_assert(std::is_same_v<decltype(stlab_v2_task_concept::target_type),
136 stlab_v2_task_concept::target_type_t>);
137static_assert(
138 std::is_same_v<decltype(stlab_v2_task_concept::pointer), stlab_v2_task_concept::pointer_t>);
139static_assert(std::is_same_v<decltype(stlab_v2_task_concept::const_pointer),
140 stlab_v2_task_concept::const_pointer_t>);
141static_assert(offsetof(stlab_v2_task_concept, dtor) == 0);
142static_assert(offsetof(stlab_v2_task_concept, move_ctor) ==
143 execution_detail::stlab_v2_task_concept_move_ctor_offset);
144static_assert(offsetof(stlab_v2_task_concept, target_type) ==
145 execution_detail::stlab_v2_task_concept_target_type_offset);
146static_assert(offsetof(stlab_v2_task_concept, pointer) ==
147 execution_detail::stlab_v2_task_concept_pointer_offset);
148static_assert(offsetof(stlab_v2_task_concept, const_pointer) ==
149 execution_detail::stlab_v2_task_concept_const_pointer_offset);
150static_assert(sizeof(stlab_v2_task_concept) ==
151 execution_detail::stlab_v2_align_offset(
152 execution_detail::stlab_v2_task_concept_const_pointer_offset +
153 sizeof(stlab_v2_task_concept::const_pointer_t),
154 alignof(stlab_v2_task_concept)));
155
156/**************************************************************************************************/
157
163template <bool NoExcept, class R, class... Args>
164class task_ {
165public:
170
172 using invoke_t = R (*)(void*, Args...) noexcept(NoExcept);
173
174private:
175 template <class F>
176 constexpr static bool maybe_empty =
177 std::is_pointer_v<std::decay_t<F>> || std::is_member_pointer_v<std::decay_t<F>> ||
178 std::is_same_v<std::function<R(Args...)>, std::decay_t<F>>;
179
180 template <class F>
181 constexpr static auto is_empty(const F& f) -> std::enable_if_t<maybe_empty<F>, bool> {
182 return !f;
183 }
184
185 template <class F>
186 constexpr static auto is_empty(const F&) -> std::enable_if_t<!maybe_empty<F>, bool> {
187 return false;
188 }
189
190 template <class F, bool Small>
191 struct model;
192
193 template <class F>
194 struct model<F, true> {
195 template <class G> // for forwarding
196 model(G&& f) : _f(std::forward<G>(f)) {}
197 model(model&&) noexcept = delete;
198
199 static void dtor(void* self) noexcept { static_cast<model*>(self)->~model(); }
200 static void move_ctor(void* self, void* p) noexcept {
201 new (p) model(std::move(static_cast<model*>(self)->_f));
202 }
203
204 /*
205 NOTE (sean-parent): `Args` are _not_ universal references. This is a `concrete`
206 interface for the model. Do not add `&&`, that would make it an rvalue reference.
207 The `forward<Args>` here is correct. We are forwarding from the client defined
208 signature to the actual captured model.
209 */
210
211 // NOLINTNEXTLINE(performance-unnecessary-value-param)
212 static auto invoke(void* self, Args... args) noexcept(NoExcept) -> R {
213 return std::invoke(static_cast<model*>(self)->_f, std::forward<Args>(args)...);
214 }
215
216 static auto target_type() noexcept -> const std::type_info& { return typeid(F); }
217 static auto pointer(void* self) noexcept -> void* { return &static_cast<model*>(self)->_f; }
218 static auto const_pointer(const void* self) noexcept -> const void* {
219 return &static_cast<const model*>(self)->_f;
220 }
221#if defined(__GNUC__) && __GNUC__ < 7 && !defined(__clang__)
222 static const concept_t _vtable;
223 static const invoke_t _invoke;
224#else
225 static constexpr concept_t _vtable = {dtor, move_ctor, target_type, pointer, const_pointer};
226 static constexpr invoke_t _invoke = invoke;
227#endif
228 F _f;
229 };
230
231 template <class F>
232 struct model<F, false> {
233 template <class G> // for forwarding
234 model(G&& f) : _p(std::make_unique<F>(std::forward<G>(f))) {}
235 model(model&&) noexcept = default;
236
237 static void dtor(void* self) noexcept { static_cast<model*>(self)->~model(); }
238 static void move_ctor(void* self, void* p) noexcept {
239 new (p) model(std::move(*static_cast<model*>(self)));
240 }
241
242 /*
243 NOTE (sean-parent): `Args` are _not_ universal references. This is a `concrete`
244 interface for the model. Do not add `&&`, that would make it an rvalue reference.
245 The `forward<Args>` here is correct. We are forwarding from the client defined
246 signature to the actual captured model.
247 */
248
249 static auto invoke(void* self, Args... args) noexcept(NoExcept) -> R {
250 return std::invoke(*static_cast<model*>(self)->_p, std::forward<Args>(args)...);
251 }
252
253 static auto target_type() noexcept -> const std::type_info& { return typeid(F); }
254 static auto pointer(void* self) noexcept -> void* {
255 return static_cast<model*>(self)->_p.get();
256 }
257 static auto const_pointer(const void* self) noexcept -> const void* {
258 return static_cast<const model*>(self)->_p.get();
259 }
260
261#if defined(__GNUC__) && __GNUC__ < 7 && !defined(__clang__)
262 static const concept_t _vtable;
263 static const invoke_t _invoke;
264#else
265 static constexpr concept_t _vtable = {dtor, move_ctor, target_type, pointer, const_pointer};
266 static constexpr invoke_t _invoke = invoke;
267#endif
268
269 std::unique_ptr<F> _p;
270 };
271
272 // empty (default) vtable
273 static void dtor(void*) noexcept {}
274 static void move_ctor(void*, void*) noexcept {}
275 // NOLINTNEXTLINE(performance-unnecessary-value-param)
276 static auto invoke(void*, Args...) noexcept(NoExcept) -> R {
277 if constexpr (NoExcept) {
278 try {
279 throw std::bad_function_call();
280 } catch (...) {
281 std::terminate();
282 }
283 } else {
284 throw std::bad_function_call();
285 }
286 }
287 static auto target_type_() noexcept -> const std::type_info& { return typeid(void); }
288 static auto pointer(void*) noexcept -> void* { return nullptr; }
289 static auto const_pointer(const void*) noexcept -> const void* { return nullptr; }
290
291#if defined(__GNUC__) && __GNUC__ < 7 && !defined(__clang__)
292 static const concept_t _vtable;
293#else
294 static constexpr concept_t _vtable = {dtor, move_ctor, target_type_, pointer, const_pointer};
295#endif
296
297 /*
298 The layout of this object is going to be:
299 _vtable_ptr
300 _invoke
301 _model
302
303 Because the model is going to be max-aligned, it will leave a gap between the vtable_ptr and
304 the _model, we fill that gap by lifting the _invoke pointer into it from the vtable. This means
305 invoke calls require one less indirection.
306
307 The size of the model is big enough for 6 pointers or (2 max aligned object - 2 pointers)
308 (which would typically be 2 pointers). The rational here is that we want the total object size
309 to be a power of 2, for the object but the model store to be large enough to hold 2
310 weak-pointers (which is 4 pointers total), so we give things a little extra room.
311 */
312
313 const concept_t* _vtable_ptr = &_vtable;
314 invoke_t _invoke = invoke;
316 std::array<unsigned char, stlab_v2_task_storage_size> _model;
317
318public:
319 using result_type = R;
320
323 [[nodiscard]] auto relocation_concept() const noexcept -> const concept_t* {
324 return _vtable_ptr;
325 }
326
328 [[nodiscard]] auto relocation_invoke() const noexcept -> invoke_t { return _invoke; }
329
333 [[nodiscard]] auto relocation_source() noexcept -> void* { return &_model; }
334
342 task_(const concept_t* vtable_ptr, invoke_t invoke_fn, void* source) noexcept :
343 _vtable_ptr(vtable_ptr), _invoke(invoke_fn) {
344 vtable_ptr->move_ctor(source, &_model);
345 }
346
347 constexpr task_() noexcept = default;
348 constexpr task_(std::nullptr_t) noexcept : task_() {}
349 task_(const task_&) = delete;
350 task_(const task_&&) = delete;
351 task_(task_&& x) noexcept : _vtable_ptr(x._vtable_ptr), _invoke(x._invoke) {
352 _vtable_ptr->move_ctor(&x._model, &_model);
353 }
354
355 template <class F,
356 std::enable_if_t<!NoExcept || std::is_nothrow_invocable_v<F, Args...>, bool> = true>
357 task_(F&& f) {
358 using small_t = model<std::decay_t<F>, true>;
359 using large_t = model<std::decay_t<F>, false>;
360 using model_t =
361 std::conditional_t<(sizeof(small_t) <= stlab_v2_task_storage_size) &&
362 (alignof(small_t) <= stlab_v2_task_storage_alignment) &&
363 std::is_nothrow_move_constructible_v<std::decay_t<F>>,
364 small_t, large_t>;
365
366 if (is_empty(f)) return;
367
368 new (&_model) model_t(std::forward<F>(f));
369 _vtable_ptr = &model_t::_vtable;
370 _invoke = &model_t::invoke;
371 }
372
373 ~task_() { _vtable_ptr->dtor(&_model); };
374
375 auto operator=(const task_&) -> task_& = delete;
376
377 auto operator=(task_&& x) noexcept -> task_& {
378 if (this == &x) return *this;
379 _vtable_ptr->dtor(&_model);
380 _vtable_ptr = x._vtable_ptr;
381 _invoke = x._invoke;
382 _vtable_ptr->move_ctor(&x._model, &_model);
383 return *this;
384 }
385
386 auto operator=(std::nullptr_t) noexcept -> task_& { return *this = task_(); }
387
388 template <class F>
389 auto operator=(F&& f)
390 -> std::enable_if_t<!NoExcept || std::is_nothrow_invocable_v<decltype(f), Args...>,
391 task_&> {
392 return *this = task_(std::forward<F>(f));
393 }
394
395 void swap(task_& x) noexcept { std::swap(*this, x); }
396
397 explicit operator bool() const { return _vtable_ptr->const_pointer(&_model) != nullptr; }
398
399 [[nodiscard]] auto target_type() const noexcept -> const std::type_info& {
400 return _vtable_ptr->target_type();
401 }
402
403 template <class T>
404 auto target() -> T* {
405 return (target_type() == typeid(T)) ? static_cast<T*>(_vtable_ptr->pointer(&_model)) :
406 nullptr;
407 }
408
409 template <class T>
410 [[nodiscard]] auto target() const -> const T* {
411 return (target_type() == typeid(T)) ?
412 static_cast<const T*>(_vtable_ptr->const_pointer(&_model)) :
413 nullptr;
414 }
415
416 template <class... Brgs>
417 auto operator()(Brgs&&... brgs) noexcept(NoExcept) -> R {
418 return _invoke(&_model, std::forward<Brgs>(brgs)...);
419 }
420
421 friend inline void swap(task_& x, task_& y) noexcept { return x.swap(y); }
422 friend inline auto operator==(const task_& x, std::nullptr_t) -> bool {
423 return !static_cast<bool>(x);
424 }
425 friend inline auto operator==(std::nullptr_t, const task_& x) -> bool {
426 return !static_cast<bool>(x);
427 }
428 friend inline auto operator!=(const task_& x, std::nullptr_t) -> bool {
429 return static_cast<bool>(x);
430 }
431 friend inline auto operator!=(std::nullptr_t, const task_& x) -> bool {
432 return static_cast<bool>(x);
433 }
434};
435
436/**************************************************************************************************/
437
438template <template <bool, class, class...> class T, class F>
440
441template <template <bool, class, class...> class T, class R, class... Args>
442struct noexcept_deducer<T, R(Args...)> {
443 using type = T<false, R, Args...>;
444};
445
446template <template <bool, class, class...> class T, class R, class... Args>
447struct noexcept_deducer<T, R(Args...) noexcept> {
448 using type = T<true, R, Args...>;
449};
450
452template <class F>
454
455/**************************************************************************************************/
456
458
459STLAB_EXECUTION_VERSION_NAMESPACE_END()
460} // namespace stlab
461
462/**************************************************************************************************/
463
464#endif
465
466/**************************************************************************************************/
Type-erased, move-only callable with signature R(Args...) (or noexcept variant).
Definition task.hpp:164
auto relocation_invoke() const noexcept -> invoke_t
Returns the function used to invoke this task's target.
Definition task.hpp:328
R(*)(void *, Args...) noexcept(NoExcept) invoke_t
Function used to invoke a task's target, valid for as long as the target is live.
Definition task.hpp:172
task_(const concept_t *vtable_ptr, invoke_t invoke_fn, void *source) noexcept
Constructs a task by relocating the target described by (vtable_ptr, invoke_fn) out of source,...
Definition task.hpp:342
stlab_v2_task_concept concept_t
Stable vtable describing how to move-construct, destroy, and introspect a task's target,...
Definition task.hpp:169
auto relocation_concept() const noexcept -> const concept_t *
Returns the vtable needed to relocate, invoke, or destroy this task's target without depending on its...
Definition task.hpp:323
auto relocation_source() noexcept -> void *
Returns the address of this task's inline model storage.
Definition task.hpp:333
constexpr std::size_t stlab_v2_task_storage_alignment
Alignment required by a v2 task's relocatable inline model.
Definition task.hpp:84
constexpr std::size_t stlab_v2_task_storage_size
Number of bytes reserved for a v2 task's relocatable inline model.
Definition task.hpp:79
typename noexcept_deducer< task_, F >::type task
task_ with noexcept deduced from the function type F (e.g. void() vs void() noexcept).
Definition task.hpp:453
Definition default_executor.hpp:49
Definition task.hpp:439
Versioned operation table used to relocate a task target across the shared executor ABI.
Definition task.hpp:64