1 /*
2 Copyright (c) 2005-2023 Intel Corporation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 #include "oneapi/tbb/detail/_config.h"
18 #include "oneapi/tbb/detail/_template_helpers.h"
19
20 #include "oneapi/tbb/global_control.h"
21 #include "oneapi/tbb/tbb_allocator.h"
22 #include "oneapi/tbb/spin_mutex.h"
23
24 #include "governor.h"
25 #include "threading_control.h"
26 #include "market.h"
27 #include "misc.h"
28
29 #include <atomic>
30 #include <set>
31
32 namespace tbb {
33 namespace detail {
34 namespace r1 {
35
36 //! Comparator for a set of global_control objects
37 struct control_storage_comparator {
38 bool operator()(const d1::global_control* lhs, const d1::global_control* rhs) const;
39 };
40
41 class control_storage {
42 friend struct global_control_impl;
43 friend std::size_t global_control_active_value(int);
44 friend void global_control_lock();
45 friend void global_control_unlock();
46 friend std::size_t global_control_active_value_unsafe(d1::global_control::parameter);
47 protected:
48 std::size_t my_active_value{0};
49 std::set<d1::global_control*, control_storage_comparator, tbb_allocator<d1::global_control*>> my_list{};
50 spin_mutex my_list_mutex{};
51 public:
52 virtual ~control_storage() = default;
53 virtual std::size_t default_value() const = 0;
apply_active(std::size_t new_active)54 virtual void apply_active(std::size_t new_active) {
55 my_active_value = new_active;
56 }
is_first_arg_preferred(std::size_t a,std::size_t b) const57 virtual bool is_first_arg_preferred(std::size_t a, std::size_t b) const {
58 return a>b; // prefer max by default
59 }
active_value()60 virtual std::size_t active_value() {
61 spin_mutex::scoped_lock lock(my_list_mutex); // protect my_list.empty() call
62 return !my_list.empty() ? my_active_value : default_value();
63 }
64
active_value_unsafe()65 std::size_t active_value_unsafe() {
66 return !my_list.empty() ? my_active_value : default_value();
67 }
68 };
69
70 class alignas(max_nfs_size) allowed_parallelism_control : public control_storage {
default_value() const71 std::size_t default_value() const override {
72 return max(1U, governor::default_num_threads());
73 }
is_first_arg_preferred(std::size_t a,std::size_t b) const74 bool is_first_arg_preferred(std::size_t a, std::size_t b) const override {
75 return a<b; // prefer min allowed parallelism
76 }
apply_active(std::size_t new_active)77 void apply_active(std::size_t new_active) override {
78 control_storage::apply_active(new_active);
79 __TBB_ASSERT(my_active_value >= 1, nullptr);
80 // -1 to take external thread into account
81 threading_control::set_active_num_workers(my_active_value - 1);
82 }
active_value()83 std::size_t active_value() override {
84 spin_mutex::scoped_lock lock(my_list_mutex); // protect my_list.empty() call
85 if (my_list.empty()) {
86 return default_value();
87 }
88
89 // non-zero, if market is active
90 const std::size_t workers = threading_control::max_num_workers();
91 // We can't exceed market's maximal number of workers.
92 // +1 to take external thread into account
93 return workers ? min(workers + 1, my_active_value) : my_active_value;
94 }
95 };
96
97 class alignas(max_nfs_size) stack_size_control : public control_storage {
default_value() const98 std::size_t default_value() const override {
99 #if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */
100 static auto ThreadStackSizeDefault = [] {
101 ULONG_PTR hi, lo;
102 GetCurrentThreadStackLimits(&lo, &hi);
103 return hi - lo;
104 }();
105 return ThreadStackSizeDefault;
106 #else
107 return ThreadStackSize;
108 #endif
109 }
apply_active(std::size_t new_active)110 void apply_active(std::size_t new_active) override {
111 control_storage::apply_active(new_active);
112 #if __TBB_WIN8UI_SUPPORT && (_WIN32_WINNT < 0x0A00)
113 __TBB_ASSERT( false, "For Windows 8 Store* apps we must not set stack size" );
114 #endif
115 }
116 };
117
118 class alignas(max_nfs_size) terminate_on_exception_control : public control_storage {
default_value() const119 std::size_t default_value() const override {
120 return 0;
121 }
122 };
123
124 class alignas(max_nfs_size) lifetime_control : public control_storage {
is_first_arg_preferred(std::size_t,std::size_t) const125 bool is_first_arg_preferred(std::size_t, std::size_t) const override {
126 return false; // not interested
127 }
default_value() const128 std::size_t default_value() const override {
129 return 0;
130 }
apply_active(std::size_t new_active)131 void apply_active(std::size_t new_active) override {
132 if (new_active == 1) {
133 // reserve the market reference
134 threading_control::register_lifetime_control();
135 } else if (new_active == 0) { // new_active == 0
136 threading_control::unregister_lifetime_control(/*blocking_terminate*/ false);
137 }
138 control_storage::apply_active(new_active);
139 }
140 };
141
142 static allowed_parallelism_control allowed_parallelism_ctl;
143 static stack_size_control stack_size_ctl;
144 static terminate_on_exception_control terminate_on_exception_ctl;
145 static lifetime_control lifetime_ctl;
146 static control_storage *controls[] = {&allowed_parallelism_ctl, &stack_size_ctl, &terminate_on_exception_ctl, &lifetime_ctl};
147
global_control_lock()148 void global_control_lock() {
149 for (auto& ctl : controls) {
150 ctl->my_list_mutex.lock();
151 }
152 }
153
global_control_unlock()154 void global_control_unlock() {
155 int N = std::distance(std::begin(controls), std::end(controls));
156 for (int i = N - 1; i >= 0; --i) {
157 controls[i]->my_list_mutex.unlock();
158 }
159 }
160
global_control_active_value_unsafe(d1::global_control::parameter param)161 std::size_t global_control_active_value_unsafe(d1::global_control::parameter param) {
162 __TBB_ASSERT_RELEASE(param < d1::global_control::parameter_max, nullptr);
163 return controls[param]->active_value_unsafe();
164 }
165
166 //! Comparator for a set of global_control objects
operator ()(const d1::global_control * lhs,const d1::global_control * rhs) const167 inline bool control_storage_comparator::operator()(const d1::global_control* lhs, const d1::global_control* rhs) const {
168 __TBB_ASSERT_RELEASE(lhs->my_param < d1::global_control::parameter_max , nullptr);
169 return lhs->my_value < rhs->my_value || (lhs->my_value == rhs->my_value && lhs < rhs);
170 }
171
terminate_on_exception()172 bool terminate_on_exception() {
173 return d1::global_control::active_value(d1::global_control::terminate_on_exception) == 1;
174 }
175
176 struct global_control_impl {
177 private:
erase_if_presenttbb::detail::r1::global_control_impl178 static bool erase_if_present(control_storage* const c, d1::global_control& gc) {
179 auto it = c->my_list.find(&gc);
180 if (it != c->my_list.end()) {
181 c->my_list.erase(it);
182 return true;
183 }
184 return false;
185 }
186
187 public:
188
createtbb::detail::r1::global_control_impl189 static void create(d1::global_control& gc) {
190 __TBB_ASSERT_RELEASE(gc.my_param < d1::global_control::parameter_max, nullptr);
191 control_storage* const c = controls[gc.my_param];
192
193 spin_mutex::scoped_lock lock(c->my_list_mutex);
194 if (c->my_list.empty() || c->is_first_arg_preferred(gc.my_value, c->my_active_value)) {
195 // to guarantee that apply_active() is called with current active value,
196 // calls it here and in internal_destroy() under my_list_mutex
197 c->apply_active(gc.my_value);
198 }
199 c->my_list.insert(&gc);
200 }
201
destroytbb::detail::r1::global_control_impl202 static void destroy(d1::global_control& gc) {
203 __TBB_ASSERT_RELEASE(gc.my_param < d1::global_control::parameter_max, nullptr);
204 control_storage* const c = controls[gc.my_param];
205 // Concurrent reading and changing global parameter is possible.
206 spin_mutex::scoped_lock lock(c->my_list_mutex);
207 __TBB_ASSERT(gc.my_param == d1::global_control::scheduler_handle || !c->my_list.empty(), nullptr);
208 std::size_t new_active = (std::size_t)(-1), old_active = c->my_active_value;
209
210 if (!erase_if_present(c, gc)) {
211 __TBB_ASSERT(gc.my_param == d1::global_control::scheduler_handle , nullptr);
212 return;
213 }
214 if (c->my_list.empty()) {
215 __TBB_ASSERT(new_active == (std::size_t) - 1, nullptr);
216 new_active = c->default_value();
217 } else {
218 new_active = (*c->my_list.begin())->my_value;
219 }
220 if (new_active != old_active) {
221 c->apply_active(new_active);
222 }
223 }
224
remove_and_check_if_emptytbb::detail::r1::global_control_impl225 static bool remove_and_check_if_empty(d1::global_control& gc) {
226 __TBB_ASSERT_RELEASE(gc.my_param < d1::global_control::parameter_max, nullptr);
227 control_storage* const c = controls[gc.my_param];
228
229 spin_mutex::scoped_lock lock(c->my_list_mutex);
230 __TBB_ASSERT(!c->my_list.empty(), nullptr);
231 erase_if_present(c, gc);
232 return c->my_list.empty();
233 }
234 #if TBB_USE_ASSERT
is_presenttbb::detail::r1::global_control_impl235 static bool is_present(d1::global_control& gc) {
236 __TBB_ASSERT_RELEASE(gc.my_param < d1::global_control::parameter_max, nullptr);
237 control_storage* const c = controls[gc.my_param];
238
239 spin_mutex::scoped_lock lock(c->my_list_mutex);
240 auto it = c->my_list.find(&gc);
241 if (it != c->my_list.end()) {
242 return true;
243 }
244 return false;
245 }
246 #endif // TBB_USE_ASSERT
247 };
248
create(d1::global_control & gc)249 void __TBB_EXPORTED_FUNC create(d1::global_control& gc) {
250 global_control_impl::create(gc);
251 }
destroy(d1::global_control & gc)252 void __TBB_EXPORTED_FUNC destroy(d1::global_control& gc) {
253 global_control_impl::destroy(gc);
254 }
255
remove_and_check_if_empty(d1::global_control & gc)256 bool remove_and_check_if_empty(d1::global_control& gc) {
257 return global_control_impl::remove_and_check_if_empty(gc);
258 }
259 #if TBB_USE_ASSERT
is_present(d1::global_control & gc)260 bool is_present(d1::global_control& gc) {
261 return global_control_impl::is_present(gc);
262 }
263 #endif // TBB_USE_ASSERT
global_control_active_value(int param)264 std::size_t __TBB_EXPORTED_FUNC global_control_active_value(int param) {
265 __TBB_ASSERT_RELEASE(param < d1::global_control::parameter_max, nullptr);
266 return controls[param]->active_value();
267 }
268
269 } // namespace r1
270 } // namespace detail
271 } // namespace tbb
272