Fun 0.41.5
The programming language that makes You have fun
Loading...
Searching...
No Matches
thread_common.c
Go to the documentation of this file.
1/*
2 * This file is part of the Fun programming language.
3 * https://fun-lang.xyz/
4 *
5 * Copyright 2025 Johannes Findeisen <you@hanez.org>
6 * Licensed under the terms of the Apache-2.0 license.
7 * https://opensource.org/license/apache-2-0
8 */
9
23
24/* Cross-platform minimal threading support embedded into vm.c TU */
25
26#include <stdint.h>
27#include <time.h>
28
29#ifdef _WIN32
30#include <windows.h>
31typedef HANDLE fun_thread_handle_t;
32typedef DWORD fun_thread_ret_t;
33#define FUN_THREAD_CALL WINAPI
34#define fun_sleep_ms(ms) Sleep((DWORD)(ms))
35#else
36#include <pthread.h>
37#include <unistd.h>
38typedef pthread_t fun_thread_handle_t;
39typedef void *fun_thread_ret_t;
40#define FUN_THREAD_CALL
41static inline void fun_sleep_ms(long ms) {
42 if (ms <= 0) return;
43 struct timespec ts;
44 ts.tv_sec = ms / 1000;
45 ts.tv_nsec = (ms % 1000) * 1000000L;
46 nanosleep(&ts, NULL);
47}
48#endif
49
50#ifndef FUN_MAX_THREADS
51#define FUN_MAX_THREADS 64
52#endif
53
54typedef struct {
56 int used;
57 int done;
58 Value result; /* owned */
59#ifdef _WIN32
60 DWORD threadId;
61#endif
63
64static FunThreadEntry g_threads[FUN_MAX_THREADS];
65
66#ifdef _WIN32
67static CRITICAL_SECTION g_thr_lock;
68static int g_thr_lock_inited = 0;
69static void fun_thr_lock_init(void) {
70 if (!g_thr_lock_inited) {
71 InitializeCriticalSection(&g_thr_lock);
72 g_thr_lock_inited = 1;
73 }
74}
75static void fun_lock(void) {
76 if (!g_thr_lock_inited) fun_thr_lock_init();
77 EnterCriticalSection(&g_thr_lock);
78}
79static void fun_unlock(void) {
80 LeaveCriticalSection(&g_thr_lock);
81}
82#else
83static pthread_mutex_t g_thr_lock = PTHREAD_MUTEX_INITIALIZER;
84static void fun_lock(void) {
85 pthread_mutex_lock(&g_thr_lock);
86}
87static void fun_unlock(void) {
88 pthread_mutex_unlock(&g_thr_lock);
89}
90#endif
91
92typedef struct {
93 Bytecode *fn; /* function to call */
94 int argc;
95 Value *args; /* array of argc Values (owned by task, will be freed) */
96 int slot; /* registry slot */
97} FunTask;
98
99#ifdef _WIN32
100static fun_thread_ret_t FUN_THREAD_CALL fun_thread_main(LPVOID param)
101#else
102static fun_thread_ret_t fun_thread_main(void *param)
103#endif
104{
105 FunTask *task = (FunTask *)param;
106 VM *tvm = (VM *)malloc(sizeof(VM));
107 if (!tvm) {
108 fprintf(stderr, "Runtime error: failed to allocate thread VM\n");
109 /* Minimal cleanup of task before exiting thread */
110 for (int i = 0; i < task->argc; ++i) free_value(task->args[i]);
111 free(task->args);
112 free(task);
113#ifdef _WIN32
114 return 0;
115#else
116 return NULL;
117#endif
118 }
119 vm_init(tvm);
120
121 /* Build wrapper: LOAD_CONST <fn>; LOAD_CONST <arg0> ...; CALL argc; HALT */
122 Bytecode *wrap = bytecode_new();
123 /* Use make_function constructor which correctly handles the pointer */
124 int cFn = bytecode_add_constant(wrap, make_function(task->fn));
126 for (int i = 0; i < task->argc; ++i) {
127 int cArg = bytecode_add_constant(wrap, deep_copy_value(&task->args[i]));
129 }
132
133 vm_run(tvm, wrap);
134
135 /* Take the top of stack as result if present, else Nil */
136 Value res = make_nil();
137 if (tvm->sp >= 0) {
138 res = deep_copy_value(&tvm->stack[tvm->sp]);
139 }
140
141 /* cleanup */
142 for (int i = 0; i < task->argc; ++i) {
143 free_value(task->args[i]);
144 }
145 free(task->args);
146 bytecode_free(wrap);
147 vm_reset(tvm);
148 free(tvm);
149
150 /* store result */
151 fun_lock();
152 g_threads[task->slot].result = res;
153 g_threads[task->slot].done = 1;
154 fun_unlock();
155
156 free(task);
157
158#ifdef _WIN32
159 return 0;
160#else
161 return NULL;
162#endif
163}
164
165static int fun_alloc_thread_slot(void) {
166 fun_lock();
167 int idx = -1;
168 for (int i = 0; i < FUN_MAX_THREADS; ++i) {
169 if (!g_threads[i].used) {
170 g_threads[i].used = 1;
171 g_threads[i].done = 0;
172 g_threads[i].result = make_nil();
173 idx = i;
174 break;
175 }
176 }
177 fun_unlock();
178 return idx;
179}
180
181static int fun_thread_spawn(Value fnVal, Value argsMaybe, int hasArgs) {
182 if (fnVal.type != VAL_FUNCTION || !fnVal.fn) {
183 fprintf(stderr, "Runtime error: thread_spawn expects Function as first argument\n");
184 return 0;
185 }
186
187 /* Collect args */
188 int argc = 0;
189 Value *args = NULL;
190
191 if (hasArgs) {
192 if (argsMaybe.type == VAL_ARRAY && argsMaybe.arr) {
193 int n = array_length(&argsMaybe);
194 if (n > 0) {
195 args = (Value *)calloc((size_t)n, sizeof(Value));
196 if (!args) n = 0;
197 for (int i = 0; i < n; ++i) {
198 Value vi;
199 if (array_get_copy(&argsMaybe, i, &vi)) {
200 args[i] = deep_copy_value(&vi);
202 } else {
203 args[i] = make_nil();
204 }
205 }
206 argc = n;
207 }
208 } else if (argsMaybe.type != VAL_NIL) {
209 args = (Value *)calloc(1, sizeof(Value));
210 if (args) {
211 args[0] = deep_copy_value(&argsMaybe);
212 argc = 1;
213 }
214 }
215 }
216
217 int slot = fun_alloc_thread_slot();
218 if (slot < 0) {
219 fprintf(stderr, "Runtime error: too many threads\n");
220 /* free args */
221 for (int i = 0; i < argc; ++i)
222 free_value(args[i]);
223 free(args);
224 return 0;
225 }
226
227 FunTask *task = (FunTask *)calloc(1, sizeof(FunTask));
228 if (!task) {
229 for (int i = 0; i < argc; ++i)
230 free_value(args[i]);
231 free(args);
232 fun_lock();
233 g_threads[slot].used = 0;
234 fun_unlock();
235 return 0;
236 }
237 task->fn = fnVal.fn;
238 task->argc = argc;
239 task->args = args;
240 task->slot = slot;
241
242#ifdef _WIN32
243 HANDLE h = CreateThread(NULL, 0, fun_thread_main, (LPVOID)task, 0, &g_threads[slot].threadId);
244 if (!h) {
245 fprintf(stderr, "Runtime error: CreateThread failed\n");
246 for (int i = 0; i < argc; ++i)
247 free_value(args[i]);
248 free(args);
249 free(task);
250 fun_lock();
251 g_threads[slot].used = 0;
252 fun_unlock();
253 return 0;
254 }
255 fun_lock();
256 g_threads[slot].handle = h;
257 fun_unlock();
258#else
259 pthread_t tid;
260 int rc = pthread_create(&tid, NULL, fun_thread_main, (void *)task);
261 if (rc != 0) {
262 fprintf(stderr, "Runtime error: pthread_create failed\n");
263 for (int i = 0; i < argc; ++i)
264 free_value(args[i]);
265 free(args);
266 free(task);
267 fun_lock();
268 g_threads[slot].used = 0;
269 fun_unlock();
270 return 0;
271 }
272 fun_lock();
273 g_threads[slot].handle = tid;
274 fun_unlock();
275#endif
276
277 return slot + 1; /* external thread id: 1..N */
278}
279
280static Value fun_thread_join(int tid) {
282 fprintf(stderr, "Runtime error: thread_join invalid id %d\n", tid);
283 return make_nil();
284 }
285 int idx = tid - 1;
286
287#ifdef _WIN32
288 fun_lock();
289 HANDLE h = g_threads[idx].handle;
290 int used = g_threads[idx].used;
291 fun_unlock();
292 if (!used || !h) return make_nil();
293 WaitForSingleObject(h, INFINITE);
294 CloseHandle(h);
295#else
296 fun_lock();
297 pthread_t h = g_threads[idx].handle;
298 int used = g_threads[idx].used;
299 fun_unlock();
300 if (!used) return make_nil();
301 pthread_join(h, NULL);
302#endif
303
304 /* fetch result and free slot */
305 fun_lock();
306 Value res = deep_copy_value(&g_threads[idx].result);
307 free_value(g_threads[idx].result);
308 g_threads[idx].result = make_nil();
309 g_threads[idx].used = 0;
310 g_threads[idx].done = 0;
311#ifdef _WIN32
312 g_threads[idx].threadId = 0;
313#endif
314 fun_unlock();
315
316 return res;
317}
int res
Definition and.c:34
Bytecode * bytecode_new(void)
Allocate and initialize an empty Bytecode object.
Definition bytecode.c:27
int bytecode_add_instruction(Bytecode *bc, OpCode op, int32_t operand)
Append a single instruction to the instruction stream.
Definition bytecode.c:62
void bytecode_free(Bytecode *bc)
Free a Bytecode and all memory it owns.
Definition bytecode.c:92
int bytecode_add_constant(Bytecode *bc, Value v)
Append a constant to a Bytecode's constant table.
Definition bytecode.c:48
@ OP_CALL
Definition bytecode.h:60
@ OP_LOAD_CONST
Definition bytecode.h:37
@ OP_HALT
Definition bytecode.h:65
Value * args
Definition call.c:31
int rc
int idx
Definition index_of.c:38
int n
Definition insert.c:41
free(vals)
int64_t vi
Definition sclamp.c:31
Value * args
Bytecode * fn
fun_thread_handle_t handle
The Fun virtual machine state.
Definition vm.h:110
int sp
Definition vm.h:112
Value stack[STACK_SIZE]
Definition vm.h:111
Tagged union representing a Fun value.
Definition value.h:68
struct Array * arr
Definition value.h:75
ValueType type
Definition value.h:69
struct Bytecode * fn
Definition value.h:74
#define FUN_THREAD_CALL
pthread_t fun_thread_handle_t
void * fun_thread_ret_t
#define FUN_MAX_THREADS
int tid
Value make_nil(void)
Construct a nil Value.
Definition value.c:126
int array_length(const Value *v)
Get the element count of an array Value.
Definition value.c:176
Value make_function(struct Bytecode *fn)
Construct a function Value referencing bytecode.
Definition value.c:114
Value deep_copy_value(const Value *v)
Deep copy a Value, recursively copying arrays and maps.
Definition value.c:463
void free_value(Value v)
Free dynamic storage owned by a Value.
Definition value.c:517
int array_get_copy(const Value *v, int index, Value *out)
Copy an array element into out.
Definition value.c:192
@ VAL_ARRAY
Definition value.h:55
@ VAL_FUNCTION
Definition value.h:54
@ VAL_NIL
Definition value.h:57
void vm_init(VM *vm)
Initialize a VM instance to its default state.
Definition vm.c:714
#define fprintf
Definition vm.c:200
void vm_reset(VM *vm)
Reset the VM to a clean state.
Definition vm.c:382
void vm_run(VM *vm, Bytecode *entry)
Execute the provided entry bytecode in the VM. Pushes an initial frame and runs until HALT or an unre...