1 #ifndef BMTASK__H__INCLUDED__
2 #define BMTASK__H__INCLUDED__
3 /*
4 Copyright(c) 2020 Anatoliy Kuznetsov(anatoliy_kuznetsov at yahoo.com)
5 
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9 
10     http://www.apache.org/licenses/LICENSE-2.0
11 
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 
18 For more information please visit:  http://bitmagic.io
19 */
20 
21 /*! \file bmtask.h
22     \brief Task definitions for parallel programming with BitMagic
23 
24     The design intent is to make tasks reasonably compatible with
25     different threading and execution models, possibly with different
26     languages and non-C++ runtimes.
27 
28 */
29 #include <atomic>
30 
31 #include "bmbuffer.h"
32 
33 namespace bm
34 {
35 
36 /** @defgroup bmtasks Task parallel programming
37     Task parallel programming compatible with different execution models
38     and runtimes
39 
40     @ingroup bmagic
41 */
42 
43 /** Typedef for a call-back function pointer (pthread conformant signature)
44     @ingroup bmtasks
45 */
46 typedef void* (*task_func_type)(void*);
47 
48 /**
49     Descriptor for a task as a callback function, arguments, result.
50 
51     Structure contains pointers but being a descriptor it does not own
52     their scope or nature (how it is allocated).
53 
54     @ingroup bmtasks
55 */
56 struct task_description
57 {
58     enum task_flags
59     {
60         no_flag = 0,     ///< no flag specified
61         barrier_ok = 1u,  ///< barrier waits all prev.tasks done without error
62         barrier_any = (1u << 1), ///< barrier waits all prev tasks done (success or not)
63         barrier_ok_delayed = (1u << 2)
64     };
65 
66     task_func_type  func;      ///< pthread-like function callback
67     void*           argp;      ///< arg pointer
68     void*           ret;       ///< ret pointer
69 
70     void*           ctx0;      ///< reserved
71     void*           ctx1;      ///< reserved
72     bm::id64_t      param0;    ///< reserved
73 
74     /// Union to add extra flexible payload to tasks
75     union
76     {
77         int                i32;;
78         unsigned           u32;
79         unsigned long long u64;
80         float              fp32;
81         double             fp64;
82         void*              void_ptr;
83     } payload0, payload1;
84 
85     bm::id64_t              flags;     ///< task flags to designate barriers
86     unsigned                err_code;  ///< error code
87     std::atomic_uint        done;      ///< 0 - pending
88 
89     // ----------------------------------------------------
90     // Construction
91     //
task_descriptiontask_description92     task_description() BMNOEXCEPT {}
93 
task_descriptiontask_description94     task_description(const task_description& td) BMNOEXCEPT
95     {
96         func = td.func;
97         argp = td.argp;
98         ret  = td.ret;
99         ctx0 = td.ctx0;
100         ctx1 = td.ctx1;
101         param0 = td.param0;
102 
103         payload0 = td.payload0;
104         payload1 = td.payload1;
105 
106         flags = td.flags;
107 
108         err_code = td.err_code;
109         done.store(td.done.load()); // atomic operation
110     }
111 
112     task_description(task_func_type  f, void* argptr = 0) BMNOEXCEPT
113     {
114         init(f, argptr, 0, 0, 0);
115     }
116 
inittask_description117     void init(task_func_type  f, void* argptr,
118               void* c0, void* c1, bm::id64_t p0) BMNOEXCEPT
119     {
120         func = f; argp = argptr;
121         ret = 0; ctx0 = c0; ctx1 = c1;
122         param0 = p0; flags = 0; err_code = 0;
123         done = 0;
124         payload0.u64 = payload1.u64 = 0;
125     }
126 };
127 
128 /**
129     Interface definition (base class) for a group of tasks (batch)
130     @ingroup bmtasks
131  */
132 class task_batch_base
133 {
134 public:
135     typedef unsigned size_type;
136 public:
~task_batch_base()137     virtual ~task_batch_base() {}
138 
139     /// Return size of batch
140     virtual size_type size() = 0;
141 
142     /// Get task by index in the batch
143     /// @param task_idx - task index in the batch
144     /// @return task description
145     virtual bm::task_description* get_task(size_type task_idx) = 0;
146 
147 };
148 
149 /**
150     Basic implementation for collection of tasks for parallel execution
151     @ingroup bmtasks
152  */
153 template<typename BVAlloc>
154 class task_batch : public task_batch_base
155 {
156 public:
157     typedef BVAlloc                     bv_allocator_type;
158     typedef task_batch_base::size_type  size_type;
159     typedef
160     bm::heap_vector<bm::task_description, bv_allocator_type, true> task_vector_type;
161 
162 public:
163 
164     /// task_batch_base intreface implementation
165     //@{
size()166     virtual size_type size() { return (size_type) task_vect_.size(); }
get_task(size_type task_idx)167     virtual bm::task_description* get_task(size_type task_idx)
168         { return &task_vect_.at(task_idx); }
169     //@}
170 
171     /// Get access to internal task vector
172     ///
get_task_vector()173     task_vector_type& get_task_vector()  BMNOEXCEPT { return task_vect_; }
get_task_vector()174     const task_vector_type& get_task_vector() const  BMNOEXCEPT
175         { return task_vect_; }
176 
177 protected:
178     task_vector_type      task_vect_; ///< list of tasks
179 };
180 
181 
182 /**
183     Run task batch sequentially
184 
185     Function is used for testing and debugging purposes or as a reference
186     to implement custom parallel executors
187 
188     @param tasks - collection of tasks to run
189     @ingroup bmtasks
190  */
191 inline
run_task_batch(task_batch_base & tasks)192 void run_task_batch(task_batch_base & tasks)
193 {
194     task_batch_base::size_type batch_size = tasks.size();
195     for (task_batch_base::size_type i = 0; i < batch_size; ++i)
196     {
197         bm::task_description* tdescr = tasks.get_task(i);
198         tdescr->argp = tdescr; // restore the self referenece
199         tdescr->ret = tdescr->func(tdescr->argp);
200         tdescr->done = 1;
201     } // for
202 }
203 
204 
205 /**
206     "noexcept" traits detection for T::lock()
207     @internal
208     @ingroup bmtasks
209  */
210 template <typename T>
211 struct is_lock_noexcept
212 {
213 #if BM_DONT_WANT_TYPE_TRAITS_HEADER // not used
214     constexpr static bool value = noexcept(((T*)nullptr)->lock());
215 #else
216     constexpr static bool value = noexcept(std::declval<T>().lock());
217 #endif
218 };
219 
220 /**
221     Simple scoped lock guard
222     @internal
223     @ingroup bmtasks
224  */
225 template<typename Lock> class lock_guard
226 {
227 public:
lock_guard(Lock & lk)228     lock_guard(Lock& lk) noexcept(bm::is_lock_noexcept<Lock>::value)
229         : lk_(lk) {
230         lk_.lock();
231     }
~lock_guard()232     ~lock_guard() { lk_.unlock(); }
233 private:
234     lock_guard(const lock_guard<Lock>&) = delete;
235     lock_guard<Lock>& operator=(const lock_guard<Lock>&) = delete;
236 private:
237     Lock& lk_;
238 };
239 
240 
241 
242 } // namespace bm
243 
244 #endif
245 
246