Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
thread_posix.c
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <[email protected]>
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19/** @file
20 POSIX implementation of the thread-level operations in `serene/rt/fiber/thread.h`, realised with
21 pthreads. The pthreads mutex and condition functions return their error code as the return value
22 and leave the global `errno` untouched. Every such code is mapped to an `srn_thread_status_t` so no
23 pthread code escapes the public API. The raw code is also logged through `THREAD_LOG`, which is a
24 debug-only diagnostic and not part of the interface.
25*/
26
27// POSIX thread backend, pthreads. On a target without pthreads (Windows) this file compiles to
28// nothing and a sibling backend should supply the srn_thread_*/srn_mutex_*/srn_cond_* operations
29// instead. The selection is by target macro, so the whole runtime source set can be handed to a
30// cross compiler and only the matching backend emits symbols. The include of thread.h sits inside
31// the guard because it refuses to compile off a supported threading platform.
32#if defined(__unix__) || defined(__APPLE__)
33
34# include <errno.h>
35
37# include "serene/utils.h"
38
39# define THREAD_LOG(FMT, ...) DBG("THREAD", FMT __VA_OPT__(, ) __VA_ARGS__)
40
41/**
42 * Map a pthreads return code onto the neutral status. Anything without a dedicated code (`EDEADLK`,
43 * `EINVAL`, `ESRCH`, and so on) becomes `SRN_THREAD_ERROR`. The precise code is recovered from the
44 * THREAD_LOG line at the call site.
45 */
46static srn_thread_status_t status_from_errno(int rc) {
47 switch (rc) {
48 case 0:
49 return SRN_THREAD_OK;
50 case EAGAIN:
51 return SRN_THREAD_AGAIN;
52 case ENOMEM:
53 return SRN_THREAD_NOMEM;
54 case EPERM:
55 return SRN_THREAD_PERM;
56 default:
57 return SRN_THREAD_ERROR;
58 }
59}
60
61/**
62 * `pthreads` run a thread through a `void *(*)(void *)` routine, while the modeled operation runs a
63 * `void (*)(void *)`. This trampoline bridges the two, it reads the function and argument the spawn
64 * recorded on the thread, runs them, and returns the null pthreads expects.
65 */
66static void *posix_trampoline(void *arg) {
67 srn_thread_t *t = arg;
68 t->fn(t->arg);
69 return nullptr;
70}
71
72srn_thread_status_t srn_thread_spawn(srn_thread_t *t, void (*fn)(void *), void *arg) {
74 PANIC_IF_NULL(fn);
75
76 t->fn = fn;
77 t->arg = arg;
78
79 int rc = pthread_create(&t->handle, nullptr, posix_trampoline, t);
80 if (rc != 0) {
81 THREAD_LOG("pthread_create failed (errno %d)", rc);
82 }
83 return status_from_errno(rc);
84}
85
88 // The thread's return value is discarded. The trampoline always returns null.
89 int rc = pthread_join(t->handle, nullptr);
90 if (rc != 0) {
91 THREAD_LOG("pthread_join failed (errno %d)", rc);
92 }
93 return status_from_errno(rc);
94}
95
98 int rc = pthread_mutex_init(&m->handle, nullptr);
99 if (rc != 0) {
100 THREAD_LOG("pthread_mutex_init failed (errno %d)", rc);
101 }
102 return status_from_errno(rc);
103}
104
106 PANIC_IF_NULL(m);
107 int rc = pthread_mutex_lock(&m->handle);
108 if (rc != 0) {
109 THREAD_LOG("pthread_mutex_lock failed (errno %d)", rc);
110 }
111 return status_from_errno(rc);
112}
113
115 PANIC_IF_NULL(m);
116 int rc = pthread_mutex_unlock(&m->handle);
117 if (rc != 0) {
118 THREAD_LOG("pthread_mutex_unlock failed (errno %d)", rc);
119 }
120 return status_from_errno(rc);
121}
122
124 PANIC_IF_NULL(m);
125 int rc = pthread_mutex_destroy(&m->handle);
126 if (rc != 0) {
127 THREAD_LOG("pthread_mutex_destroy failed (errno %d)", rc);
128 }
129 return status_from_errno(rc);
130}
131
133 PANIC_IF_NULL(c);
134 int rc = pthread_cond_init(&c->handle, nullptr);
135 if (rc != 0) {
136 THREAD_LOG("pthread_cond_init failed (errno %d)", rc);
137 }
138 return status_from_errno(rc);
139}
140
142 PANIC_IF_NULL(c);
143 PANIC_IF_NULL(m);
144 int rc = pthread_cond_wait(&c->handle, &m->handle);
145 if (rc != 0) {
146 THREAD_LOG("pthread_cond_wait failed (errno %d)", rc);
147 }
148 return status_from_errno(rc);
149}
150
152 PANIC_IF_NULL(c);
153 int rc = pthread_cond_signal(&c->handle);
154 if (rc != 0) {
155 THREAD_LOG("pthread_cond_signal failed (errno %d)", rc);
156 }
157 return status_from_errno(rc);
158}
159
161 PANIC_IF_NULL(c);
162 int rc = pthread_cond_broadcast(&c->handle);
163 if (rc != 0) {
164 THREAD_LOG("pthread_cond_broadcast failed (errno %d)", rc);
165 }
166 return status_from_errno(rc);
167}
168
170 PANIC_IF_NULL(c);
171 int rc = pthread_cond_destroy(&c->handle);
172 if (rc != 0) {
173 THREAD_LOG("pthread_cond_destroy failed (errno %d)", rc);
174 }
175 return status_from_errno(rc);
176}
177
178#endif // POSIX thread backend
pthread_cond_t handle
Definition thread.h:55
pthread_mutex_t handle
Definition thread.h:51
void * arg
Definition thread.h:47
pthread_t handle
Definition thread.h:41
void(* fn)(void *)
The function the thread runs and its argument, recorded by srn_thread_spawn and read once by the back...
Definition thread.h:46
srn_thread_t, srn_mutex_t, and srn_cond_t model the thread-level operations the runtime needs,...
srn_thread_status_t srn_mutex_destroy(srn_mutex_t *m)
Release a mutex's resources.
srn_thread_status_t srn_mutex_init(srn_mutex_t *m)
srn_thread_status_t srn_cond_destroy(srn_cond_t *c)
Release a condition's resources.
srn_thread_status_t srn_thread_join(srn_thread_t *t)
Block until the thread started for t returns.
srn_thread_status_t srn_mutex_unlock(srn_mutex_t *m)
srn_thread_status_t
Result of a thread operation.
Definition thread.h:61
@ SRN_THREAD_AGAIN
The system lacked the resources to start the thread, such as a per-process thread limit.
Definition thread.h:65
@ SRN_THREAD_OK
Definition thread.h:62
@ SRN_THREAD_ERROR
A failure the backend could not map to the above.
Definition thread.h:71
@ SRN_THREAD_PERM
The caller lacks permission for the requested operation.
Definition thread.h:69
@ SRN_THREAD_NOMEM
Out of memory.
Definition thread.h:67
srn_thread_status_t srn_cond_wait(srn_cond_t *c, srn_mutex_t *m)
Release m, sleep until notified, then re-acquire m before returning.
srn_thread_status_t srn_mutex_lock(srn_mutex_t *m)
srn_thread_status_t srn_cond_init(srn_cond_t *c)
srn_thread_status_t srn_cond_notify_one(srn_cond_t *c)
Wake one waiter.
srn_thread_status_t srn_cond_notify_all(srn_cond_t *c)
Wake every waiter.
srn_thread_status_t srn_thread_spawn(srn_thread_t *t, void(*fn)(void *), void *arg)
Run fn(arg) on a new OS thread.
#define PANIC_IF_NULL(ptr)
Definition utils.h:66