@scriptc/runtime 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/scr_lib.c CHANGED
@@ -104,14 +104,14 @@ extern char **environ; /* env snapshot (scr_env_pairs) */
104
104
 
105
105
  /* ── process ─────────────────────────────────────────────────────────── */
106
106
 
107
- static int scr_lib_argc = 0;
108
- static char **scr_lib_argv = NULL;
109
- static ScrArr *scr_argv_arr = NULL; /* interned process.argv */
110
- static ScrStr *scr_platform_str = NULL; /* interned process.platform */
111
- static ScrStr *scr_exec_path_str = NULL; /* interned process.execPath */
112
- static ScrStr *scr_arch_str = NULL; /* interned process.arch */
113
- static ScrStr *scr_versions_node_str = NULL; /* interned process.versions.node */
114
- static ScrStr *scr_versions_openssl_str = NULL; /* interned process.versions.openssl */
107
+ static SCR_TL int scr_lib_argc = 0;
108
+ static SCR_TL char **scr_lib_argv = NULL;
109
+ static SCR_TL ScrArr *scr_argv_arr = NULL; /* interned process.argv */
110
+ static SCR_TL ScrStr *scr_platform_str = NULL; /* interned process.platform */
111
+ static SCR_TL ScrStr *scr_exec_path_str = NULL; /* interned process.execPath */
112
+ static SCR_TL ScrStr *scr_arch_str = NULL; /* interned process.arch */
113
+ static SCR_TL ScrStr *scr_versions_node_str = NULL; /* interned process.versions.node */
114
+ static SCR_TL ScrStr *scr_versions_openssl_str = NULL; /* interned process.versions.openssl */
115
115
 
