ssa: delete unused label skip_must_be_flat
[ajla.git] / th_os2.c
blobdd6015043c6fce6088d34e318dafc202126a3143
1 /*
2 * Copyright (C) 2024 Mikulas Patocka
4 * This file is part of Ajla.
6 * Ajla is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
9 * version.
11 * Ajla is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along with
16 * Ajla. If not, see <https://www.gnu.org/licenses/>.
19 #include "ajla.h"
21 #include "obj_reg.h"
22 #include "mem_al.h"
24 #include "thread.h"
26 #ifdef THREAD_OS2
28 struct os2_thread {
29 struct list wait_entry;
30 HEV wakeup;
31 HEV terminate;
32 #ifndef HAVE___THREAD
33 uintptr_t tls[OS2_THREAD_KEY_MAX];
34 #endif
35 void (*function)(void *);
36 void *arg;
37 thread_priority_t priority;
40 static struct os2_thread thread_1;
42 #ifndef HAVE___THREAD
43 static os2_tls_key_t os2_tls_used;
44 #endif
46 mutex_t thread_spawn_mutex;
48 #ifdef HAVE__THREADSTORE
50 static void os2_tcb_set(struct os2_thread *tcb)
52 *_threadstore() = tcb;
55 static inline struct os2_thread *os2_tcb(void)
57 return cast_cpp(struct os2_thread *, *_threadstore());
60 #else
62 #define OS2_MAX_THREADS 256
64 static struct os2_thread *os2_thread_array[OS2_MAX_THREADS];
66 static void os2_tcb_set(struct os2_thread *tcb)
68 unsigned id = (unsigned)*_threadid;
69 if (unlikely(id >= OS2_MAX_THREADS))
70 fatal("too high thread id: %u", id);
71 os2_thread_array[id] = tcb;
74 static inline struct os2_thread *os2_tcb(void)
76 unsigned id = (unsigned)*_threadid;
77 ajla_assert(id < OS2_MAX_THREADS, (file_line, "too high thread id: %u", id));
78 return os2_thread_array[id];
81 #endif
83 unsigned thread_concurrency(void)
85 APIRET r;
86 ULONG n_cpus = 0;
87 r = DosQuerySysInfo(26, 26, &n_cpus, sizeof(n_cpus));
88 if (unlikely(r != 0)) {
89 /*warning("DosQuerySysInfo(26) failed: %lu", r);*/
90 return 1;
92 if (unlikely(!n_cpus)) {
93 warning("DosQuerySysInfo(26) returned zero");
94 return 1;
96 #ifdef OS2_MAX_THREADS
97 if (unlikely(n_cpus > OS2_MAX_THREADS / 2))
98 return OS2_MAX_THREADS / 2;
99 #endif
100 return (unsigned)n_cpus;
103 #ifdef OS2_USE_FMUTEX
105 #define do_mutex_init(m) \
106 do { \
107 unsigned r; \
108 r = _fmutex_create(m, 0); \
109 if (unlikely(r != 0)) \
110 fatal("_fmutex_cerate failed at %s: %u", position_string(position_arg), r);\
111 } while (0)
113 #define do_mutex_done(m) \
114 do { \
115 int av; \
116 unsigned r; \
117 av = _fmutex_available(m); \
118 if (unlikely(!av)) \
119 internal(caller_file_line, "mutex_done: _fmutex is still locked");\
120 r = _fmutex_close(m); \
121 if (unlikely(r != 0)) \
122 internal(caller_file_line, "mutex_done: _fmutex_close failed: %u", r);\
123 } while (0)
125 #define do_mutex_lock(m) \
126 do { \
127 unsigned r; \
128 r = _fmutex_request(m, _FMR_IGNINT); \
129 if (unlikely(r != 0)) \
130 internal(caller_file_line, "mutex_lock: _fmutex_request failed: %u", r);\
131 } while (0)
133 #define do_mutex_trylock(m) \
134 do { \
135 unsigned r; \
136 r = _fmutex_request(m, _FMR_IGNINT | _FMR_NOWAIT); \
137 if (unlikely(r != 0)) { \
138 if (likely(r == ERROR_MUTEX_OWNED)) \
139 return false; \
140 internal(caller_file_line, "mutex_trylock: _fmutex_request failed: %u", r);\
142 return true; \
143 } while (0)
145 #define do_mutex_unlock(m) \
146 do { \
147 unsigned r; \
148 r = _fmutex_release(m); \
149 if (unlikely(r != 0)) \
150 internal(caller_file_line, "mutex_unlock: _fmutex_release failed: %u", r);\
151 } while (0)
153 #else
155 #define do_mutex_init(m) \
156 do { \
157 APIRET r; \
158 r = DosCreateMutexSem(NULL, m, 0, FALSE); \
159 if (unlikely(r != 0)) \
160 fatal("DosCreateMutexSem failed at %s: %lu", position_string(position_arg), r);\
161 } while (0)
163 #define do_mutex_done(m) \
164 do { \
165 APIRET r; \
166 r = DosCloseMutexSem(*m); \
167 if (unlikely(r != 0)) \
168 internal(caller_file_line, "mutex_done: DosCloseMutexSem failed: %lu", r);\
169 } while (0)
171 #define do_mutex_lock(m) \
172 do { \
173 APIRET r; \
174 again: \
175 r = DosRequestMutexSem(*m, SEM_INDEFINITE_WAIT); \
176 if (unlikely(r != 0)) { \
177 if (r == ERROR_INTERRUPT || r == ERROR_TIMEOUT) \
178 goto again; \
179 internal(caller_file_line, "mutex_lock: DosRequestMutexSem failed: %lu", r);\
181 } while (0)
183 #define do_mutex_trylock(m) \
184 do { \
185 APIRET r; \
186 again: \
187 r = DosRequestMutexSem(*m, SEM_IMMEDIATE_RETURN); \
188 if (unlikely(r != 0)) { \
189 if (likely(r == ERROR_TIMEOUT)) \
190 return false; \
191 if (r == ERROR_INTERRUPT) \
192 goto again; \
193 internal(caller_file_line, "mutex_trylock: DosRequestMutexSem failed: %lu", r);\
195 return true; \
196 } while (0)
198 #define do_mutex_unlock(m) \
199 do { \
200 APIRET r; \
201 r = DosReleaseMutexSem(*m); \
202 if (unlikely(r != 0)) \
203 internal(caller_file_line, "mutex_unlock: DosReleaseMutexSem failed: %lu", r);\
204 } while (0)
206 #endif
208 #define do_cond_init(c) \
209 do { \
210 mutex_init_position(&(c)->mutex pass_position); \
211 list_init(&(c)->wait_list); \
212 } while (0)
214 #define do_cond_done(c) \
215 do { \
216 mutex_done_position(&(c)->mutex pass_position); \
217 ajla_assert_lo(list_is_empty(&(c)->wait_list), (caller_file_line, "cond_done: wait list is not empty"));\
218 } while (0)
220 #define do_cond_lock(c) \
221 do { \
222 mutex_lock_position(&(c)->mutex pass_position); \
223 } while (0)
225 #define do_cond_unlock(c) \
226 do { \
227 mutex_unlock_position(&(c)->mutex pass_position); \
228 } while (0)
230 #define do_cond_unlock_signal(c) \
231 do { \
232 APIRET r; \
233 struct os2_thread *tcb; \
234 if (unlikely(!list_is_empty(&(c)->wait_list))) { \
235 tcb = get_struct((c)->wait_list.next, struct os2_thread, wait_entry);\
236 list_del(&tcb->wait_entry); \
237 tcb->wait_entry.prev = NULL; \
238 } else { \
239 tcb = NULL; \
241 mutex_unlock_position(&(c)->mutex pass_position); \
242 if (unlikely(tcb != NULL)) { \
243 r = DosPostEventSem(tcb->wakeup); \
244 if (unlikely(r != 0)) \
245 internal(caller_file_line, "cond_unlock_signal: DosPostEventSem failed: %lu", r);\
247 } while (0)
249 #define do_cond_unlock_broadcast(c) \
250 do { \
251 APIRET r; \
252 struct list list; \
253 struct list *l; \
254 list_take(&list, &(c)->wait_list); \
255 for (l = list.next; l != &list; l = l->next) \
256 l->prev = NULL; \
257 mutex_unlock_position(&(c)->mutex pass_position); \
258 while (list.next != &list) { \
259 struct os2_thread *tcb = get_struct(list.next, struct os2_thread, wait_entry);\
260 list.next = tcb->wait_entry.next; \
261 r = DosPostEventSem(tcb->wakeup); \
262 if (unlikely(r != 0)) \
263 internal(caller_file_line, "cond_broadcast: DosPostEventSem failed: %lu", r);\
265 } while (0)
267 #define do_cond_wait(c) \
268 do { \
269 APIRET r; \
270 ULONG sink; \
271 struct os2_thread *tcb; \
272 tcb = os2_tcb(); \
273 list_add(&(c)->wait_list, &tcb->wait_entry); \
274 mutex_unlock_position(&(c)->mutex pass_position); \
275 again: \
276 r = DosWaitEventSem(tcb->wakeup, SEM_INDEFINITE_WAIT); \
277 if (unlikely(r != 0)) { \
278 if (r == ERROR_INTERRUPT || r == ERROR_TIMEOUT) \
279 goto again; \
280 internal(caller_file_line, "cond_wait: DosWaitEventSem failed: %lu", r);\
282 r = DosResetEventSem(tcb->wakeup, &sink); \
283 if (unlikely(r != 0)) \
284 internal(caller_file_line, "cond_wait: DosResetEventSem failed: %lu", r);\
285 mutex_lock_position(&(c)->mutex pass_position); \
286 } while (0)
288 /* warning: this function can end prematurely on ERROR_INTERRUPT */
289 #define do_cond_wait_us(c, us) \
290 do { \
291 APIRET r; \
292 ULONG sink; \
293 struct os2_thread *tcb; \
294 tcb = os2_tcb(); \
295 list_add(&(c)->wait_list, &tcb->wait_entry); \
296 mutex_unlock_position(&(c)->mutex pass_position); \
297 r = DosWaitEventSem(tcb->wakeup, (us + 999) / 1000); \
298 if (likely(r != 0)) { \
299 if (unlikely(!(likely(r == ERROR_TIMEOUT) || r == ERROR_INTERRUPT)))\
300 internal(caller_file_line, "cond_wait_us: DosWaitEventSem 1 failed: %lu", r);\
301 mutex_lock_position(&(c)->mutex pass_position); \
302 if (likely(tcb->wait_entry.prev != NULL)) { \
303 list_del(&tcb->wait_entry); \
304 return false; \
305 } else { \
306 again: \
307 r = DosWaitEventSem(tcb->wakeup, SEM_INDEFINITE_WAIT);\
308 if (unlikely(r != 0)) { \
309 if (r == ERROR_INTERRUPT || r == ERROR_TIMEOUT)\
310 goto again; \
311 internal(caller_file_line, "cond_wait: DosWaitEventSem 2 failed: %lu", r);\
313 r = DosResetEventSem(tcb->wakeup, &sink); \
314 if (unlikely(r != 0)) \
315 internal(caller_file_line, "cond_wait_us: DosResetEventSem 2 failed: %lu", r);\
316 return true; \
318 } else { \
319 r = DosResetEventSem(tcb->wakeup, &sink); \
320 if (unlikely(r != 0)) \
321 internal(caller_file_line, "cond_wait_us: DosResetEventSem 1 failed: %lu", r);\
322 mutex_lock_position(&(c)->mutex pass_position); \
323 return true; \
325 } while (0)
327 static void os2_thread_init(struct os2_thread *tcb, bool t1 argument_position)
329 APIRET r;
330 r = DosCreateEventSem(NULL, &tcb->wakeup, 0, FALSE);
331 if (unlikely(r != 0))
332 fatal("DosCreateEventSem 1 failed at %s: %lu", caller_file_line, r);
333 if (!t1) {
334 r = DosCreateEventSem(NULL, &tcb->terminate, 0, FALSE);
335 if (unlikely(r != 0))
336 fatal("DosCreateEventSem 2 failed at %s: %lu", caller_file_line, r);
338 #ifndef HAVE___THREAD
339 (void)memset(&tcb->tls, 0, sizeof tcb->tls);
340 #endif
343 static void os2_thread_done(struct os2_thread *tcb, bool t1 argument_position)
345 APIRET r;
346 ULONG cnt;
347 r = DosQueryEventSem(tcb->wakeup, &cnt);
348 if (unlikely(r != 0))
349 internal(caller_file_line, "os2_thread_done: DosQueryEventSem 1 failed: %lu", r);
350 if (unlikely(cnt != 0))
351 internal(caller_file_line, "os2_thread_done: wakeup semaphore set: %lu", cnt);
352 r = DosCloseEventSem(tcb->wakeup);
353 if (unlikely(r != 0))
354 internal(caller_file_line, "os2_thread_done: DosCloseEventSem 1 failed: %lu", r);
355 if (!t1) {
356 r = DosQueryEventSem(tcb->terminate, &cnt);
357 if (unlikely(r != 0))
358 internal(caller_file_line, "os2_thread_done: DosQueryEventSem 2 failed: %lu", r);
359 if (unlikely(cnt != 1))
360 internal(caller_file_line, "os2_thread_done: terminate semaphore not set: %lu", cnt);
361 r = DosCloseEventSem(tcb->terminate);
362 if (unlikely(r != 0))
363 internal(caller_file_line, "os2_thread_done: DosCloseEventSem 2 failed: %lu", r);
367 static void os2_thread_function(void *tcb_)
369 APIRET r;
370 struct os2_thread *tcb = cast_cpp(struct os2_thread *, tcb_);
371 ULONG cls;
372 LONG del;
373 os2_tcb_set(tcb);
374 cls = PRTYC_NOCHANGE;
375 del = 0;
376 if (tcb->priority == PRIORITY_TIMER) {
377 #if 0
378 cls = PRTYC_TIMECRITICAL;
379 #else
380 del = 31;
381 #endif
383 r = DosSetPriority(PRTYS_THREAD, cls, del, 0);
384 if (unlikely(r != 0))
385 warning("DosSetPriority(%ld,%lu) failed: %lu", cls, del, r);
386 asm_setup_thread();
387 tcb->function(tcb->arg);
388 tls_destructor_call();
389 r = DosPostEventSem(tcb->terminate);
390 if (unlikely(r != 0))
391 internal(file_line, "os2_thread_function: DosPostEventSem failed: %lu", r);
394 #define do_thread_spawn(t, function, arg, priority, err) \
395 do { \
396 struct os2_thread *tcb; \
397 int btr; \
398 tcb = mem_alloc_mayfail(struct os2_thread *, sizeof(struct os2_thread), err);\
399 if (unlikely(!tcb)) \
400 return false; \
401 os2_thread_init(tcb, false pass_position); \
402 tcb->function = function; \
403 tcb->arg = arg; \
404 tcb->priority = priority; \
405 mutex_lock(&thread_spawn_mutex); \
406 btr = _beginthread(os2_thread_function, NULL, MINIMUM_STACK_SIZE, tcb);\
407 mutex_unlock(&thread_spawn_mutex); \
408 if (unlikely(btr == -1)) { \
409 int er = errno; \
410 ajla_error_t e = error_from_errno(EC_SYSCALL, er); \
411 fatal("_beginthread failed at %s: %d, %s", position_string(position_arg), er, error_decode(e));\
412 mem_free(tcb); \
413 return false; \
415 *(t) = tcb; \
416 } while (0)
418 #define do_thread_join(t) \
419 do { \
420 APIRET r; \
421 struct os2_thread *tcb = *(t); \
422 again: \
423 r = DosWaitEventSem(tcb->terminate, SEM_INDEFINITE_WAIT); \
424 if (unlikely(r != 0)) { \
425 if (r == ERROR_INTERRUPT || r == ERROR_TIMEOUT) \
426 goto again; \
427 internal(caller_file_line, "thread_join: DosWaitEventSem failed: %lu", r);\
429 os2_thread_done(tcb, false pass_position); \
430 mem_free(tcb); \
431 } while (0)
433 #ifndef HAVE___THREAD
435 #define do_tls_init(tl) \
436 do { \
437 ajla_assert_lo(os2_tls_used < OS2_THREAD_KEY_MAX, (caller_file_line, "tls_init: too many tls keys: %d", os2_tls_used));\
438 *(tl) = os2_tls_used++; \
439 } while (0)
441 #define do_tls_done(tl) \
442 do { \
443 ajla_assert_lo(*(tl) < os2_tls_used, (caller_file_line, "tls_done: invalid tls key: %d >= %d", *(tl), os2_tls_used));\
444 } while (0)
446 #define do_tls_get(tl, ret) \
447 do { \
448 ajla_assert(*(tl) < os2_tls_used, (caller_file_line, "tls_get: invalid tls key: %d >= %d", *(tl), os2_tls_used));\
449 *(ret) = os2_tcb()->tls[*(tl)]; \
450 } while (0)
452 #define do_tls_set(tl, val) \
453 do { \
454 ajla_assert(*(tl) < os2_tls_used, (caller_file_line, "tls_set: invalid tls key: %d >= %d", *(tl), os2_tls_used));\
455 os2_tcb()->tls[*(tl)] = (val); \
456 } while (0)
458 #endif
460 #include "th_com.inc"
462 void thread_init(void)
464 #ifndef HAVE___THREAD
465 os2_tls_used = 0;
466 #endif
467 os2_thread_init(&thread_1, true pass_file_line);
468 os2_tcb_set(&thread_1);
469 thread_common_init();
470 mutex_init(&thread_spawn_mutex);
473 void thread_done(void)
475 ajla_assert_lo(os2_tcb() == &thread_1, (file_line, "thread_done: mismatching thread 1: %p != %p", os2_tcb(), &thread_1));
476 mutex_done(&thread_spawn_mutex);
477 thread_common_done();
478 #if defined(DEBUG_LOW_OVERHEAD)
479 os2_tcb_set(NULL);
480 #endif
481 os2_thread_done(&thread_1, true pass_file_line);
484 #endif