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# include <unistd.h>
36
37# ifdef __linux__
38# include <sched.h>
39# endif
40
42# include "serene/utils.h"
43
44# define THREAD_LOG(FMT, ...) DBG("THREAD", FMT __VA_OPT__(, ) __VA_ARGS__)
45
46/**
47 * Map a pthreads return code onto the neutral status. Anything without a dedicated code (`EDEADLK`,
48 * `EINVAL`, `ESRCH`, and so on) becomes `SRN_THREAD_ERROR`. The precise code is recovered from the
49 * THREAD_LOG line at the call site.
50 */
51static srn_thread_status_t status_from_errno(int rc) {
52 switch (rc) {
53 case 0:
54 return SRN_THREAD_OK;
55 case EAGAIN:
56 return SRN_THREAD_AGAIN;
57 case ENOMEM:
58 return SRN_THREAD_NOMEM;
59 case EPERM:
60 return SRN_THREAD_PERM;
61 default:
62 return SRN_THREAD_ERROR;
63 }
64}
65
66/**
67 * `pthreads` run a thread through a `void *(*)(void *)` routine, while the modeled operation runs a
68 * `void (*)(void *)`. This trampoline bridges the two, it reads the function and argument the spawn
69 * recorded on the thread, runs them, and returns the null pthreads expects.
70 */
71static void *posix_trampoline(void *arg) {
72 srn_thread_t *t = arg;
73 t->fn(t->arg);
74 return nullptr;
75}
76
77srn_thread_status_t srn_thread_spawn(srn_thread_t *t, void (*fn)(void *), void *arg) {
79 PANIC_IF_NULL(fn);
80
81 t->fn = fn;
82 t->arg = arg;
83
84 int rc = pthread_create(&t->handle, nullptr, posix_trampoline, t);
85 if (rc != 0) {
86 THREAD_LOG("pthread_create failed (errno %d)", rc);
87 }
88 return status_from_errno(rc);
89}
90
91size_t srn_thread_cpu_count(void) {
92# ifdef __linux__
93 // The affinity mask, not the machine. A process pinned to a subset of the
94 // CPUs (taskset, a cgroup cpuset) should size its pool by that subset,
95 // sysconf below reports the whole machine regardless.
96 cpu_set_t set;
97 if (sched_getaffinity(0, sizeof(set), &set) == 0) {
98 int n = CPU_COUNT(&set);
99 if (n > 0) {
100 return (size_t)n;
101 }
102 }
103# endif
104 long n = sysconf(_SC_NPROCESSORS_ONLN);
105 return n > 0 ? (size_t)n : 1;
106}
107
109 PANIC_IF_NULL(t);
110 // The thread's return value is discarded. The trampoline always returns null.
111 int rc = pthread_join(t->handle, nullptr);
112 if (rc != 0) {
113 THREAD_LOG("pthread_join failed (errno %d)", rc);
114 }
115 return status_from_errno(rc);
116}
117
119 PANIC_IF_NULL(m);
120 int rc = pthread_mutex_init(&m->handle, nullptr);
121 if (rc != 0) {
122 THREAD_LOG("pthread_mutex_init failed (errno %d)", rc);
123 }
124 return status_from_errno(rc);
125}
126
128 PANIC_IF_NULL(m);
129 int rc = pthread_mutex_lock(&m->handle);
130 if (rc != 0) {
131 THREAD_LOG("pthread_mutex_lock failed (errno %d)", rc);
132 }
133 return status_from_errno(rc);
134}
135
137 PANIC_IF_NULL(m);
138 int rc = pthread_mutex_unlock(&m->handle);
139 if (rc != 0) {
140 THREAD_LOG("pthread_mutex_unlock failed (errno %d)", rc);
141 }
142 return status_from_errno(rc);
143}
144
146 PANIC_IF_NULL(m);
147 int rc = pthread_mutex_destroy(&m->handle);
148 if (rc != 0) {
149 THREAD_LOG("pthread_mutex_destroy failed (errno %d)", rc);
150 }
151 return status_from_errno(rc);
152}
153
155 PANIC_IF_NULL(c);
156 int rc = pthread_cond_init(&c->handle, nullptr);
157 if (rc != 0) {
158 THREAD_LOG("pthread_cond_init failed (errno %d)", rc);
159 }
160 return status_from_errno(rc);
161}
162
164 PANIC_IF_NULL(c);
165 PANIC_IF_NULL(m);
166 int rc = pthread_cond_wait(&c->handle, &m->handle);
167 if (rc != 0) {
168 THREAD_LOG("pthread_cond_wait failed (errno %d)", rc);
169 }
170 return status_from_errno(rc);
171}
172
174 PANIC_IF_NULL(c);
175 int rc = pthread_cond_signal(&c->handle);
176 if (rc != 0) {
177 THREAD_LOG("pthread_cond_signal failed (errno %d)", rc);
178 }
179 return status_from_errno(rc);
180}
181
183 PANIC_IF_NULL(c);
184 int rc = pthread_cond_broadcast(&c->handle);
185 if (rc != 0) {
186 THREAD_LOG("pthread_cond_broadcast failed (errno %d)", rc);
187 }
188 return status_from_errno(rc);
189}
190
192 PANIC_IF_NULL(c);
193 int rc = pthread_cond_destroy(&c->handle);
194 if (rc != 0) {
195 THREAD_LOG("pthread_cond_destroy failed (errno %d)", rc);
196 }
197 return status_from_errno(rc);
198}
199
200#endif // POSIX thread backend
int n
Definition acutest.h:525
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)
size_t srn_thread_cpu_count(void)
The number of CPUs the calling process may run threads on, at least 1.
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