116
116
  static void scr_lib_cleanup(void) {
117
117
  scr_arr_release(scr_argv_arr);
@@ -175,6 +175,8 @@ ScrStr *scr_process_platform(void) {
175
175
  scr_platform_str = scr_str_new("darwin", 6);
176
176
  #elif defined(__linux__)
177
177
  scr_platform_str = scr_str_new("linux", 5);
178
+ #elif defined(__wasi__)
179
+ scr_platform_str = scr_str_new("wasi", 4);
178
180
  #elif defined(_WIN32)
179
181
  scr_platform_str = scr_str_new("win32", 5);
180
182
  #else
@@ -188,7 +190,11 @@ ScrStr *scr_process_platform(void) {
188
190
  * Node spells its own build's arch. Interned like process.platform. */
189
191
  ScrStr *scr_process_arch(void) {
190
192
  if (!scr_arch_str) {
191
- #if defined(__aarch64__) || defined(_M_ARM64)
193
+ #if defined(__wasm32__)
194
+ scr_arch_str = scr_str_new("wasm32", 6);
195
+ #elif defined(__wasm64__)
196
+ scr_arch_str = scr_str_new("wasm64", 6);
197
+ #elif defined(__aarch64__) || defined(_M_ARM64)
192
198
  scr_arch_str = scr_str_new("arm64", 5);
193
199
  #elif defined(__x86_64__) || defined(_M_X64)
194
200
  scr_arch_str = scr_str_new("x64", 3);
@@ -350,11 +356,11 @@ ScrArr *scr_env_pairs(void) {
350
356
 
351
357
  double scr_process_pid(void) { return (double)getpid(); }
352
358
 
353
- #ifdef _WIN32
359
+ #if defined(_WIN32) || defined(__wasi__)
354
360
  /* No uids/gids exist on Windows: Node's process object simply has no
355
361
  * getuid/getgid members there, so a call is the property-access TypeError
356
362
  * below — thrown catchably, exactly what `process.getuid()` does under
357
- * Windows Node. */
363
+ * Windows Node. WASI has no uid/gid process model either. */
358
364
  double scr_process_getuid(void) {
359
365
  scr_throw_error_msg(SCR_ERR_TYPE, "process.getuid is not a function", 32);
360
366
  return 0;
@@ -532,6 +538,14 @@ static int scr_win_kill(int pid, int sig) {
532
538
  return 0;
533
539
  }
534
540
  #define scr_sys_kill(pid, sig) scr_win_kill((int)(pid), (sig))
541
+ #elif defined(__wasi__)
542
+ static int scr_wasi_kill(int pid, int sig) {
543
+ (void)pid;
544
+ (void)sig;
545
+ errno = ENOSYS;
546
+ return -1;
547
+ }
548
+ #define scr_sys_kill(pid, sig) scr_wasi_kill((int)(pid), (sig))
535
549
  #else
536
550
  #define scr_sys_kill(pid, sig) kill((pid_t)(pid), (sig))
537
551
  #endif
@@ -689,6 +703,27 @@ ScrStr *scr_os_tmpdir(void) {
689
703
  if (len > 1 && (buf[len - 1] == '\\' || buf[len - 1] == '/')) len--;
690
704
  return scr_str_new(buf, len);
691
705
  }
706
+ #elif defined(__wasi__)
707
+ /* WASI has an inherited environment but no passwd database, uname, or
708
+ * physical-memory query. Keep the useful environment-backed answers and
709
+ * spell the platform explicitly for the rest. */
710
+ ScrStr *scr_os_homedir(void) {
711
+ const char *home = getenv("HOME");
712
+ return scr_str_new(home ? home : "", home ? strlen(home) : 0);
713
+ }
714
+ ScrStr *scr_os_user_name(void) {
715
+ const char *user = getenv("USER");
716
+ return scr_str_new(user ? user : "", user ? strlen(user) : 0);
717
+ }
718
+ ScrStr *scr_os_user_shell(void) { return scr_str_new("", 0); }
719
+ ScrStr *scr_os_user_homedir(void) { return scr_os_homedir(); }
720
+ ScrStr *scr_os_release(void) { return scr_str_new("", 0); }
721
+ ScrStr *scr_os_type(void) { return scr_str_new("WASI", 4); }
722
+ double scr_os_totalmem(void) { return 0; }
723
+ /* The guest temp namespace is stable across hosts. `scriptc run` preopens
724
+ * the host's /tmp at this path; other WASI hosts can provide the same
725
+ * capability without leaking a host-specific TMPDIR into the module. */
726
+ ScrStr *scr_os_tmpdir(void) { return scr_str_new("/tmp", 4); }
692
727
  #else
693
728
  ScrStr *scr_os_homedir(void) {
694
729
  /* uv_os_homedir: $HOME when set (even empty is "set" only if non-NULL;
@@ -1068,8 +1103,27 @@ void scr_process_exit(double code) {
1068
1103
  * attribute monotonic stamp — the binary's own start, which is what
1069
1104
  * "the current Node.js process" means for a compiled program). */
1070
1105
  #ifdef _WIN32
1106
+ #if defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES)
1107
+ /* The uptime anchor belongs to the host PROCESS, not to a library
1108
+ * instance. InitOnce keeps the lazy Windows spelling race-free when
1109
+ * several thread instances ask for the clock concurrently. */
1110
+ static INIT_ONCE scr_uptime_once = INIT_ONCE_STATIC_INIT;
1071
1111
  static double scr_uptime_t0_ms;
1112
+ static BOOL CALLBACK scr_uptime_anchor_once(PINIT_ONCE once, PVOID param,
1113
+ PVOID *ctx) {
1114
+ (void)once;
1115
+ (void)param;
1116
+ (void)ctx;
1117
+ scr_uptime_t0_ms = (double)GetTickCount64();
1118
+ return TRUE;
1119
+ }
1120
+ static void scr_uptime_anchor_init(void) {
1121
+ InitOnceExecuteOnce(&scr_uptime_once, scr_uptime_anchor_once, NULL, NULL);
1122
+ }
1123
+ #else
1124
+ static SCR_TL double scr_uptime_t0_ms;
1072
1125
  static void scr_uptime_anchor_init(void) { scr_uptime_t0_ms = (double)GetTickCount64(); }
1126
+ #endif
1073
1127
  static double scr_uptime_now_ms(void) { return (double)GetTickCount64(); }
1074
1128
  #else
1075
1129
  #include <sys/resource.h>
@@ -1077,6 +1131,10 @@ static double scr_uptime_now_ms(void) { return (double)GetTickCount64(); }
1077
1131
  #ifdef __APPLE__
1078
1132
  #include <mach/mach.h>
1079
1133
  #endif
1134
+ /* A load-time, process-wide anchor: the constructor runs on only the
1135
+ * initial thread, while thread-instanced archives are entered from worker
1136
+ * threads whose TLS slots would otherwise stay zero. It is immutable once
1137
+ * main starts, so sharing it adds no cross-instance mutable state. */
1080
1138
  static double scr_uptime_t0_ms;
1081
1139
  static double scr_uptime_now_ms(void) {
1082
1140
  struct timespec ts;
@@ -1089,7 +1147,9 @@ __attribute__((constructor)) static void scr_uptime_anchor_init(void) {
1089
1147
  #endif
1090
1148
 
1091
1149
  double scr_process_uptime(void) {
1092
- #ifdef _WIN32
1150
+ #if defined(_WIN32) && defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES)
1151
+ scr_uptime_anchor_init();
1152
+ #elif defined(_WIN32)
1093
1153
  if (scr_uptime_t0_ms == 0) scr_uptime_anchor_init();
1094
1154
  #endif
1095
1155
  return (scr_uptime_now_ms() - scr_uptime_t0_ms) / 1000.0;
@@ -1099,7 +1159,9 @@ double scr_process_uptime(void) {
1099
1159
  * start (Node's timeOrigin anchor), fractional — the same monotonic
1100
1160
  * clock and anchor as uptime, in Node's performance.now units. */
1101
1161
  double scr_perf_now(void) {
1102
- #ifdef _WIN32
1162
+ #if defined(_WIN32) && defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES)
1163
+ scr_uptime_anchor_init();
1164
+ #elif defined(_WIN32)
1103
1165
  if (scr_uptime_t0_ms == 0) scr_uptime_anchor_init();
1104
1166
  #endif
1105
1167
  return scr_uptime_now_ms() - scr_uptime_t0_ms;
@@ -1226,7 +1288,7 @@ double scr_thread_cpu_system_diff(double prev) { return scr_thread_cpu_system()
1226
1288
  * Darwin's bytes by 1024; the rest are getrusage's own counters, zero
1227
1289
  * where the platform never fills them). */
1228
1290
  double scr_process_rusage(double idx) {
1229
- #ifdef _WIN32
1291
+ #if defined(_WIN32) || defined(__wasi__)
1230
1292
  /* uv fills only the CPU times and page-fault/maxRSS slice on Windows;
1231
1293
  * everything else answers 0 — Node's own shape there. */
1232
1294
  switch ((int)idx) {
@@ -1318,7 +1380,7 @@ double scr_available_memory(void) {
1318
1380
  /* process._exiting — true once the exit sequence began (set above and by
1319
1381
  * scr_run_exit_listeners in scr_events.c; the flag lives HERE so reading
1320
1382
  * it never forces the events unit into the link). */
1321
- bool scr_process_in_exit = false;
1383
+ SCR_TL bool scr_process_in_exit = false;
1322
1384
 
1323
1385
  bool scr_process_exiting(void) { return scr_process_in_exit; }
1324
1386
 
@@ -1336,6 +1398,11 @@ double scr_process_umask(double mask) {
1336
1398
  _umask_s((int)mask, &prev);
1337
1399
  }
1338
1400
  return (double)prev;
1401
+ #elif defined(__wasi__)
1402
+ static SCR_TL mode_t current = 022;
1403
+ mode_t prev = current;
1404
+ if (mask >= 0) current = (mode_t)mask;
1405
+ return (double)prev;
1339
1406
  #else
1340
1407
  mode_t prev;
1341
1408
  if (mask < 0) {
@@ -1359,7 +1426,7 @@ void scr_process_chdir(ScrStr *dir) {
1359
1426
  /* net's process-wide happy-eyeballs attempt budget (Node's
1360
1427
  * getDefaultAutoSelectFamilyAttemptTimeout pair, default 250ms). Lives in
1361
1428
  * the core unit so the knob never forces scr_net.c into the link. */
1362
- static double scr_net_autosel_timeout_ms = 250;
1429
+ static SCR_TL double scr_net_autosel_timeout_ms = 250;
1363
1430
 
1364
1431
  double scr_net_get_autosel_timeout(void) { return scr_net_autosel_timeout_ms; }
1365
1432
 
@@ -1653,9 +1720,10 @@ void scr_fs_chmod(ScrStr *path, double mode) {
1653
1720
  }
1654
1721
 
1655
1722
  void scr_fs_chown(ScrStr *path, double uid, double gid) {
1656
- #ifdef _WIN32
1723
+ #if defined(_WIN32) || defined(__wasi__)
1657
1724
  /* libuv's uv_fs_chown on Windows is an unconditional no-op success —
1658
- * Node's chownSync "works" and changes nothing there; same here. */
1725
+ * Node's chownSync "works" and changes nothing there; WASI likewise has
1726
+ * no ownership model. */
1659
1727
  (void)path; (void)uid; (void)gid;
1660
1728
  #else
1661
1729
  /* Node passes the ids straight to chown(2); -1 is the POSIX "leave
@@ -1863,7 +1931,7 @@ static bool scr_fs_write_fd_valid(double fd) {
1863
1931
  * call, preserving any signal that was already pending. */
1864
1932
  static ssize_t scr_fs_write_once(int fd, const void *data, size_t length,
1865
1933
  bool positioned, double position) {
1866
- #ifdef _WIN32
1934
+ #if defined(_WIN32) || defined(__wasi__)
1867
1935
  return positioned
1868
1936
  ? scr_fs_pwrite(fd, data, length, position)
1869
1937
  : write(fd, data, length);
@@ -2520,23 +2588,23 @@ void scr_fs_rm_opts_retry(ScrStr *path, bool recursive, bool force, double max_r
2520
2588
  scr_str_release(f.path);
2521
2589
  }
2522
2590
 
2523
- #ifdef _WIN32
2524
- /* No mkdtemp in the CRT: libuv's own fallback shape — six random
2591
+ #if defined(_WIN32) || defined(__wasi__)
2592
+ /* No mkdtemp in the Windows CRT or WASI libc: libuv's own fallback shape — six random
2525
2593
  * [a-z0-9] name characters from the CSPRNG, retried on EEXIST. */
2526
- static char *scr_win_mkdtemp(char *tmpl) {
2594
+ static char *scr_portable_mkdtemp(char *tmpl) {
2527
2595
  static const char cs[] = "abcdefghijklmnopqrstuvwxyz0123456789";
2528
2596
  size_t len = strlen(tmpl);
2529
2597
  for (int tries = 0; tries < 32; tries++) {
2530
2598
  unsigned char r[6];
2531
2599
  arc4random_buf(r, sizeof r);
2532
2600
  for (size_t i = 0; i < 6; i++) tmpl[len - 6 + i] = cs[r[i] % 36];
2533
- if (mkdir(tmpl) == 0) return tmpl;
2601
+ if (scr_sys_mkdir(tmpl, 0777) == 0) return tmpl;
2534
2602
  if (errno != EEXIST) break;
2535
2603
  }
2536
2604
  memcpy(tmpl + len - 6, "XXXXXX", 6); /* the error message shows the template */
2537
2605
  return NULL;
2538
2606
  }
2539
- #define mkdtemp scr_win_mkdtemp
2607
+ #define mkdtemp scr_portable_mkdtemp
2540
2608
  #endif
2541
2609
 
2542
2610
  ScrStr *scr_fs_mkdtemp(ScrStr *prefix) {
@@ -2688,6 +2756,8 @@ double scr_process_columns(double fd) {
2688
2756
  CONSOLE_SCREEN_BUFFER_INFO info;
2689
2757
  if (h == INVALID_HANDLE_VALUE || !GetConsoleScreenBufferInfo(h, &info)) return -1;
2690
2758
  return (double)(info.srWindow.Right - info.srWindow.Left + 1);
2759
+ #elif defined(__wasi__)
2760
+ return -1;
2691
2761
  #else
2692
2762
  struct winsize ws;
2693
2763
  if (ioctl((int)fd, TIOCGWINSZ, &ws) != 0) return -1;
@@ -2702,8 +2772,8 @@ double scr_process_columns(double fd) {
2702
2772
  * NON-TTY stdin: Node's process.stdin is a Socket with no setRawMode
2703
2773
  * member at all, so the call throws Node's exact catchable TypeError. */
2704
2774
  #ifdef _WIN32
2705
- static DWORD scr_stdin_cooked;
2706
- static bool scr_stdin_cooked_saved = false;
2775
+ static SCR_TL DWORD scr_stdin_cooked;
2776
+ static SCR_TL bool scr_stdin_cooked_saved = false;
2707
2777
 
2708
2778
  void scr_process_stdin_set_raw_mode(bool raw) {
2709
2779
  if (!isatty(0)) {
@@ -2728,9 +2798,15 @@ void scr_process_stdin_set_raw_mode(bool raw) {
2728
2798
  (void)SetConsoleMode(h, scr_stdin_cooked);
2729
2799
  }
2730
2800
  }
2801
+ #elif defined(__wasi__)
2802
+ void scr_process_stdin_set_raw_mode(bool raw) {
2803
+ (void)raw;
2804
+ const char msg[] = "process.stdin.setRawMode is not a function";
2805
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
2806
+ }
2731
2807
  #else
2732
- static struct termios scr_stdin_cooked;
2733
- static bool scr_stdin_cooked_saved = false;
2808
+ static SCR_TL struct termios scr_stdin_cooked;
2809
+ static SCR_TL bool scr_stdin_cooked_saved = false;
2734
2810
 
2735
2811
  void scr_process_stdin_set_raw_mode(bool raw) {
2736
2812
  if (!isatty(0)) {
package/src/scr_library.c CHANGED
@@ -10,6 +10,24 @@
10
10
  * profile-agnostic and the program TU carries every profile-named symbol —
11
11
  * one place for the conformance symbol audit to look.
12
12
  *
13
+ * Multi-instance processes (the profile's abi.localize_runtime): the
14
+ * archive build demotes this file's externals — and every other runtime
15
+ * internal — to LOCAL symbols, so each linked archive carries its own
16
+ * private copy of all the state here: its own sink registration, poison
17
+ * flag, arena, and entry-symbol slot. Sinks therefore register per
18
+ * instance, and a trap poisons only the instance it fired in. Nothing in
19
+ * this file is thread-aware; the embedder contract is one thread per
20
+ * instance (an instance is never entered from two threads).
21
+ *
22
+ * Thread-instanced archives (the profile's abi.instance_per_thread) take
23
+ * the same story one level down: SCR_TL moves this file's state — and
24
+ * every other mutable static in the archive — into thread-local storage,
25
+ * so ONE linked archive serves one independent instance per embedder
26
+ * thread. A thread registers its own sink and calls the init entry before
27
+ * serving; its trap poisons only its own instance while sibling threads'
28
+ * instances keep answering. The one-thread-per-instance contract is
29
+ * unchanged — the calling thread IS the instance selector.
30
+ *
13
31
  * State after a sink call: none is legal. The trap fired mid-operation with
14
32
  * no unwinding — heap, arena, and collector state are unspecified; the library
15
33
  * is POISONED. Every runtime-touching entry prologue aborts on the flag
@@ -23,9 +41,9 @@
23
41
 
24
42
  /* ── sink registration + poison ───────────────────────────────────────── */
25
43
 
26
- static ScrLibSinkFn scr_library_sink = NULL;
27
- static void *scr_library_sink_ctx = NULL;
28
- static bool scr_library_poisoned = false;
44
+ static SCR_TL ScrLibSinkFn scr_library_sink = NULL;
45
+ static SCR_TL void *scr_library_sink_ctx = NULL;
46
+ static SCR_TL bool scr_library_poisoned = false;
29
47
 
30
48
  void scr_library_set_sink(ScrLibSinkFn fn, void *ctx) {
31
49
  /* Latest registration wins; re-registration is permitted before a trap.
@@ -62,10 +80,12 @@ void scr_library_set_sink(ScrLibSinkFn fn, void *ctx) {
62
80
 
63
81
  /* The current-entry slot: every generated entry's prologue records its
64
82
  * external symbol before dispatching into core code. A single static slot
65
- * is sound — exactly one core is live per process (the one-live-core rule),
66
- * entries never nest, and a trap can only fire while an entry is on the
67
- * stack. NULL (never entered) renders as the empty symbol field. */
68
- static const char *scr_library_entry_symbol = NULL;
83
+ * is sound — exactly one core is live per copy of this state (the sole
84
+ * core in a classic process; each archive's own instance under
85
+ * abi.localize_runtime, where this slot is a per-instance local), entries
86
+ * never nest, and a trap can only fire while an entry is on the stack.
87
+ * NULL (never entered) renders as the empty symbol field. */
88
+ static SCR_TL const char *scr_library_entry_symbol = NULL;
69
89
 
70
90
  /* Detected-trap classification: the runtime's trap sites self-classify
71
91
  * through their message conventions (the exact bytes the executable lane
@@ -103,7 +123,7 @@ static _Noreturn void scr_library_trap_deliver(const char *msg, size_t len, uint
103
123
  * no malloc on the failure path; text truncates before structure ever
104
124
  * would (codes and symbols are short; an oversized remediation drops
105
125
  * whole, never split). */
106
- static char buf[2048];
126
+ static SCR_TL char buf[2048];
107
127
  const char *code = scr_library_trap_code(msg, len);
108
128
  const char *text = msg;
109
129
  size_t text_len = len;
@@ -168,7 +188,7 @@ __attribute__((noinline)) _Noreturn void scr_trap_len(const char *msg, size_t le
168
188
  }
169
189
 
170
190
  __attribute__((noinline)) _Noreturn void scr_trap_fmt(const char *fmt, ...) {
171
- static char buf[512]; /* no malloc on the invariant-failure path */
191
+ static SCR_TL char buf[512]; /* no malloc on the invariant-failure path */
172
192
  va_list ap;
173
193
  va_start(ap, fmt);
174
194
  int n = vsnprintf(buf, sizeof buf, fmt, ap);
@@ -202,8 +222,8 @@ typedef struct {
202
222
  bool is_str; /* ScrStr vs ScrBytes — picks the release */
203
223
  } ScrCoreArenaEnt;
204
224
 
205
- static ScrCoreArenaEnt *scr_library_arena = NULL;
206
- static size_t scr_library_arena_n = 0, scr_library_arena_cap = 0;
225
+ static SCR_TL ScrCoreArenaEnt *scr_library_arena = NULL;
226
+ static SCR_TL size_t scr_library_arena_n = 0, scr_library_arena_cap = 0;
207
227
 
208
228
  static void scr_library_arena_keep(void *v, bool is_str) {
209
229
  if (scr_library_arena_n == scr_library_arena_cap) {
@@ -285,8 +305,8 @@ void scr_library_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len) {
285
305
  * satisfied because the entry persists. */
286
306
 
287
307
  #define SCR_LIB_MAX_RESETS 32
288
- static void (*scr_library_resets[SCR_LIB_MAX_RESETS])(void);
289
- static size_t scr_library_nresets = 0;
308
+ static SCR_TL void (*scr_library_resets[SCR_LIB_MAX_RESETS])(void);
309
+ static SCR_TL size_t scr_library_nresets = 0;
290
310
 
291
311
  void scr_library_register_reset(void (*fn)(void)) {
292
312
  for (size_t i = 0; i < scr_library_nresets; i++) {
package/src/scr_map.c CHANGED
@@ -24,7 +24,7 @@
24
24
  /* Live heap-map count for the RC audit lane (-DSCR_RC_AUDIT); same contract
25
25
  * as scr_str_live_count in scr_string.c. */
26
26
  #ifdef SCR_RC_AUDIT
27
- static long scr_live_maps = 0;
27
+ static SCR_TL long scr_live_maps = 0;
28
28
  long scr_map_live_count(void) { return scr_live_maps; }
29
29
  #endif
30
30
 
package/src/scr_musl.c ADDED
@@ -0,0 +1,274 @@
1
+ /* musl libc shim — compiled into linux-musl target builds only (cc.ts adds
2
+ * this TU together with -DSCR_MUSL). musl deliberately provides no libc
3
+ * identification macro, so the target-selected project macro is the guard.
4
+ *
5
+ * The x86_64 ucontext implementation below is derived from libucontext
6
+ * commit 49e671dd52ff6791295d8161ad3b6da7dc5f6f9d:
7
+ * https://github.com/kaniini/libucontext
8
+ *
9
+ * Copyright (c) 2018-2025 Ariadne Conill <ariadne@dereferenced.org>
10
+ *
11
+ * Permission to use, copy, modify, and/or distribute this software for any
12
+ * purpose with or without fee is hereby granted, provided that the above
13
+ * copyright notice and this permission notice appear in all copies.
14
+ *
15
+ * This software is provided 'as is' and without any warranty, express or
16
+ * implied. In no event shall the authors be liable for any damages arising
17
+ * from the use of this software. */
18
+ #ifdef SCR_MUSL
19
+
20
+ #include "scr_runtime.h"
21
+
22
+ #include <errno.h>
23
+ #include <stdlib.h>
24
+ #include <sys/random.h>
25
+ #ifndef SCR_LIB
26
+ #include <stdarg.h>
27
+ #include <ucontext.h>
28
+ #endif
29
+
30
+ #if !defined(__x86_64__)
31
+ #error "scriptc's musl runtime currently supports x86_64 only"
32
+ #endif
33
+
34
+ /* The scriptc runtime uses arc4random_buf as its infallible CSPRNG contract.
35
+ * Linux getrandom has the same kernel source and needs neither mutable state
36
+ * nor an fd. Retry interrupts and short reads; any other failure enters the
37
+ * runtime trap funnel rather than returning predictable or partially
38
+ * initialized bytes. Executables still print and abort there, while library
39
+ * artifacts deliver the failure to their registered panic sink. */
40
+ void arc4random_buf(void *buf, size_t n) {
41
+ unsigned char *p = buf;
42
+ while (n > 0) {
43
+ ssize_t got = getrandom(p, n, 0);
44
+ if (got > 0) {
45
+ p += (size_t)got;
46
+ n -= (size_t)got;
47
+ continue;
48
+ }
49
+ if (got < 0 && errno == EINTR) continue;
50
+ scr_trap("scriptc: getrandom failed\n");
51
+ }
52
+ }
53
+
54
+ /* Library artifacts contain no fibers or event loop, so they need only the
55
+ * CSPRNG shim above and must not acquire context-switching definitions. */
56
+ #ifndef SCR_LIB
57
+
58
+ /* musl exposes the POSIX ucontext types but intentionally omits these legacy
59
+ * functions. scriptc's async/generator fibers need only user-space register
60
+ * swaps; they never use a context to mutate the process signal mask. This is
61
+ * libucontext's fast (non-POSIX-signal-mask) x86_64 implementation, emitted
62
+ * under the standard symbol names that musl leaves unresolved. */
63
+ _Static_assert(offsetof(ucontext_t, uc_mcontext.gregs) == 40,
64
+ "unexpected musl x86_64 ucontext layout");
65
+ _Static_assert(offsetof(ucontext_t, uc_mcontext.fpregs) == 224,
66
+ "unexpected musl x86_64 fpregs layout");
67
+ _Static_assert(offsetof(ucontext_t, __fpregs_mem) == 424,
68
+ "unexpected musl x86_64 fpregs storage layout");
69
+
70
+ /* Keep the assembler independent of whether the sysroot exposes glibc's
71
+ * REG_* spellings as enum constants, self-referential macros, or neither. */
72
+ #undef REG_R8
73
+ #undef REG_R9
74
+ #undef REG_R10
75
+ #undef REG_R11
76
+ #undef REG_R12
77
+ #undef REG_R13
78
+ #undef REG_R14
79
+ #undef REG_R15
80
+ #undef REG_RDI
81
+ #undef REG_RSI
82
+ #undef REG_RBP
83
+ #undef REG_RBX
84
+ #undef REG_RDX
85
+ #undef REG_RAX
86
+ #undef REG_RCX
87
+ #undef REG_RSP
88
+ #undef REG_RIP
89
+ #define REG_R8 0
90
+ #define REG_R9 1
91
+ #define REG_R10 2
92
+ #define REG_R11 3
93
+ #define REG_R12 4
94
+ #define REG_R13 5
95
+ #define REG_R14 6
96
+ #define REG_R15 7
97
+ #define REG_RDI 8
98
+ #define REG_RSI 9
99
+ #define REG_RBP 10
100
+ #define REG_RBX 11
101
+ #define REG_RDX 12
102
+ #define REG_RAX 13
103
+ #define REG_RCX 14
104
+ #define REG_RSP 15
105
+ #define REG_RIP 16
106
+
107
+ #define SCR_STR_INNER(x) #x
108
+ #define SCR_STR(x) SCR_STR_INNER(x)
109
+ #define SCR_UC_GREG(reg) (40 + ((reg) * 8))
110
+
111
+ __asm__(
112
+ ".text\n"
113
+ ".global getcontext\n"
114
+ "getcontext:\n"
115
+ " movq %r8, " SCR_STR(SCR_UC_GREG(REG_R8)) "(%rdi)\n"
116
+ " movq %r9, " SCR_STR(SCR_UC_GREG(REG_R9)) "(%rdi)\n"
117
+ " movq %r10, " SCR_STR(SCR_UC_GREG(REG_R10)) "(%rdi)\n"
118
+ " movq %r11, " SCR_STR(SCR_UC_GREG(REG_R11)) "(%rdi)\n"
119
+ " movq %r12, " SCR_STR(SCR_UC_GREG(REG_R12)) "(%rdi)\n"
120
+ " movq %r13, " SCR_STR(SCR_UC_GREG(REG_R13)) "(%rdi)\n"
121
+ " movq %r14, " SCR_STR(SCR_UC_GREG(REG_R14)) "(%rdi)\n"
122
+ " movq %r15, " SCR_STR(SCR_UC_GREG(REG_R15)) "(%rdi)\n"
123
+ " movq %rdi, " SCR_STR(SCR_UC_GREG(REG_RDI)) "(%rdi)\n"
124
+ " movq %rsi, " SCR_STR(SCR_UC_GREG(REG_RSI)) "(%rdi)\n"
125
+ " movq %rbp, " SCR_STR(SCR_UC_GREG(REG_RBP)) "(%rdi)\n"
126
+ " movq %rbx, " SCR_STR(SCR_UC_GREG(REG_RBX)) "(%rdi)\n"
127
+ " movq %rdx, " SCR_STR(SCR_UC_GREG(REG_RDX)) "(%rdi)\n"
128
+ " movq %rax, " SCR_STR(SCR_UC_GREG(REG_RAX)) "(%rdi)\n"
129
+ " movq %rcx, " SCR_STR(SCR_UC_GREG(REG_RCX)) "(%rdi)\n"
130
+ " movq (%rsp), %rcx\n"
131
+ " movq %rcx, " SCR_STR(SCR_UC_GREG(REG_RIP)) "(%rdi)\n"
132
+ " leaq 8(%rsp), %rcx\n"
133
+ " movq %rcx, " SCR_STR(SCR_UC_GREG(REG_RSP)) "(%rdi)\n"
134
+ " leaq 424(%rdi), %rcx\n"
135
+ " movq %rcx, 224(%rdi)\n"
136
+ " fnstenv (%rcx)\n"
137
+ " fldenv (%rcx)\n"
138
+ " stmxcsr 448(%rdi)\n"
139
+ " xorl %eax, %eax\n"
140
+ " ret\n"
141
+
142
+ ".global setcontext\n"
143
+ "setcontext:\n"
144
+ " movq 224(%rdi), %rcx\n"
145
+ " fldenv (%rcx)\n"
146
+ " ldmxcsr 448(%rdi)\n"
147
+ " movq " SCR_STR(SCR_UC_GREG(REG_R8)) "(%rdi), %r8\n"
148
+ " movq " SCR_STR(SCR_UC_GREG(REG_R9)) "(%rdi), %r9\n"
149
+ " movq " SCR_STR(SCR_UC_GREG(REG_R10)) "(%rdi), %r10\n"
150
+ " movq " SCR_STR(SCR_UC_GREG(REG_R11)) "(%rdi), %r11\n"
151
+ " movq " SCR_STR(SCR_UC_GREG(REG_R12)) "(%rdi), %r12\n"
152
+ " movq " SCR_STR(SCR_UC_GREG(REG_R13)) "(%rdi), %r13\n"
153
+ " movq " SCR_STR(SCR_UC_GREG(REG_R14)) "(%rdi), %r14\n"
154
+ " movq " SCR_STR(SCR_UC_GREG(REG_R15)) "(%rdi), %r15\n"
155
+ " movq " SCR_STR(SCR_UC_GREG(REG_RSI)) "(%rdi), %rsi\n"
156
+ " movq " SCR_STR(SCR_UC_GREG(REG_RBP)) "(%rdi), %rbp\n"
157
+ " movq " SCR_STR(SCR_UC_GREG(REG_RBX)) "(%rdi), %rbx\n"
158
+ " movq " SCR_STR(SCR_UC_GREG(REG_RDX)) "(%rdi), %rdx\n"
159
+ " movq " SCR_STR(SCR_UC_GREG(REG_RAX)) "(%rdi), %rax\n"
160
+ " movq " SCR_STR(SCR_UC_GREG(REG_RCX)) "(%rdi), %rcx\n"
161
+ " movq " SCR_STR(SCR_UC_GREG(REG_RSP)) "(%rdi), %rsp\n"
162
+ " pushq " SCR_STR(SCR_UC_GREG(REG_RIP)) "(%rdi)\n"
163
+ " movq " SCR_STR(SCR_UC_GREG(REG_RDI)) "(%rdi), %rdi\n"
164
+ " xorl %eax, %eax\n"
165
+ " ret\n"
166
+
167
+ ".global swapcontext\n"
168
+ "swapcontext:\n"
169
+ " movq %r8, " SCR_STR(SCR_UC_GREG(REG_R8)) "(%rdi)\n"
170
+ " movq %r9, " SCR_STR(SCR_UC_GREG(REG_R9)) "(%rdi)\n"
171
+ " movq %r10, " SCR_STR(SCR_UC_GREG(REG_R10)) "(%rdi)\n"
172
+ " movq %r11, " SCR_STR(SCR_UC_GREG(REG_R11)) "(%rdi)\n"
173
+ " movq %r12, " SCR_STR(SCR_UC_GREG(REG_R12)) "(%rdi)\n"
174
+ " movq %r13, " SCR_STR(SCR_UC_GREG(REG_R13)) "(%rdi)\n"
175
+ " movq %r14, " SCR_STR(SCR_UC_GREG(REG_R14)) "(%rdi)\n"
176
+ " movq %r15, " SCR_STR(SCR_UC_GREG(REG_R15)) "(%rdi)\n"
177
+ " movq %rdi, " SCR_STR(SCR_UC_GREG(REG_RDI)) "(%rdi)\n"
178
+ " movq %rsi, " SCR_STR(SCR_UC_GREG(REG_RSI)) "(%rdi)\n"
179
+ " movq %rbp, " SCR_STR(SCR_UC_GREG(REG_RBP)) "(%rdi)\n"
180
+ " movq %rbx, " SCR_STR(SCR_UC_GREG(REG_RBX)) "(%rdi)\n"
181
+ " movq %rdx, " SCR_STR(SCR_UC_GREG(REG_RDX)) "(%rdi)\n"
182
+ " movq %rax, " SCR_STR(SCR_UC_GREG(REG_RAX)) "(%rdi)\n"
183
+ " movq %rcx, " SCR_STR(SCR_UC_GREG(REG_RCX)) "(%rdi)\n"
184
+ " movq (%rsp), %rcx\n"
185
+ " movq %rcx, " SCR_STR(SCR_UC_GREG(REG_RIP)) "(%rdi)\n"
186
+ " leaq 8(%rsp), %rcx\n"
187
+ " movq %rcx, " SCR_STR(SCR_UC_GREG(REG_RSP)) "(%rdi)\n"
188
+ " leaq 424(%rdi), %rcx\n"
189
+ " movq %rcx, 224(%rdi)\n"
190
+ " fnstenv (%rcx)\n"
191
+ " fldenv (%rcx)\n"
192
+ " stmxcsr 448(%rdi)\n"
193
+ " movq 224(%rsi), %rcx\n"
194
+ " fldenv (%rcx)\n"
195
+ " ldmxcsr 448(%rsi)\n"
196
+ " movq " SCR_STR(SCR_UC_GREG(REG_R8)) "(%rsi), %r8\n"
197
+ " movq " SCR_STR(SCR_UC_GREG(REG_R9)) "(%rsi), %r9\n"
198
+ " movq " SCR_STR(SCR_UC_GREG(REG_R10)) "(%rsi), %r10\n"
199
+ " movq " SCR_STR(SCR_UC_GREG(REG_R11)) "(%rsi), %r11\n"
200
+ " movq " SCR_STR(SCR_UC_GREG(REG_R12)) "(%rsi), %r12\n"
201
+ " movq " SCR_STR(SCR_UC_GREG(REG_R13)) "(%rsi), %r13\n"
202
+ " movq " SCR_STR(SCR_UC_GREG(REG_R14)) "(%rsi), %r14\n"
203
+ " movq " SCR_STR(SCR_UC_GREG(REG_R15)) "(%rsi), %r15\n"
204
+ " movq " SCR_STR(SCR_UC_GREG(REG_RDI)) "(%rsi), %rdi\n"
205
+ " movq " SCR_STR(SCR_UC_GREG(REG_RBP)) "(%rsi), %rbp\n"
206
+ " movq " SCR_STR(SCR_UC_GREG(REG_RBX)) "(%rsi), %rbx\n"
207
+ " movq " SCR_STR(SCR_UC_GREG(REG_RDX)) "(%rsi), %rdx\n"
208
+ " movq " SCR_STR(SCR_UC_GREG(REG_RAX)) "(%rsi), %rax\n"
209
+ " movq " SCR_STR(SCR_UC_GREG(REG_RCX)) "(%rsi), %rcx\n"
210
+ " movq " SCR_STR(SCR_UC_GREG(REG_RSP)) "(%rsi), %rsp\n"
211
+ " pushq " SCR_STR(SCR_UC_GREG(REG_RIP)) "(%rsi)\n"
212
+ " movq " SCR_STR(SCR_UC_GREG(REG_RSI)) "(%rsi), %rsi\n"
213
+ " xorl %eax, %eax\n"
214
+ " ret\n"
215
+
216
+ ".hidden scr_musl_context_trampoline\n"
217
+ "scr_musl_context_trampoline:\n"
218
+ " movq (%rbx), %rdi\n"
219
+ " testq %rdi, %rdi\n"
220
+ " je 1f\n"
221
+ " call setcontext\n"
222
+ " ud2\n"
223
+ "1:\n"
224
+ " xorl %edi, %edi\n"
225
+ " call exit\n"
226
+ " ud2\n");
227
+
228
+ extern void scr_musl_context_trampoline(void);
229
+
230
+ void makecontext(ucontext_t *ucp, void (*func)(void), int argc, ...) {
231
+ greg_t *sp;
232
+ va_list va;
233
+ int i;
234
+ unsigned int link_slot = (unsigned int)(argc > 6 ? argc - 6 : 0) + 1;
235
+
236
+ sp = (greg_t *)((uintptr_t)ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size);
237
+ sp -= link_slot;
238
+ sp = (greg_t *)(((uintptr_t)sp & ~(uintptr_t)15) - 8);
239
+
240
+ ucp->uc_mcontext.fpregs = (void *)&ucp->__fpregs_mem;
241
+ ucp->uc_mcontext.gregs[REG_RIP] = (greg_t)(uintptr_t)func;
242
+ ucp->uc_mcontext.gregs[REG_RBX] = (greg_t)(uintptr_t)&sp[link_slot];
243
+ ucp->uc_mcontext.gregs[REG_RSP] = (greg_t)(uintptr_t)sp;
244
+
245
+ sp[0] = (greg_t)(uintptr_t)&scr_musl_context_trampoline;
246
+ sp[link_slot] = (greg_t)(uintptr_t)ucp->uc_link;
247
+
248
+ va_start(va, argc);
249
+ for (i = 0; i < argc; i++) {
250
+ greg_t arg = va_arg(va, greg_t);
251
+ switch (i) {
252
+ case 0: ucp->uc_mcontext.gregs[REG_RDI] = arg; break;
253
+ case 1: ucp->uc_mcontext.gregs[REG_RSI] = arg; break;
254
+ case 2: ucp->uc_mcontext.gregs[REG_RDX] = arg; break;
255
+ case 3: ucp->uc_mcontext.gregs[REG_RCX] = arg; break;
256
+ case 4: ucp->uc_mcontext.gregs[REG_R8] = arg; break;
257
+ case 5: ucp->uc_mcontext.gregs[REG_R9] = arg; break;
258
+ default: sp[i - 5] = arg; break;
259
+ }
260
+ }
261
+ va_end(va);
262
+ }
263
+
264
+ #undef SCR_UC_GREG
265
+ #undef SCR_STR
266
+ #undef SCR_STR_INNER
267
+
268
+ #endif /* !SCR_LIB */
269
+
270
+ #else /* !SCR_MUSL */
271
+
272
+ typedef int scr_musl_unused;
273
+
274
+ #endif /* SCR_MUSL */