host.c raw
1 #include <stdlib.h>
2 #include <string.h>
3 #include <stdint.h>
4 #include <unistd.h>
5 #include <sys/wait.h>
6 #include <sys/syscall.h>
7 #include <sys/resource.h>
8 #include <sys/mman.h>
9 #include <fcntl.h>
10 #include <pthread.h>
11 #include <stdatomic.h>
12
13 __attribute__((constructor))
14 static void raise_stack_limit(void) {
15 struct rlimit rl;
16 getrlimit(RLIMIT_STACK, &rl);
17 if (rl.rlim_cur < 268435456UL) {
18 rl.rlim_cur = 268435456UL;
19 setrlimit(RLIMIT_STACK, &rl);
20 }
21 }
22
23 int32_t mxc_writefile(const char* path, int32_t pathLen, const char* data, int32_t dataLen, uint32_t mode) {
24 char pbuf[4096];
25 if (pathLen > 4095) pathLen = 4095;
26 memcpy(pbuf, path, pathLen);
27 pbuf[pathLen] = 0;
28 int fd = syscall(SYS_openat, -100, pbuf, O_WRONLY|O_CREAT|O_TRUNC, mode);
29 if (fd < 0) return -1;
30 int32_t written = 0;
31 while (written < dataLen) {
32 long n = syscall(SYS_write, fd, data + written, dataLen - written);
33 if (n <= 0) break;
34 written += (int32_t)n;
35 }
36 syscall(SYS_close, fd);
37 return written;
38 }
39
40 int32_t mxc_mkdir(const char* path, int32_t pathLen, uint32_t mode) {
41 char pbuf[4096];
42 if (pathLen > 4095) return -1;
43 int i;
44 for (i = 1; i <= pathLen; i++) {
45 if (i == pathLen || path[i] == '/') {
46 memcpy(pbuf, path, i);
47 pbuf[i] = 0;
48 syscall(SYS_mkdirat, -100, pbuf, mode);
49 }
50 }
51 return 0;
52 }
53
54 int32_t mxc_readfile(const char* path, int32_t pathLen, char* buf, int32_t bufCap) {
55 char pbuf[4096];
56 if (pathLen > 4095) pathLen = 4095;
57 memcpy(pbuf, path, pathLen);
58 pbuf[pathLen] = 0;
59 int fd = syscall(SYS_openat, -100, pbuf, O_RDONLY, 0);
60 if (fd < 0) return -1;
61 int32_t total = 0;
62 while (total < bufCap) {
63 long n = syscall(SYS_read, fd, buf + total, bufCap - total);
64 if (n <= 0) break;
65 total += (int32_t)n;
66 }
67 syscall(SYS_close, fd);
68 return total;
69 }
70
71 int32_t mxc_filesize(const char* path, int32_t pathLen) {
72 char pbuf[4096];
73 if (pathLen > 4095) pathLen = 4095;
74 memcpy(pbuf, path, pathLen);
75 pbuf[pathLen] = 0;
76 int fd = syscall(SYS_openat, -100, pbuf, O_RDONLY, 0);
77 if (fd < 0) return -1;
78 long size = syscall(SYS_lseek, fd, 0, 2); /* SEEK_END=2 */
79 syscall(SYS_close, fd);
80 return (int32_t)size;
81 }
82
83 int32_t mxc_run(const char** argv, int32_t argc) {
84 int pid = fork();
85 if (pid < 0) return -1;
86 if (pid == 0) {
87 char** args = (char**)malloc((argc + 1) * sizeof(char*));
88 if (!args) _exit(127);
89 int i;
90 for (i = 0; i < argc; i++) args[i] = (char*)argv[i];
91 args[i] = NULL;
92 execvp(args[0], args);
93 _exit(127);
94 }
95 int status;
96 waitpid(pid, &status, 0);
97 if (WIFEXITED(status)) return WEXITSTATUS(status);
98 return -1;
99 }
100
101 int32_t mxc_run_async(const char** argv, int32_t argc) {
102 int pid = fork();
103 if (pid < 0) return -1;
104 if (pid == 0) {
105 char** args = (char**)malloc((argc + 1) * sizeof(char*));
106 if (!args) _exit(127);
107 int i;
108 for (i = 0; i < argc; i++) args[i] = (char*)argv[i];
109 args[i] = NULL;
110 execvp(args[0], args);
111 _exit(127);
112 }
113 return pid;
114 }
115
116 /* Fork without exec. Returns 0 in child, pid in parent, -1 on error.
117 Child inherits the full address space (COW) so all loaded compiler
118 state (importRegistry, type info, etc.) is immediately available. */
119 int32_t mxc_fork(void) {
120 return (int32_t)fork();
121 }
122
123 /* Reap any child. Returns pid on exit 0, -pid on failure, -1 if no children. */
124 int32_t mxc_wait_any(void) {
125 int status;
126 int pid = waitpid(-1, &status, 0);
127 if (pid < 0) return -1;
128 if (WIFEXITED(status) && WEXITSTATUS(status) == 0) return pid;
129 return -pid;
130 }
131
132 struct linux_dirent64 {
133 uint64_t d_ino;
134 int64_t d_off;
135 uint16_t d_reclen;
136 uint8_t d_type;
137 char d_name[];
138 };
139
140 int32_t mxc_listdir(const char* dir, int32_t dirLen, char* buf, int32_t bufCap) {
141 char dbuf[4096];
142 if (dirLen > 4095) dirLen = 4095;
143 memcpy(dbuf, dir, dirLen);
144 dbuf[dirLen] = 0;
145 int fd = syscall(SYS_openat, -100, dbuf, 0x10000, 0); /* AT_FDCWD=-100, O_RDONLY|O_DIRECTORY */
146 if (fd < 0) return 0;
147 int32_t pos = 0;
148 char dbytes[4096];
149 for (;;) {
150 long nread = syscall(SYS_getdents64, fd, dbytes, sizeof(dbytes));
151 if (nread <= 0) break;
152 long offset = 0;
153 while (offset < nread) {
154 struct linux_dirent64* ent = (struct linux_dirent64*)(dbytes + offset);
155 char* name = ent->d_name;
156 if (!(name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0)))) {
157 int32_t nlen = (int32_t)strlen(name);
158 if (pos + nlen + 1 <= bufCap) {
159 memcpy(buf + pos, name, nlen);
160 pos += nlen;
161 buf[pos] = 0;
162 pos++;
163 }
164 }
165 offset += ent->d_reclen;
166 }
167 }
168 syscall(SYS_close, fd);
169 return pos;
170 }
171
172 int32_t mxc_getenv(const char* name, int32_t nameLen, char* buf, int32_t bufCap) {
173 char nbuf[256];
174 if (nameLen > 255) nameLen = 255;
175 memcpy(nbuf, name, nameLen);
176 nbuf[nameLen] = 0;
177 const char* val = getenv(nbuf);
178 if (!val) return 0;
179 int32_t vlen = (int32_t)strlen(val);
180 if (vlen > bufCap) vlen = bufCap;
181 memcpy(buf, val, vlen);
182 return vlen;
183 }
184
185 /* ---------------------------------------------------------------
186 Ring buffer - SPSC lock-free, matches src/runtime/ringbuf.mx
187 protocol: big-endian length prefix, 8-byte aligned messages,
188 monotonically increasing indices with mask-based wrap.
189
190 Layout: [writeIdx:4][readIdx:4][size:4][closed:4][data:size]
191 Messages: [len:4 BE][payload:len][pad to align 8]
192 --------------------------------------------------------------- */
193
194 typedef struct {
195 _Atomic(uint32_t) writeIdx;
196 _Atomic(uint32_t) readIdx;
197 uint32_t size;
198 _Atomic(uint32_t) closed;
199 char data[];
200 } mxc_ring;
201
202 /* Round up to next power of 2 */
203 static uint32_t next_pow2(uint32_t v) {
204 v--;
205 v |= v >> 1; v |= v >> 2; v |= v >> 4;
206 v |= v >> 8; v |= v >> 16;
207 return v + 1;
208 }
209
210 static uint32_t ring_align8(uint32_t n) { return (n + 7) & ~(uint32_t)7; }
211
212 void* mxc_ring_create(int32_t dataSize) {
213 uint32_t cap = next_pow2((uint32_t)dataSize);
214 if (cap < 4096) cap = 4096;
215 size_t total = sizeof(mxc_ring) + cap;
216 mxc_ring* r = (mxc_ring*)mmap(NULL, total, PROT_READ|PROT_WRITE,
217 MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
218 if (r == MAP_FAILED) return NULL;
219 atomic_store_explicit(&r->writeIdx, 0, memory_order_relaxed);
220 atomic_store_explicit(&r->readIdx, 0, memory_order_relaxed);
221 r->size = cap;
222 atomic_store_explicit(&r->closed, 0, memory_order_relaxed);
223 return r;
224 }
225
226 void mxc_ring_destroy(void* ring) {
227 if (!ring) return;
228 mxc_ring* r = (mxc_ring*)ring;
229 size_t total = sizeof(mxc_ring) + r->size;
230 munmap(r, total);
231 }
232
233 void mxc_ring_close(void* ring) {
234 mxc_ring* r = (mxc_ring*)ring;
235 atomic_store_explicit(&r->closed, 1, memory_order_release);
236 }
237
238 int32_t mxc_ring_closed(void* ring) {
239 mxc_ring* r = (mxc_ring*)ring;
240 return atomic_load_explicit(&r->closed, memory_order_acquire) ? 1 : 0;
241 }
242
243 static void ring_write_bytes(mxc_ring* r, uint32_t pos, const char* src, uint32_t len) {
244 uint32_t mask = r->size - 1;
245 uint32_t off = pos & mask;
246 uint32_t first = r->size - off;
247 if (first >= len) {
248 memcpy(r->data + off, src, len);
249 } else {
250 memcpy(r->data + off, src, first);
251 memcpy(r->data, src + first, len - first);
252 }
253 }
254
255 static void ring_read_bytes(mxc_ring* r, uint32_t pos, char* dst, uint32_t len) {
256 uint32_t mask = r->size - 1;
257 uint32_t off = pos & mask;
258 uint32_t first = r->size - off;
259 if (first >= len) {
260 memcpy(dst, r->data + off, len);
261 } else {
262 memcpy(dst, r->data + off, first);
263 memcpy(dst + first, r->data, len - first);
264 }
265 }
266
267 /* Send a length-prefixed message. Big-endian length, 8-byte aligned.
268 Returns 0=success, 1=full (retry), -1=closed. */
269 int32_t mxc_ring_send(void* ring, const char* data, int32_t len) {
270 mxc_ring* r = (mxc_ring*)ring;
271 if (atomic_load_explicit(&r->closed, memory_order_acquire)) return -1;
272 uint32_t total = ring_align8(4 + (uint32_t)len);
273 uint32_t w = atomic_load_explicit(&r->writeIdx, memory_order_relaxed);
274 uint32_t rd = atomic_load_explicit(&r->readIdx, memory_order_acquire);
275 uint32_t space = r->size - (w - rd);
276 if (space < total) return 1; /* full */
277 /* Big-endian length prefix (matches ringbuf.mx) */
278 char hdr[4];
279 hdr[0] = (char)((uint32_t)len >> 24);
280 hdr[1] = (char)((uint32_t)len >> 16);
281 hdr[2] = (char)((uint32_t)len >> 8);
282 hdr[3] = (char)((uint32_t)len);
283 ring_write_bytes(r, w, hdr, 4);
284 if (len > 0) {
285 ring_write_bytes(r, w + 4, data, (uint32_t)len);
286 }
287 /* Zero padding bytes for 8-byte alignment */
288 uint32_t padStart = 4 + (uint32_t)len;
289 uint32_t padEnd = total;
290 if (padEnd > padStart) {
291 char zeros[8] = {0};
292 ring_write_bytes(r, w + padStart, zeros, padEnd - padStart);
293 }
294 atomic_store_explicit(&r->writeIdx, w + total, memory_order_release);
295 return 0;
296 }
297
298 /* Peek at the next message length without consuming it.
299 Returns payload length, 0=empty, -1=closed+empty. */
300 int32_t mxc_ring_peek_len(void* ring) {
301 mxc_ring* r = (mxc_ring*)ring;
302 uint32_t w = atomic_load_explicit(&r->writeIdx, memory_order_acquire);
303 uint32_t rd = atomic_load_explicit(&r->readIdx, memory_order_relaxed);
304 uint32_t avail = w - rd;
305 if (avail < 4) {
306 if (atomic_load_explicit(&r->closed, memory_order_acquire)) return -1;
307 return 0;
308 }
309 char hdr[4];
310 ring_read_bytes(r, rd, hdr, 4);
311 uint32_t msgLen = ((uint32_t)(uint8_t)hdr[0] << 24) |
312 ((uint32_t)(uint8_t)hdr[1] << 16) |
313 ((uint32_t)(uint8_t)hdr[2] << 8) |
314 ((uint32_t)(uint8_t)hdr[3]);
315 uint32_t total = ring_align8(4 + msgLen);
316 if (avail < total) return 0; /* partial message */
317 return (int32_t)msgLen;
318 }
319
320 /* Receive a length-prefixed message. Returns payload length on success,
321 0=empty, -1=closed+empty. Caller must provide buf >= msgLen (use peek_len first). */
322 int32_t mxc_ring_recv(void* ring, char* buf, int32_t bufcap) {
323 mxc_ring* r = (mxc_ring*)ring;
324 uint32_t w = atomic_load_explicit(&r->writeIdx, memory_order_acquire);
325 uint32_t rd = atomic_load_explicit(&r->readIdx, memory_order_relaxed);
326 uint32_t avail = w - rd;
327 if (avail < 4) {
328 if (atomic_load_explicit(&r->closed, memory_order_acquire)) return -1;
329 return 0;
330 }
331 char hdr[4];
332 ring_read_bytes(r, rd, hdr, 4);
333 uint32_t msgLen = ((uint32_t)(uint8_t)hdr[0] << 24) |
334 ((uint32_t)(uint8_t)hdr[1] << 16) |
335 ((uint32_t)(uint8_t)hdr[2] << 8) |
336 ((uint32_t)(uint8_t)hdr[3]);
337 uint32_t total = ring_align8(4 + msgLen);
338 if (avail < total) return 0; /* partial message */
339 uint32_t copyLen = msgLen;
340 if (copyLen > (uint32_t)bufcap) copyLen = (uint32_t)bufcap;
341 if (copyLen > 0) {
342 ring_read_bytes(r, rd + 4, buf, copyLen);
343 }
344 atomic_store_explicit(&r->readIdx, rd + total, memory_order_release);
345 return (int32_t)msgLen;
346 }
347
348 /* ---------------------------------------------------------------
349 Thread management - worker threads with status tracking
350 --------------------------------------------------------------- */
351
352 /* Per-worker status: 0=running, 1=exited, 2=crashed.
353 Allocated by parent, pointer shared with the thread. */
354 typedef struct {
355 _Atomic(uint32_t) status;
356 } mxc_worker_status;
357
358 typedef struct {
359 void (*fn)(void*);
360 void* arg;
361 mxc_worker_status* ws;
362 } mxc_thread_arg;
363
364 static void* mxc_thread_entry(void* raw) {
365 mxc_thread_arg* ta = (mxc_thread_arg*)raw;
366 void (*fn)(void*) = ta->fn;
367 void* arg = ta->arg;
368 mxc_worker_status* ws = ta->ws;
369 free(ta);
370
371 fn(arg);
372
373 atomic_store_explicit(&ws->status, 1, memory_order_release);
374 return NULL;
375 }
376
377 /* Spawn a worker thread.
378 fn: worker entry point, called as fn(arg).
379 arg: opaque argument passed to fn.
380 handle_out: receives pthread_t (as uint64_t).
381 status_out: receives mxc_worker_status* (as uint64_t).
382 Returns 0 on success, -1 on failure. */
383 int32_t mxc_spawn_thread(void* fn, void* arg, void* handle_out, void* status_out) {
384 mxc_worker_status* ws = (mxc_worker_status*)malloc(sizeof(mxc_worker_status));
385 if (!ws) return -1;
386 atomic_store_explicit(&ws->status, 0, memory_order_relaxed);
387
388 mxc_thread_arg* ta = (mxc_thread_arg*)malloc(sizeof(mxc_thread_arg));
389 if (!ta) { free(ws); return -1; }
390 ta->fn = (void (*)(void*))fn;
391 ta->arg = arg;
392 ta->ws = ws;
393
394 pthread_attr_t attr;
395 pthread_attr_init(&attr);
396 /* Give workers a large stack (64MB) matching the parent's raised limit */
397 pthread_attr_setstacksize(&attr, 67108864UL);
398
399 pthread_t tid;
400 int err = pthread_create(&tid, &attr, mxc_thread_entry, ta);
401 pthread_attr_destroy(&attr);
402 if (err != 0) {
403 free(ta);
404 free(ws);
405 return -1;
406 }
407
408 *(uint64_t*)handle_out = (uint64_t)tid;
409 *(uint64_t*)status_out = (uint64_t)(uintptr_t)ws;
410 return 0;
411 }
412
413 /* Check worker status: 1=alive, 0=exited, -1=crashed */
414 int32_t mxc_thread_alive(void* status_ptr) {
415 mxc_worker_status* ws = (mxc_worker_status*)(uintptr_t)(*(uint64_t*)status_ptr);
416 uint32_t s = atomic_load_explicit(&ws->status, memory_order_acquire);
417 if (s == 0) return 1; /* alive */
418 if (s == 1) return 0; /* exited */
419 return -1; /* crashed */
420 }
421
422 /* Join worker thread, then free status. */
423 void mxc_thread_join(void* handle_ptr, void* status_ptr) {
424 pthread_t tid = (pthread_t)(*(uint64_t*)handle_ptr);
425 pthread_join(tid, NULL);
426 mxc_worker_status* ws = (mxc_worker_status*)(uintptr_t)(*(uint64_t*)status_ptr);
427 free(ws);
428 }
429
430 void mxc_usleep(int32_t us) {
431 usleep((useconds_t)us);
432 }
433
434 void mxc_exit(int32_t code) {
435 _exit(code);
436 }
437
438 /* Worker entry trampoline: the Moxie side exports mxc_worker_entry(void*),
439 and mxc_spawn_worker calls mxc_spawn_thread with that specific function. */
440 extern void mxc_worker_entry(void* arg);
441
442 int32_t mxc_spawn_worker(void* arg, void* handle_out, void* status_out) {
443 return mxc_spawn_thread((void*)mxc_worker_entry, arg, handle_out, status_out);
444 }
445
446 /* TLS runtime probe: detects whether Moxie globals are thread_local.
447 Spawns a probe thread that writes to a Moxie global via exported function.
448 If the parent doesn't see the write, globals are TLS-isolated.
449 Returns 1=TLS works, 0=globals shared (force -j 1). */
450 extern void mxc_tls_probe_write(void);
451 extern int32_t mxc_tls_probe_read(void);
452
453 static void* tls_probe_thread(void* arg) {
454 (void)arg;
455 mxc_tls_probe_write();
456 return NULL;
457 }
458
459 int32_t mxc_detect_tls(void) {
460 pthread_t t;
461 pthread_attr_t attr;
462 pthread_attr_init(&attr);
463 pthread_attr_setstacksize(&attr, 1048576); /* 1MB stack for probe */
464 int err = pthread_create(&t, &attr, tls_probe_thread, NULL);
465 pthread_attr_destroy(&attr);
466 if (err != 0) return 0; /* can't create thread, assume no TLS */
467 pthread_join(t, NULL);
468 int32_t val = mxc_tls_probe_read();
469 /* val==0 means child wrote to its own TLS copy (parent unaffected) */
470 /* val==1 means globals are shared (no TLS) */
471 return (val == 0) ? 1 : 0;
472 }
473