1pub const RISCV_RUNTIME_C: &str = concat!(
12 crate::codegen_c::runtime::lc_gc_collector_source!(),
13 crate::codegen_c::runtime::lc_gc_api_source!(),
14r#"
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18#include <errno.h>
19#include <stdint.h>
20#include <math.h>
21#include <time.h>
22#include <ucontext.h>
23#include <unistd.h>
24
25/* Nothing in a program unwinds: a failure prints its sentence and exits, and a task switches stacks by its
26 context. So the runtime's call-frame information goes to `.debug_frame`, which nothing reads at run time,
27 rather than `.eh_frame`, every entry of which a static binary's startup registers before `main`. */
28__asm__(".cfi_sections .debug_frame");
29
30/* A failure: the program stops with its reason alone on stderr, exit status 1 — the report every tier
31 gives (`FailWith`, `Fail with …`, a broken contract's reason, a runtime error) — or, inside a task of
32 a concurrent program, the task stops and never returns here, and the run fails with its first
33 failure once every other task is quiescent (the scheduler installs `logos_fail_hook` for the run). */
34static void (*logos_fail_hook)(const char *) = 0;
35
36__attribute__((noreturn)) void logos_fatal(const char *reason) {
37 fflush(stdout);
38 if (logos_fail_hook) logos_fail_hook(reason);
39 fprintf(stderr, "%s\n", reason);
40 exit(1);
41}
42
43__attribute__((noreturn)) static void logos_fatalf(const char *fmt, long long arg) {
44 char buf[256];
45 snprintf(buf, sizeof buf, fmt, arg);
46 logos_fatal(lc_gc_strdup(buf));
47}
48
49/* Rust's `char::is_whitespace`. */
50static int logos_is_space(unsigned cp) {
51 return (cp >= 0x09 && cp <= 0x0D) || cp == 0x20 || cp == 0x85 || cp == 0xA0 || cp == 0x1680
52 || (cp >= 0x2000 && cp <= 0x200A) || cp == 0x2028 || cp == 0x2029 || cp == 0x202F || cp == 0x205F
53 || cp == 0x3000;
54}
55
56/* The code point UTF-8 encodes at `s` into `cp`, answering its length in bytes. */
57static int logos_utf8_at(const unsigned char *s, unsigned *cp) {
58 if (s[0] < 0x80) { *cp = s[0]; return 1; }
59 if (s[0] < 0xE0) { *cp = ((s[0] & 0x1Fu) << 6) | (s[1] & 0x3Fu); return 2; }
60 if (s[0] < 0xF0) { *cp = ((s[0] & 0x0Fu) << 12) | ((s[1] & 0x3Fu) << 6) | (s[2] & 0x3Fu); return 3; }
61 *cp = ((s[0] & 0x07u) << 18) | ((s[1] & 0x3Fu) << 12) | ((s[2] & 0x3Fu) << 6) | (s[3] & 0x3Fu);
62 return 4;
63}
64
65/* `str::trim` — a copy of `s` without its leading and trailing whitespace. */
66static char *logos_trim(const char *s) {
67 const unsigned char *b = (const unsigned char *)s, *e = b + strlen(s);
68 unsigned cp;
69 while (b < e) {
70 int n = logos_utf8_at(b, &cp);
71 if (!logos_is_space(cp)) break;
72 b += n;
73 }
74 while (e > b) {
75 const unsigned char *p = e - 1;
76 while (p > b && (*p & 0xC0) == 0x80) p--;
77 logos_utf8_at(p, &cp);
78 if (!logos_is_space(cp)) break;
79 e = p;
80 }
81 size_t n = (size_t)(e - b);
82 char *out = (char *)lc_gc_alloc_atomic(n + 1);
83 memcpy(out, b, n);
84 out[n] = '\0';
85 return out;
86}
87
88/* The sentences an operation that fails stops the program with — the VM's own, which the emitter takes from
89 the VM and emits beside the program, and which a helper reads only on its way to stopping:
90 `logos_index_refusal` is a format of the index (`%lld`), `logos_missing_key_refusal`, `logos_parse_int_refusal`
91 and `logos_parse_float_refusal` of a text (`%s`), `logos_random_bounds_refusal` of the two bounds
92 (`%1$lld`, `%2$lld`), `logos_utf8_invalid_refusal` of a sequence's length and start (`%1$d`, `%2$lld`) and
93 `logos_utf8_incomplete_refusal` of a start (`%lld`). */
94extern const char logos_index_refusal[], logos_missing_key_refusal[], logos_exact_division_refusal[],
95 logos_float_division_refusal[],
96 logos_negative_exponent_refusal[], logos_exponent_too_large_refusal[], logos_parse_int_refusal[],
97 logos_parse_float_refusal[], logos_random_bounds_refusal[], logos_utf8_invalid_refusal[],
98 logos_utf8_incomplete_refusal[], logos_await_without_listen_refusal[];
99
100/* The 0-based cell of index `i` among `len` that is not a 1-based one in range — the rest of the VM's
101 `resolve_index`: `-i` counts from the back, and any other index stops the program. Every accessor tests the
102 1-based index in range itself, inline, and calls this out of line for everything else. */
103__attribute__((noinline, cold)) static long long logos_index_from_back(long long i, long long len) {
104 if (i <= -1 && (unsigned long long)0 - (unsigned long long)i <= (unsigned long long)len) return len + i;
105 logos_fatalf(logos_index_refusal, i);
106}
107
108/* The 0-based cell of 1-based index `i` among `len` (the VM's `resolve_index`). */
109static inline long long logos_resolve_index(long long i, long long len) {
110 if (__builtin_expect(i < 1 || i > len, 0)) return logos_index_from_back(i, len);
111 return i - 1;
112}
113
114/* Render an f64 into a fresh string EXACTLY like Rust's `{}` Display (see logos_show_f64). */
115char *logos_fmt_f64(double x) {
116 if (isnan(x)) return lc_gc_strdup("NaN");
117 if (isinf(x)) return lc_gc_strdup(x < 0 ? "-inf" : "inf");
118
119 /* Shortest significant-digit count whose scientific form round-trips. */
120 char sci[64];
121 int prec;
122 for (prec = 0; prec <= 17; prec++) {
123 snprintf(sci, sizeof sci, "%.*e", prec, x);
124 if (strtod(sci, NULL) == x) break;
125 }
126
127 /* Parse sci = [-]D[.FFF]e[+-]EE into a sign, a digit string, and a decimal exponent. */
128 const char *p = sci;
129 char sign = 0;
130 if (*p == '-') { sign = '-'; p++; }
131 char digits[40];
132 int nd = 0;
133 digits[nd++] = *p++; /* leading digit D */
134 if (*p == '.') { p++; while (*p != 'e' && *p != 'E') digits[nd++] = *p++; }
135 p++; /* skip 'e' */
136 int exp = atoi(p); /* decimal point sits after (exp+1) digits from the start */
137 int pointpos = exp + 1;
138
139 /* Reformat as fixed decimal (no exponent), matching Rust's Display. */
140 char *out = (char *)lc_gc_alloc_atomic(600);
141 int oi = 0;
142 if (sign) out[oi++] = '-';
143 if (pointpos <= 0) {
144 out[oi++] = '0';
145 out[oi++] = '.';
146 for (int i = 0; i < -pointpos; i++) out[oi++] = '0';
147 for (int i = 0; i < nd; i++) out[oi++] = digits[i];
148 } else if (pointpos >= nd) {
149 for (int i = 0; i < nd; i++) out[oi++] = digits[i];
150 for (int i = 0; i < pointpos - nd; i++) out[oi++] = '0';
151 } else {
152 for (int i = 0; i < nd; i++) {
153 if (i == pointpos) out[oi++] = '.';
154 out[oi++] = digits[i];
155 }
156 }
157 out[oi] = '\0';
158 return out;
159}
160
161void logos_show_f64(double x) {
162 char *s = logos_fmt_f64(x);
163 printf("%s\n", s);
164 lc_gc_free(s);
165}
166
167/* ---- Text (heap char*) ---- */
168
169/* Stringify one slot value to a fresh string, by its static kind (0=Int, 1=Float, 2=Bool,
170 * 3=Text) — the AOT analog of `to_display_string`. `bits` is the raw 64-bit slot content (an i64, an
171 * f64 bit pattern, 0/1, or a `char*`). */
172"#,
173 crate::codegen_c::runtime::exact_numbers_source!(),
174 r#"
175/* ---- Rational: a handle to an exact number of the section above (an `LDec`), which follows the VM's rules for
176 two exact numbers — a whole rational answers `/`, `%`, `//`, `**` and the bitwise operators as the integer it
177 equals. An operand is a rational handle (`flag` 1) or a machine Int (`flag` 0). ---- */
178
179static long long logos_ldec_box(LDec d) {
180 LDec *p = (LDec *)lc_gc_alloc(sizeof(LDec));
181 *p = d;
182 return (long long)p;
183}
184
185static LDec logos_ldec_of(long long v, int flag) { return flag ? *(const LDec *)v : ldec_from_i64(v); }
186
187/* An Int as the rational it equals. */
188long long logos_ldec_of_int(long long v) { return logos_ldec_box(ldec_from_i64(v)); }
189
190/* `a op b`, a rational: op 0 `+`, 1 `-`, 2 `*`, 3 `/`, 4 the exact `/`, 5 `//`, 6 `%`, 7 `**`. */
191long long logos_ldec_op(long long a, int af, long long b, int bf, int op) {
192 LDec x = logos_ldec_of(a, af), y = logos_ldec_of(b, bf);
193 switch (op) {
194 case 0: return logos_ldec_box(ldec_add(x, y));
195 case 1: return logos_ldec_box(ldec_sub(x, y));
196 case 2: return logos_ldec_box(ldec_mul(x, y));
197 case 3: return logos_ldec_box(ldec_quotient(x, y));
198 case 4:
199 if (ldec_is_zero(&y)) logos_fatal(logos_exact_division_refusal);
200 return logos_ldec_box(ldec_div(x, y));
201 case 5: return logos_ldec_box(ldec_floor_quotient(x, y));
202 case 6: return logos_ldec_box(ldec_remainder(x, y));
203 default: return logos_ldec_box(ldec_power(x, y));
204 }
205}
206
207/* `a & b`, `a | b`, `a ^ b`, `a << b`, `a >> b` (`op` '&', '|', '^', '<', '>') over two whole numbers, an Int. */
208long long logos_ldec_bitwise(long long a, int af, long long b, int bf, int op) {
209 return ldec_bitwise(logos_ldec_of(a, af), logos_ldec_of(b, bf), (char)op);
210}
211
212/* The order of two exact numbers: -1, 0 or 1. */
213int logos_ldec_cmp(long long a, int af, long long b, int bf) { return ldec_cmp(logos_ldec_of(a, af), logos_ldec_of(b, bf)); }
214
215/* The order of a rational against a real by the real's exact value: -1, 0 or 1, and 2 when the real is NaN. */
216int logos_ldec_cmp_f64(long long a, double f) { return ldec_cmp_f64(*(const LDec *)a, f); }
217
218/* The double the VM converts a rational to beside a real. */
219double logos_ldec_to_f64(long long a) { return ldec_to_f64(*(const LDec *)a); }
220
221/* `a op f`, or `f op a` when the rational is on the right (`rational_left` 0), a rational beside a real, as the VM
222 computes it on the rational's double: op 0 `+`, 1 `-`, 2 `*`, 3 `/`, 4 `**`. */
223double logos_ldec_real_op(long long a, double f, int rational_left, int op) {
224 double x = ldec_to_f64(*(const LDec *)a);
225 double l = rational_left ? x : f, r = rational_left ? f : x;
226 switch (op) {
227 case 0: return l + r;
228 case 1: return l - r;
229 case 2: return l * r;
230 case 3:
231 if (r == 0.0) logos_fatal(logos_float_division_refusal);
232 return l / r;
233 default: return pow(l, r);
234 }
235}
236
237/* A rational where the VM takes a machine integer: the integer a whole one is, else the place's refusal. */
238long long logos_ldec_as_i64(long long a, const char *past_word, const char *fraction) {
239 return ldec_as_i64(*(const LDec *)a, past_word, fraction);
240}
241
242/* `num/den`, or the integer a whole rational equals. */
243char *logos_ldec_str(long long handle) { return ldec_to_string((const LDec *)handle); }
244
245/* ---- Temporal: Date (days since epoch) / Moment (nanos since epoch), ≡ the VM's Show ---- */
246
247/* Floored (Euclidean) division — a pre-epoch Moment yields the correct date + a 0..86399 time-of-day. */
248static long long logos_fdiv(long long a, long long b) {
249 long long q = a / b;
250 if ((a % b != 0) && ((a < 0) != (b < 0))) q--;
251 return q;
252}
253
254/* Civil date from days-since-epoch (Howard Hinnant's algorithm, matching `RuntimeValue::Date`). */
255static void logos_civil(long long days, long long *year, long long *mon, long long *day) {
256 long long z = days + 719468;
257 long long era = (z >= 0 ? z : z - 146096) / 146097;
258 long long doe = z - era * 146097;
259 long long yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
260 long long y = yoe + era * 400;
261 long long doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
262 long long mp = (5 * doy + 2) / 153;
263 long long d = doy - (153 * mp + 2) / 5 + 1;
264 long long m = mp + (mp < 10 ? 3 : -9);
265 *year = y + (m <= 2 ? 1 : 0);
266 *mon = m;
267 *day = d;
268}
269
270char *logos_date_str(long long days) {
271 long long y, m, d;
272 logos_civil(days, &y, &m, &d);
273 char *b = (char *)lc_gc_alloc_atomic(32);
274 snprintf(b, 32, "%04lld-%02lld-%02lld", y, m, d);
275 return b;
276}
277
278char *logos_moment_str(long long nanos) {
279 long long total_seconds = logos_fdiv(nanos, 1000000000LL);
280 long long days = logos_fdiv(total_seconds, 86400LL);
281 long long day_seconds = total_seconds - days * 86400LL;
282 long long hours = day_seconds / 3600;
283 long long minutes = (day_seconds % 3600) / 60;
284 long long y, m, d;
285 logos_civil(days, &y, &m, &d);
286 char *b = (char *)lc_gc_alloc_atomic(48);
287 snprintf(b, 48, "%04lld-%02lld-%02lld %02lld:%02lld", y, m, d, hours, minutes);
288 return b;
289}
290
291/* `today` / `now` — the injected fixed clock (env `LOGOS_FIXED_DAYS`/`LOGOS_FIXED_NANOS`, set by the
292 differential harness so the AOT binary and the VM oracle read the SAME instant) else the real clock. */
293long long logos_today(void) {
294 const char *e = getenv("LOGOS_FIXED_DAYS");
295 if (e) return atoll(e);
296 return (long long)(time(NULL) / 86400);
297}
298
299long long logos_now(void) {
300 const char *e = getenv("LOGOS_FIXED_NANOS");
301 if (e) return atoll(e);
302 return (long long)time(NULL) * 1000000000LL;
303}
304
305/* time.lg's `now()` — the Unix time in milliseconds from the real clock, as the interpreter reads it. */
306long long logos_time_now(void) {
307 struct timespec ts;
308 timespec_get(&ts, TIME_UTC);
309 return (long long)ts.tv_sec * 1000 + (long long)(ts.tv_nsec / 1000000);
310}
311
312/* env.lg's `get(name)`, in the halves the VM expands it into: whether the process environment sets the
313 variable, and its text (`""` when it is unset). */
314long long logos_env_has(const char *name) {
315 return getenv(name) != NULL;
316}
317
318char *logos_env_value(const char *name) {
319 const char *v = getenv(name);
320 return lc_gc_strdup(v ? v : "");
321}
322
323/* How a cell of the exact extension displays, set by the extension's adapter when the shim embeds it
324 (`codegen_riscv::runtime`); a program that holds no cell never reads it. */
325static char *(*logos_tower_text_hook)(long long);
326
327char *logos_val_to_str(long long bits, int kind) {
328 switch (kind) {
329 case 0: { char *b = (char *)lc_gc_alloc_atomic(24); snprintf(b, 24, "%lld", bits); return b; }
330 case 1: { double f; memcpy(&f, &bits, 8); return logos_fmt_f64(f); }
331 case 2: return lc_gc_strdup(bits ? "true" : "false");
332 case 4: return logos_ldec_str(bits); /* Rational */
333 case 5: return logos_date_str(bits); /* Date */
334 case 6: return logos_moment_str(bits); /* Moment */
335 case 7: return lc_gc_strdup("nothing"); /* a value-or-nothing holding nothing */
336 case 8: { /* a PeerAgent: `<peer topic>` */
337 size_t n = strlen((const char *)bits) + 8;
338 char *b = (char *)lc_gc_alloc_atomic(n);
339 snprintf(b, n, "<peer %s>", (const char *)bits);
340 return b;
341 }
342 case 9: return logos_tower_text_hook(bits); /* a cell of the exact extension */
343 default: return lc_gc_strdup((const char *)bits); /* Text: copy so the result is independently owned */
344 }
345}
346
347/* ---- A Text a register owns: `t = t + v` in place ----
348 While a register's owned flag is set, its Text is a block of its own — its length and capacity just before
349 the characters — which an append grows in place, amortized. Any other Text is copied into such a block
350 first, so an alias never sees the change. The characters stay an ordinary C string. */
351typedef struct { size_t len, cap; } logos_text_block;
352
353/* `t = t + v` into the register at `slot`: `v`'s text by its kind tag. `v` is read before anything moves. */
354void logos_text_append_own(char **slot, long long *owned, long long v, int tag) {
355 char *made = tag == 3 ? NULL : logos_val_to_str(v, tag);
356 const char *add = made ? made : (const char *)v;
357 size_t n = strlen(add);
358 logos_text_block *b;
359 if (*owned) {
360 b = (logos_text_block *)*slot - 1;
361 } else {
362 size_t len = strlen(*slot);
363 b = (logos_text_block *)lc_gc_alloc_atomic(sizeof *b + len + n + 1);
364 b->len = len;
365 b->cap = len + n;
366 memcpy(b + 1, *slot, len + 1);
367 *owned = 1;
368 }
369 if (b->len + n > b->cap) {
370 size_t cap = b->cap * 2 > b->len + n ? b->cap * 2 : b->len + n;
371 b = (logos_text_block *)lc_gc_realloc_atomic(b, sizeof *b + cap + 1);
372 b->cap = cap;
373 }
374 memcpy((char *)(b + 1) + b->len, add, n + 1);
375 b->len += n;
376 *slot = (char *)(b + 1);
377 lc_gc_free(made);
378}
379
380/* `Op::Concat` — stringify both operands (by kind) and join, matching the VM's `arith::concat`. */
381char *logos_concat_vals(long long a, int ak, long long b, int bk) {
382 char *sa = logos_val_to_str(a, ak);
383 char *sb = logos_val_to_str(b, bk);
384 size_t la = strlen(sa), lb = strlen(sb);
385 char *out = (char *)lc_gc_alloc_atomic(la + lb + 1);
386 memcpy(out, sa, la);
387 memcpy(out + la, sb, lb);
388 out[la + lb] = '\0';
389 lc_gc_free(sa);
390 lc_gc_free(sb);
391 return out;
392}
393
394/* `Show` of a Text value — raw contents + newline, exactly like the VM. */
395void logos_show_text(const char *s) { printf("%s\n", s); }
396
397/* `Fail with <value>.` — the value exactly as `Show` renders it. */
398void logos_fail_val(long long bits, int kind) { logos_fatal(logos_val_to_str(bits, kind)); }
399
400/* ---- Program arguments (`args()`) + `parseInt` ---- */
401static int logos_argc = 0;
402static char **logos_argv = NULL;
403void logos_set_args(int argc, char **argv) { logos_argc = argc; logos_argv = argv; }
404
405/* A failure naming a Text: `refusal` is a format of it (`%s`). */
406__attribute__((noreturn)) static void logos_fatals(const char *refusal, const char *text) {
407 size_t n = strlen(refusal) + strlen(text) + 1;
408 char *why = (char *)lc_gc_alloc_atomic(n);
409 snprintf(why, n, refusal, text);
410 logos_fatal(why);
411}
412
413/* `parseInt(t)` — the VM's: `t` trimmed (`str::trim`), then the whole of it an `i64` in base 10 as
414 `str::parse` reads one (an optional sign, then digits, in range). Anything else stops the program with
415 `logos_parse_int_refusal`. */
416long long logos_parse_int(const char *s) {
417 const char *refusal = logos_parse_int_refusal;
418 const char *p = logos_trim(s);
419 int negative = *p == '-';
420 if (*p == '+' || *p == '-') p++;
421 if (!*p) logos_fatals(refusal, s);
422 unsigned long long v = 0, limit = negative ? 9223372036854775808ULL : 9223372036854775807ULL;
423 for (; *p; p++) {
424 if (*p < '0' || *p > '9') logos_fatals(refusal, s);
425 unsigned long long d = (unsigned long long)(*p - '0');
426 if (v > (limit - d) / 10) logos_fatals(refusal, s);
427 v = v * 10 + d;
428 }
429 return negative ? (long long)(0 - v) : (long long)v;
430}
431
432/* Whether `s` spells `word`, ASCII case folded. */
433static int logos_ascii_is(const char *s, const char *word) {
434 for (; *word; s++, word++) {
435 char c = *s >= 'A' && *s <= 'Z' ? (char)(*s - 'A' + 'a') : *s;
436 if (c != *word) return 0;
437 }
438 return !*s;
439}
440
441/* `parseFloat(t)` — the VM's: `t` trimmed, then the whole of it an `f64` as `str::parse` reads one: an
442 optional sign, then `inf`, `infinity` or `nan` in any case, or digits with at most one point among them
443 and at least one digit, then an optional exponent (`e`, an optional sign, digits). Anything else stops the
444 program with `logos_parse_float_refusal`. The value is the correctly rounded one (`strtod`). */
445double logos_parse_float(const char *s) {
446 const char *refusal = logos_parse_float_refusal;
447 const char *t = logos_trim(s), *p = t;
448 int negative = *p == '-';
449 if (*p == '+' || *p == '-') p++;
450 if (logos_ascii_is(p, "inf") || logos_ascii_is(p, "infinity")) return negative ? -INFINITY : INFINITY;
451 if (logos_ascii_is(p, "nan")) return negative ? -NAN : NAN;
452 int digits = 0;
453 while (*p >= '0' && *p <= '9') { p++; digits++; }
454 if (*p == '.') {
455 p++;
456 while (*p >= '0' && *p <= '9') { p++; digits++; }
457 }
458 if (!digits) logos_fatals(refusal, s);
459 if (*p == 'e' || *p == 'E') {
460 p++;
461 if (*p == '+' || *p == '-') p++;
462 if (!(*p >= '0' && *p <= '9')) logos_fatals(refusal, s);
463 while (*p >= '0' && *p <= '9') p++;
464 }
465 if (*p) logos_fatals(refusal, s);
466 return strtod(t, NULL);
467}
468
469/* `chr(codepoint)` — a single-character UTF-8 Text handle, matching the VM's `char::from_u32`. */
470char *logos_chr(long long cp) {
471 char *out = (char *)lc_gc_alloc_atomic(5);
472 unsigned int c = (unsigned int)cp;
473 int n = 0;
474 if (c < 0x80) { out[n++] = (char)c; }
475 else if (c < 0x800) { out[n++] = (char)(0xC0 | (c >> 6)); out[n++] = (char)(0x80 | (c & 0x3F)); }
476 else if (c < 0x10000) {
477 out[n++] = (char)(0xE0 | (c >> 12));
478 out[n++] = (char)(0x80 | ((c >> 6) & 0x3F));
479 out[n++] = (char)(0x80 | (c & 0x3F));
480 } else {
481 out[n++] = (char)(0xF0 | (c >> 18));
482 out[n++] = (char)(0x80 | ((c >> 12) & 0x3F));
483 out[n++] = (char)(0x80 | ((c >> 6) & 0x3F));
484 out[n++] = (char)(0x80 | (c & 0x3F));
485 }
486 out[n] = '\0';
487 return out;
488}
489
490/* ---- CRDT counters (G-Counter / PN-Counter) — a per-replica count map ---- */
491/* The observable is the SUM; merge/join take the per-replica MAX (IDEMPOTENT — a redelivered state
492 never double-counts), so it converges regardless of order/duplication. The replica ids are drawn
493 from a local monotone sequence: the differential observable (the sum) depends only on the ids being
494 DISTINCT and STABLE within a run, not their absolute values, so it matches the VM whatever the VM's
495 own (process-global) replica sequence happens to be. */
496
497typedef struct { long long my_rid; long long len, cap; long long *rids; long long *cnts; } logos_crdt;
498
499static long long logos_replica_seq = 0;
500
501logos_crdt *logos_crdt_new(void) {
502 logos_crdt *c = (logos_crdt *)lc_gc_alloc(sizeof(logos_crdt));
503 c->my_rid = logos_replica_seq++;
504 c->len = 1; c->cap = 4;
505 c->rids = (long long *)lc_gc_alloc_atomic((size_t)c->cap * 8);
506 c->cnts = (long long *)lc_gc_alloc_atomic((size_t)c->cap * 8);
507 c->rids[0] = c->my_rid; c->cnts[0] = 0;
508 return c;
509}
510
511/* Increment (or decrement) THIS counter's own replica entry, in place. */
512void logos_crdt_bump(logos_crdt *c, long long amount, int negate) {
513 long long d = negate ? -amount : amount;
514 for (long long i = 0; i < c->len; i++) {
515 if (c->rids[i] == c->my_rid) { c->cnts[i] += d; return; }
516 }
517 /* my_rid always present (seeded in _new); defensive. */
518 c->cnts[0] += d;
519}
520
521long long logos_crdt_sum(const logos_crdt *c) {
522 long long s = 0;
523 for (long long i = 0; i < c->len; i++) s += c->cnts[i];
524 return s;
525}
526
527static void logos_crdt_put_max(logos_crdt *d, long long rid, long long cnt) {
528 for (long long i = 0; i < d->len; i++) {
529 if (d->rids[i] == rid) { if (cnt > d->cnts[i]) d->cnts[i] = cnt; return; }
530 }
531 if (d->len == d->cap) {
532 d->cap *= 2;
533 d->rids = (long long *)lc_gc_realloc_atomic(d->rids, (size_t)d->cap * 8);
534 d->cnts = (long long *)lc_gc_realloc_atomic(d->cnts, (size_t)d->cap * 8);
535 }
536 d->rids[d->len] = rid; d->cnts[d->len] = cnt; d->len++;
537}
538
539/* Lattice join: per-replica MAX of `s` into `d`, in place (idempotent, commutative, associative). */
540void logos_crdt_merge(logos_crdt *d, const logos_crdt *s) {
541 for (long long i = 0; i < s->len; i++) logos_crdt_put_max(d, s->rids[i], s->cnts[i]);
542}
543
544/* `copy` of a counter: the same state, the same replica. */
545logos_crdt *logos_crdt_clone(const logos_crdt *c) {
546 logos_crdt *d = (logos_crdt *)lc_gc_alloc(sizeof(logos_crdt));
547 *d = *c;
548 d->rids = (long long *)lc_gc_alloc_atomic((size_t)c->cap * 8);
549 d->cnts = (long long *)lc_gc_alloc_atomic((size_t)c->cap * 8);
550 memcpy(d->rids, c->rids, (size_t)c->len * 8);
551 memcpy(d->cnts, c->cnts, (size_t)c->len * 8);
552 return d;
553}
554
555/* ---- Closures — a heap `[body_index, captured values…, present flags…]` cell array ---- */
556
557long long *logos_closure_new(long long body, long long ncap) {
558 long long *c = (long long *)lc_gc_alloc((size_t)(1 + 2 * ncap) * 8);
559 c[0] = body;
560 return c;
561}
562
563void logos_closure_set(long long *c, long long idx, long long val) { c[idx] = val; }
564
565/* ---- `FormatValue` spec helpers (the spec is a compile-time constant, dispatched in codegen) ---- */
566
567/* Currency `$` — `${:.2}` (two decimals). Matches `apply_format_spec`'s float path. */
568char *logos_fmt_currency(double f) {
569 char *b = (char *)lc_gc_alloc_atomic(64);
570 snprintf(b, 64, "$%.2f", f);
571 return b;
572}
573
574/* Precision `.N` — `{:.N}` fixed-decimal float (round-to-nearest-even, matching Rust's Display). */
575char *logos_fmt_fixed(double f, int prec) {
576 char *b = (char *)lc_gc_alloc_atomic(64 + (size_t)prec);
577 snprintf(b, 64 + (size_t)prec, "%.*f", prec, f);
578 return b;
579}
580
581/* Alignment/width — pad `s` to `width` columns: dir '>' right, '<' left, '^' center (extra column on
582 the RIGHT, matching Rust's `{:^w}`). No truncation when `s` is already wide enough. */
583char *logos_pad(const char *s, long long width, char dir) {
584 size_t len = strlen(s);
585 if ((long long)len >= width) return lc_gc_strdup(s);
586 size_t pad = (size_t)width - len;
587 char *out = (char *)lc_gc_alloc_atomic((size_t)width + 1);
588 size_t left = dir == '>' ? pad : dir == '^' ? pad / 2 : 0;
589 size_t right = pad - left;
590 memset(out, ' ', left);
591 memcpy(out + left, s, len);
592 memset(out + left + len, ' ', right);
593 out[width] = '\0';
594 return out;
595}
596
597/* EXACT i64-vs-f64 comparison for a mixed Int/Float relational op — replicates the VM's
598 * `cmp_i64_f64_exact` + `rel` (crates/logicaffeine_base/src/numeric.rs, semantics/compare.rs). `i`/`f`
599 * are the numeric operands (the Int first regardless of source order); `reversed` = 1 when the SOURCE
600 * order was (Float, Int) so the comparison is (f vs i); `op` selects lt=0/gt=1/le=2/ge=3/eq=4/ne=5.
601 * Returns 0/1. A NaN operand is unordered: every ordering/eq is false, `!=` is true — matching `rel`. */
602int logos_cmp_i64_f64_rel(long long i, double f, int reversed, int op) {
603 int ord; /* -2 unordered, else -1 (i<f) / 0 (i==f) / 1 (i>f) */
604 if (isnan(f)) {
605 ord = -2;
606 } else if (isinf(f)) {
607 ord = (f > 0) ? -1 : 1;
608 } else if (i <= (1LL << 53) && i >= -(1LL << 53)) {
609 double di = (double)i; /* |i| <= 2^53 is exact */
610 ord = di < f ? -1 : (di > f ? 1 : 0);
611 } else if (f >= 9223372036854775808.0) { /* f >= 2^63 > any i64 */
612 ord = -1;
613 } else if (f < -9223372036854775808.0) { /* f < -2^63 < any i64 */
614 ord = 1;
615 } else {
616 long long t = (long long)f; /* trunc toward zero, exact for f in i64 range */
617 ord = i < t ? -1 : (i > t ? 1 : 0);
618 }
619 if (reversed && ord != -2) ord = -ord;
620 switch (op) {
621 case 0: return ord == -1; /* lt */
622 case 1: return ord == 1; /* gt */
623 case 2: return ord == -1 || ord == 0; /* le (None -> false) */
624 case 3: return ord == 0 || ord == 1; /* ge (None -> false) */
625 case 4: return ord == 0; /* eq (None -> false) */
626 default: return ord != 0; /* ne (None -> true) */
627 }
628}
629
630
631/* ---- Seq (heap growable array of uniform 8-byte cells) ----
632 * Every scalar element (i64 / f64-bits / char*) is one 8-byte cell; the element KIND is tracked by the
633 * emitter and only needed for Show. VALUE SEMANTICS BY OWNERSHIP: `Move` copies the handle (share), and a
634 * register's collection is mutated only through a `_own` helper handed the register's SLOT and its OWNED
635 * flag. A clear flag means the handle may be reachable from somewhere else (an alias, a global, a field, an
636 * iterator, a closure), so the helper first copies the collection into the slot and sets the flag; then it
637 * mutates in place. An alias or an in-flight iterator keeps its own list — the VM's copy-on-write and
638 * snapshot — and a register that owns its collection mutates it in amortized O(1). The emitter keeps the
639 * flag: an op that makes a fresh collection sets it, any op that could leave the handle elsewhere clears it. */
640typedef struct { long long len, cap; long long *data; } logos_seq;
641
642/* An empty sequence with room for `capacity` cells, in one block: the cells follow the header until the sequence
643 first outgrows them (`logos_seq_room`). A record, a tuple and an enum value never grow, so each is one block. */
644logos_seq *logos_seq_new_cap(long long capacity) {
645 if (capacity < 0) capacity = 0;
646 logos_seq *s = (logos_seq *)lc_gc_alloc(sizeof(logos_seq) + (size_t)capacity * 8);
647 s->len = 0;
648 s->cap = capacity;
649 s->data = capacity ? (long long *)(s + 1) : NULL;
650 return s;
651}
652
653/* Room for `cap` cells: cells that still follow the header move to a block of their own, any other block resizes. */
654static void logos_seq_room(logos_seq *a, long long cap) {
655 if (a->data == (long long *)(a + 1)) {
656 long long *cells = (long long *)lc_gc_alloc((size_t)cap * 8);
657 memcpy(cells, a->data, (size_t)a->len * 8);
658 a->data = cells;
659 } else {
660 a->data = (long long *)lc_gc_realloc(a->data, (size_t)cap * 8);
661 }
662 a->cap = cap;
663}
664
665/* A fresh sequence, with room for the cells its first pushes add: an enum value's tag and payload, a tuple's items,
666 a list's first items all go in its one block. */
667logos_seq *logos_seq_new(void) { return logos_seq_new_cap(4); }
668
669static logos_seq *logos_seq_dup(const logos_seq *a) {
670 logos_seq *s = logos_seq_new_cap(a->len);
671 s->len = a->len;
672 if (a->len) memcpy(s->data, a->data, (size_t)a->len * 8);
673 return s;
674}
675
676/* In-place append, into a collection its caller has made its own; the header pointer identity stays stable
677 (only the cells move), and growth is taken out of line. */
678__attribute__((noinline)) static void logos_seq_push_growing(logos_seq *a, long long val) {
679 logos_seq_room(a, a->cap ? a->cap * 2 : 4);
680 a->data[a->len++] = val;
681}
682
683void logos_seq_push_inplace(logos_seq *a, long long val) {
684 if (__builtin_expect(a->len == a->cap, 0)) {
685 logos_seq_push_growing(a, val);
686 return;
687 }
688 a->data[a->len++] = val;
689}
690
691/* The collection in `*slot`, made the register's own: a copy when `*owned` is clear. */
692static logos_seq *logos_seq_own(logos_seq **slot, long long *owned) {
693 if (!*owned) { *slot = logos_seq_dup(*slot); *owned = 1; }
694 return *slot;
695}
696
697/* The register's own collection, for an op that changes several of its cells in place (`Merge`): the
698 emitter calls it only when the register's owned flag is clear. */
699logos_seq *logos_seq_make_own(logos_seq **slot, long long *owned) {
700 return logos_seq_own(slot, owned);
701}
702
703__attribute__((noinline)) static void logos_seq_push_copying(logos_seq **slot, long long *owned, long long val) {
704 logos_seq_push_inplace(logos_seq_own(slot, owned), val);
705}
706
707/* `Push val to xs`. `val` arrives by value, read before the register is made its own. */
708void logos_seq_push_own(logos_seq **slot, long long *owned, long long val) {
709 if (__builtin_expect(!*owned, 0)) {
710 logos_seq_push_copying(slot, owned, val);
711 return;
712 }
713 logos_seq_push_inplace(*slot, val);
714}
715
716/* `args()` → a `Seq of Text` of the process argv (index 1 is the program name, exactly as the VM's
717 `program_args`), so `item 2 of args()` is the first real argument. */
718logos_seq *logos_args(void) {
719 logos_seq *s = logos_seq_new();
720 for (int i = 0; i < logos_argc; i++) logos_seq_push_inplace(s, (long long)logos_argv[i]);
721 return s;
722}
723
724long long logos_seq_len(const logos_seq *a) { return a->len; }
725
726/* `Pop from xs into v` — the last element, removed (`present` 1), or `nothing` from an empty list (`present`
727 0). An empty pop changes nothing, so it leaves the register's ownership as it found it. */
728/* The call sites whose callee stops the program itself rather than answer nothing: pairs of a return
729 address and the sentence to stop with, ended by a zero pair. The emitter writes the table; a helper
730 searches it only on its way to stopping. */
731extern const struct { const void *site; const char *reason; } logos_fail_sites[];
732
733__attribute__((noreturn, noinline)) static void logos_fail_at(const void *site) {
734 for (long long i = 0; logos_fail_sites[i].site; i++) {
735 if (logos_fail_sites[i].site == site) logos_fatal(logos_fail_sites[i].reason);
736 }
737 abort();
738}
739
740typedef struct { long long present, value; } logos_popped;
741logos_popped logos_seq_pop_own(logos_seq **slot, long long *owned) {
742 logos_popped r = {0, 0};
743 if (!(*slot)->len) return r;
744 logos_seq *s = logos_seq_own(slot, owned);
745 r.present = 1;
746 r.value = s->data[--s->len];
747 return r;
748}
749
750/* `Pop from xs into v` where the op after it fails on `nothing`: the last element, or, from an empty list,
751 the program stopped with the sentence registered for the call site. */
752__attribute__((noinline)) long long logos_seq_pop_value(logos_seq **slot, long long *owned) {
753 if (!(*slot)->len) logos_fail_at(__builtin_return_address(0));
754 logos_seq *s = logos_seq_own(slot, owned);
755 return s->data[--s->len];
756}
757
758/* In-place indexed write (1-based) — a struct-field mutation that must be visible through every alias
759 of the struct (`CrdtBump`'s counter update; the seq header pointer stays stable). */
760__attribute__((noinline, cold)) static void logos_seq_set_from_back(logos_seq *a, long long i, long long val) {
761 a->data[logos_index_from_back(i, a->len)] = val;
762}
763
764void logos_seq_set_inplace(logos_seq *a, long long i, long long val) {
765 if (__builtin_expect(i < 1 || i > a->len, 0)) {
766 logos_seq_set_from_back(a, i, val);
767 return;
768 }
769 a->data[i - 1] = val;
770}
771
772/* `Set item i of xs to val` (1-based, or from the back when negative), and a struct's field write (its cells
773 are a seq). An index out of range stops the program as the VM does, before anything is copied. */
774__attribute__((noinline)) static void logos_seq_set_own_copying(logos_seq **slot, long long *owned, long long i, long long val) {
775 long long at = logos_resolve_index(i, (*slot)->len);
776 logos_seq_own(slot, owned)->data[at] = val;
777}
778
779void logos_seq_set_own(logos_seq **slot, long long *owned, long long i, long long val) {
780 logos_seq *s = *slot;
781 if (__builtin_expect(*owned && i >= 1 && i <= s->len, 1)) {
782 s->data[i - 1] = val;
783 return;
784 }
785 logos_seq_set_own_copying(slot, owned, i, val);
786}
787
788/* The value in field `cell` (1-based) of the record in `*slot`, made the register's own record's own: the record
789 copied unless `*owned` says the register is its only holder, the value copied with `copy` unless
790 `*field_owned` says the record is its only holder, and both flags set. */
791__attribute__((noinline)) static long long logos_field_own(logos_seq **slot, long long *owned, long long *field_owned,
792 long long cell, long long (*copy)(long long)) {
793 logos_seq *record = logos_seq_own(slot, owned);
794 long long value = record->data[cell - 1];
795 if (!*field_owned) {
796 value = copy(value);
797 record->data[cell - 1] = value;
798 *field_owned = 1;
799 }
800 return value;
801}
802
803static long long logos_field_seq_copy(long long list) { return (long long)logos_seq_dup((const logos_seq *)list); }
804
805__attribute__((noinline)) static void logos_field_push_copying(logos_seq **slot, long long *owned, long long *field_owned,
806 long long cell, long long val) {
807 logos_seq_push_inplace((logos_seq *)logos_field_own(slot, owned, field_owned, cell, logos_field_seq_copy), val);
808}
809
810/* `Push val to r's field` — into the register's own record's own list: in place while the field flag is set
811 (the emitter keeps a field flag set only while its register's owned flag is), so a loop of pushes copies at
812 most once. */
813void logos_field_push_own(logos_seq **slot, long long *owned, long long *field_owned, long long cell, long long val) {
814 if (__builtin_expect(!*field_owned, 0)) {
815 logos_field_push_copying(slot, owned, field_owned, cell, val);
816 return;
817 }
818 logos_seq_push_inplace((logos_seq *)(*slot)->data[cell - 1], val);
819}
820
821static long long logos_field_crdt_copy(long long c) { return (long long)logos_crdt_clone((const logos_crdt *)c); }
822
823__attribute__((noinline)) static void logos_field_bump_copying(logos_seq **slot, long long *owned, long long *field_owned,
824 long long cell, long long amount, int negate) {
825 logos_crdt_bump((logos_crdt *)logos_field_own(slot, owned, field_owned, cell, logos_field_crdt_copy), amount, negate);
826}
827
828/* `Increase/Decrease r's counter by amount` — the register's own record's own counter: in place while the
829 field flag is set. */
830void logos_field_bump_own(logos_seq **slot, long long *owned, long long *field_owned, long long cell, long long amount,
831 int negate) {
832 if (__builtin_expect(!*field_owned, 0)) {
833 logos_field_bump_copying(slot, owned, field_owned, cell, amount, negate);
834 return;
835 }
836 logos_crdt_bump((logos_crdt *)(*slot)->data[cell - 1], amount, negate);
837}
838
839/* `a followed by b` — fresh seq with a's elements then b's. */
840logos_seq *logos_seq_concat(const logos_seq *a, const logos_seq *b) {
841 logos_seq *s = logos_seq_new_cap(a->len + b->len);
842 s->len = a->len + b->len;
843 if (a->len) memcpy(s->data, a->data, (size_t)a->len * 8);
844 if (b->len) memcpy(s->data + a->len, b->data, (size_t)b->len * 8);
845 return s;
846}
847
848/* `items start through end of a` — the VM's `slice`: the 1-based inclusive range, and empty unless it lies
849 within the list (`slice.get(start - 1 .. end)`, the bounds read as unsigned). */
850logos_seq *logos_seq_slice(const logos_seq *a, long long start, long long end) {
851 unsigned long long from = (unsigned long long)start ? (unsigned long long)start - 1 : 0, to = (unsigned long long)end;
852 logos_seq *s = logos_seq_new();
853 if (from < to && to <= (unsigned long long)a->len) {
854 for (unsigned long long i = from; i < to; i++) logos_seq_push_inplace(s, a->data[i]);
855 }
856 return s;
857}
858
859/* Element equality by kind (0=Int, 1=Float, 3=Text) — the VM's `values_equal` for scalar cells. */
860static int logos_cell_eq(long long a, long long b, int kind) {
861 if (kind == 3) return strcmp((const char *)a, (const char *)b) == 0;
862 if (kind == 1) { double x, y; memcpy(&x, &a, 8); memcpy(&y, &b, 8); return x == y; }
863 return a == b;
864}
865
866/* Two struct fields compared by kind (a policy `ObjectFieldEquals` — the VM's `values_equal`). The
867 cells are 1-based; struct field indices are always in range. */
868int logos_struct_field_eq(logos_seq *a, long long ai, logos_seq *b, long long bi, int kind) {
869 return logos_cell_eq(a->data[ai - 1], b->data[bi - 1], kind);
870}
871
872/* `a contains val` — membership by element kind. */
873int logos_seq_contains(const logos_seq *a, long long val, int kind) {
874 for (long long i = 0; i < a->len; i++) {
875 if (logos_cell_eq(a->data[i], val, kind)) return 1;
876 }
877 return 0;
878}
879
880/* `a contains val` where the items or the needle are rationals (`items_rational`, `val_rational`): by value, an Int
881 by the rational it equals. */
882int logos_seq_contains_exact(const logos_seq *a, int items_rational, long long val, int val_rational) {
883 for (long long i = 0; i < a->len; i++) {
884 if (logos_ldec_cmp(a->data[i], items_rational, val, val_rational) == 0) return 1;
885 }
886 return 0;
887}
888
889/* An Int put among rationals goes in as the rational it equals: a literal's item, a pushed one, one stored. */
890void logos_seq_push_inplace_ldec(logos_seq *a, long long v) { logos_seq_push_inplace(a, logos_ldec_of_int(v)); }
891void logos_seq_push_own_ldec(logos_seq **slot, long long *owned, long long v) {
892 logos_seq_push_own(slot, owned, logos_ldec_of_int(v));
893}
894void logos_seq_set_own_ldec(logos_seq **slot, long long *owned, long long i, long long v) {
895 logos_seq_set_own(slot, owned, i, logos_ldec_of_int(v));
896}
897
898/* A list of Ints as the list of the rationals they equal. */
899logos_seq *logos_seq_as_ldec(const logos_seq *a) {
900 logos_seq *s = logos_seq_new();
901 for (long long i = 0; i < a->len; i++) logos_seq_push_inplace_ldec(s, a->data[i]);
902 return s;
903}
904
905/* ---- Set (insertion-ordered, dedup'd — shares the logos_seq storage) ---- */
906
907/* `Add val to s` — append if not already present (dedup). A present element changes nothing. */
908void logos_set_add_own(logos_seq **slot, long long *owned, long long val, int kind) {
909 if (logos_seq_contains(*slot, val, kind)) return;
910 logos_seq_push_inplace(logos_seq_own(slot, owned), val);
911}
912
913/* `Remove val from s` — drop every matching element, keeping the order of the rest. An absent element
914 changes nothing. */
915void logos_set_remove_own(logos_seq **slot, long long *owned, long long val, int kind) {
916 if (!logos_seq_contains(*slot, val, kind)) return;
917 logos_seq *s = logos_seq_own(slot, owned);
918 long long kept = 0;
919 for (long long i = 0; i < s->len; i++) {
920 if (!logos_cell_eq(s->data[i], val, kind)) s->data[kept++] = s->data[i];
921 }
922 s->len = kept;
923}
924
925/* `a union b` — a's elements, then b's novel elements (insertion-ordered). */
926logos_seq *logos_set_union(const logos_seq *a, const logos_seq *b, int kind) {
927 logos_seq *s = logos_seq_dup(a);
928 for (long long i = 0; i < b->len; i++) {
929 if (!logos_seq_contains(s, b->data[i], kind)) {
930 if (s->len == s->cap) logos_seq_room(s, s->cap ? s->cap * 2 : 4);
931 s->data[s->len++] = b->data[i];
932 }
933 }
934 return s;
935}
936
937/* `a intersect b` — a's elements that are also in b (a's order). */
938logos_seq *logos_set_intersect(const logos_seq *a, const logos_seq *b, int kind) {
939 logos_seq *s = logos_seq_new();
940 for (long long i = 0; i < a->len; i++) {
941 if (logos_seq_contains(b, a->data[i], kind)) {
942 if (s->len == s->cap) logos_seq_room(s, s->cap ? s->cap * 2 : 4);
943 s->data[s->len++] = a->data[i];
944 }
945 }
946 return s;
947}
948
949/* `Show` of a set — `{1, 2, 3}` (braces; insertion order; text elements unquoted). */
950void logos_set_show_int(const logos_seq *a) {
951 putchar('{');
952 for (long long i = 0; i < a->len; i++) { if (i) fputs(", ", stdout); printf("%lld", a->data[i]); }
953 fputs("}\n", stdout);
954}
955void logos_set_show_text(const logos_seq *a) {
956 putchar('{');
957 for (long long i = 0; i < a->len; i++) { if (i) fputs(", ", stdout); fputs((const char *)a->data[i], stdout); }
958 fputs("}\n", stdout);
959}
960
961/* `copy of a` — deep clone. */
962logos_seq *logos_seq_clone(const logos_seq *a) { return logos_seq_dup(a); }
963
964/* A struct is a fixed array of `n` field cells (declaration order), zero-initialized. */
965logos_seq *logos_struct_new(long long n) {
966 logos_seq *s = logos_seq_new_cap(n);
967 if (n > 0) memset(s->data, 0, (size_t)n * 8);
968 s->len = n > 0 ? n : 0;
969 return s;
970}
971
972/* Print without a trailing newline (for building `Name { f: v, ... }` piece by piece). */
973void logos_print_raw(const char *s) { fputs(s, stdout); }
974void logos_print_nl(void) { putchar('\n'); }
975
976/* Granlund–Montgomery / libdivide unsigned magic division — a byte-for-byte port of the VM's
977 * `magic_eval` (vm/compiler.rs). Emitted only for a constant non-power-of-two divisor with an
978 * Oracle-proven non-negative dividend; `mul_back == 0` is `/`, `mul_back == c` is `%`. */
979long long logos_magic_div(long long x, unsigned long long magic, int more, long long mul_back) {
980 unsigned long long n = (unsigned long long)x;
981 unsigned long long q;
982 if (more & 0x80) {
983 q = n >> (more & 0x3F);
984 } else {
985 unsigned long long hi = (unsigned long long)(((unsigned __int128)magic * (unsigned __int128)n) >> 64);
986 if (more & 0x40) {
987 unsigned long long t = ((n - hi) >> 1) + hi;
988 q = t >> (more & 0x3F);
989 } else {
990 q = hi >> (more & 0x3F);
991 }
992 }
993 if (mul_back == 0) return (long long)q;
994 return x - (long long)q * mul_back;
995}
996
997/* Integer exponentiation by squaring (wrapping, matching the VM's in-i64 result). A negative exponent, or
998 * one past 32 bits, stops the program with the VM's sentence. */
999long long logos_ipow(long long base, long long exp) {
1000 if (exp < 0) logos_fatal(logos_negative_exponent_refusal);
1001 if (exp > 4294967295LL) logos_fatal(logos_exponent_too_large_refusal);
1002 unsigned long long r = 1, b = (unsigned long long)base, e = (unsigned long long)exp;
1003 while (e > 0) { if (e & 1) r *= b; b *= b; e >>= 1; }
1004 return (long long)r;
1005}
1006
1007/* `lo to hi` — a materialized inclusive integer range (empty if lo > hi). */
1008logos_seq *logos_seq_range(long long lo, long long hi) {
1009 logos_seq *s = logos_seq_new();
1010 for (long long v = lo; v <= hi; v++) {
1011 if (s->len == s->cap) logos_seq_room(s, s->cap ? s->cap * 2 : 8);
1012 s->data[s->len++] = v;
1013 }
1014 return s;
1015}
1016
1017/* `start to end`, and `items start through end of a`, with a bound that is a rational (`lf`, `hf`): each the
1018 integer a whole one is, a fraction or one past i64 refused in the VM's words for the place. */
1019logos_seq *logos_seq_range_ldec(long long lo, int lf, long long hi, int hf, const char *past_word, const char *fraction) {
1020 return logos_seq_range(lf ? logos_ldec_as_i64(lo, past_word, fraction) : lo,
1021 hf ? logos_ldec_as_i64(hi, past_word, fraction) : hi);
1022}
1023logos_seq *logos_seq_slice_ldec(const logos_seq *a, long long start, int sf, long long end, int ef,
1024 const char *past_word, const char *fraction) {
1025 return logos_seq_slice(a, sf ? logos_ldec_as_i64(start, past_word, fraction) : start,
1026 ef ? logos_ldec_as_i64(end, past_word, fraction) : end);
1027}
1028
1029/* `item i of xs` — 1-based, or from the back when negative; an index out of range stops the program as the
1030 VM does. */
1031__attribute__((noinline, cold)) static long long logos_seq_index_from_back(const logos_seq *a, long long i) {
1032 return a->data[logos_index_from_back(i, a->len)];
1033}
1034
1035long long logos_seq_index(const logos_seq *a, long long i) {
1036 if (__builtin_expect(i < 1 || i > a->len, 0)) return logos_seq_index_from_back(a, i);
1037 return a->data[i - 1];
1038}
1039
1040/* `length of text` — BYTE length (the VM's `Text` length is `s.len()`, not the char count). */
1041long long logos_text_len(const char *s) { return (long long)strlen(s); }
1042
1043/* `item i of text` — 1-based character access returning a single-character Text, matching the VM's
1044 `index_get`: an ASCII string indexes by byte; otherwise the i-th Unicode scalar (negative i counts
1045 from the end); an out-of-range index errors like the VM (exit nonzero). */
1046char *logos_text_index(const char *s, long long i) {
1047 long long blen = (long long)strlen(s);
1048 int ascii = 1;
1049 for (long long k = 0; k < blen; k++) {
1050 if ((unsigned char)s[k] >= 0x80) { ascii = 0; break; }
1051 }
1052 if (ascii && i >= 1 && i <= blen) {
1053 char *out = (char *)lc_gc_alloc_atomic(2);
1054 out[0] = s[i - 1];
1055 out[1] = '\0';
1056 return out;
1057 }
1058 long long char_len = blen;
1059 if (!ascii) {
1060 char_len = 0;
1061 for (long long k = 0; k < blen;) {
1062 unsigned char c = (unsigned char)s[k];
1063 int n = c < 0x80 ? 1 : c < 0xE0 ? 2 : c < 0xF0 ? 3 : 4;
1064 k += n;
1065 char_len++;
1066 }
1067 }
1068 long long idx;
1069 if (i >= 1) {
1070 idx = i - 1;
1071 if (idx >= char_len) goto oob;
1072 } else if (i <= -1) {
1073 long long back = -i;
1074 if (back > char_len) goto oob;
1075 idx = char_len - back;
1076 } else {
1077 goto oob;
1078 }
1079 {
1080 long long k = 0, ci = 0;
1081 while (k < blen) {
1082 unsigned char c = (unsigned char)s[k];
1083 int n = c < 0x80 ? 1 : c < 0xE0 ? 2 : c < 0xF0 ? 3 : 4;
1084 if (ci == idx) {
1085 char *out = (char *)lc_gc_alloc_atomic(n + 1);
1086 for (int t = 0; t < n; t++) out[t] = s[k + t];
1087 out[n] = '\0';
1088 return out;
1089 }
1090 k += n;
1091 ci++;
1092 }
1093 }
1094oob:
1095 logos_fatalf(logos_index_refusal, i);
1096}
1097
1098/* ---- Files: the VM's sandboxed in-memory VFS, which keeps files only inside a `## Test`'s sealed world ----
1099 A program keeps none, so every read finds nothing and stops the program with `missing` (a format of the
1100 path) — the VM's sentence, taken from the VM by the emitter. */
1101__attribute__((noreturn)) void logos_vfs_read(const char *path, const char *missing) {
1102 size_t n = strlen(missing) + strlen(path) + 1;
1103 char *why = (char *)lc_gc_alloc_atomic(n);
1104 snprintf(why, n, missing, path);
1105 logos_fatal(why);
1106}
1107
1108/* `Repeat for c in t` — the Text's characters, each a Text of its own. */
1109logos_seq *logos_text_chars(const char *s) {
1110 logos_seq *out = logos_seq_new();
1111 const unsigned char *p = (const unsigned char *)s;
1112 while (*p) {
1113 unsigned cp;
1114 int n = logos_utf8_at(p, &cp);
1115 char *c = (char *)lc_gc_alloc_atomic((size_t)n + 1);
1116 memcpy(c, p, (size_t)n);
1117 c[n] = '\0';
1118 logos_seq_push_inplace(out, (long long)c);
1119 p += n;
1120 }
1121 return out;
1122}
1123
1124/* Eight bytes from the system's entropy; `getentropy` fails only for a request over 256 bytes. */
1125static unsigned long long logos_entropy(void) {
1126 unsigned long long draw;
1127 if (getentropy(&draw, sizeof draw) != 0) abort();
1128 return draw;
1129}
1130
1131/* `randomInt(lo, hi)` — uniform over `lo..=hi` from the system's entropy (rejection sampling, so no bound is
1132 favored); crossed bounds stop the program with `logos_random_bounds_refusal`. */
1133long long logos_random_int(long long lo, long long hi) {
1134 if (lo > hi) {
1135 char why[256];
1136 snprintf(why, sizeof why, logos_random_bounds_refusal, lo, hi);
1137 logos_fatal(lc_gc_strdup(why));
1138 }
1139 unsigned long long span = (unsigned long long)hi - (unsigned long long)lo, draw, limit;
1140 if (span == ~0ULL) return (long long)logos_entropy();
1141 limit = ~0ULL - (~0ULL % (span + 1) + 1) % (span + 1);
1142 do draw = logos_entropy(); while (draw > limit);
1143 return (long long)((unsigned long long)lo + draw % (span + 1));
1144}
1145
1146/* `text_bytes(t)` — each UTF-8 byte of the Text, as an Int 0..255. */
1147logos_seq *logos_text_bytes(const char *s) {
1148 logos_seq *out = logos_seq_new();
1149 for (const unsigned char *p = (const unsigned char *)s; *p; p++) logos_seq_push_inplace(out, *p);
1150 return out;
1151}
1152
1153/* `readReals(path)` — the file's little-endian binary64 values, eight bytes a value in order, each slot of the
1154 Seq of Real the value's bits; refused as `logicaffeine_system::file::read_reals` refuses: a file it cannot read,
1155 or one that is not a whole number of values. */
1156logos_seq *logos_read_reals(const char *path) {
1157 FILE *f = fopen(path, "rb");
1158 char *why;
1159 size_t n;
1160 if (!f) {
1161 const char *reason = strerror(errno);
1162 n = strlen(path) + strlen(reason) + 64;
1163 why = (char *)lc_gc_alloc_atomic(n);
1164 snprintf(why, n, "readReals: cannot read '%s': %s", path, reason);
1165 logos_fatal(why);
1166 }
1167 fseek(f, 0, SEEK_END);
1168 long size = ftell(f);
1169 fseek(f, 0, SEEK_SET);
1170 if (size < 0 || size % 8 != 0) {
1171 fclose(f);
1172 n = strlen(path) + 96;
1173 why = (char *)lc_gc_alloc_atomic(n);
1174 snprintf(why, n, "readReals: '%s' holds %ld bytes, not a whole number of 8-byte Reals", path, size);
1175 logos_fatal(why);
1176 }
1177 logos_seq *out = logos_seq_new_cap(size / 8 > 0 ? size / 8 : 1);
1178 unsigned char b[8];
1179 for (long i = 0; i < size / 8; i++) {
1180 if (fread(b, 1, 8, f) != 8) logos_fatals("readReals: cannot read '%s'", path);
1181 unsigned long long bits = 0;
1182 for (int k = 7; k >= 0; k--) bits = (bits << 8) | b[k];
1183 logos_seq_push_inplace(out, (long long)bits);
1184 }
1185 fclose(f);
1186 return out;
1187}
1188
1189/* The length a UTF-8 sequence led by `b` claims, 0 for a byte no sequence starts with — Rust's
1190 `utf8_char_width`. */
1191static int logos_utf8_width(unsigned b) {
1192 if (b < 0x80) return 1;
1193 if (b < 0xC2) return 0;
1194 if (b < 0xE0) return 2;
1195 if (b < 0xF0) return 3;
1196 if (b < 0xF5) return 4;
1197 return 0;
1198}
1199
1200/* Rust's UTF-8 validation (`core::str::from_utf8`), step for step: 1 when `v[0..n)` is UTF-8, else 0 with
1201 where the valid prefix ends and the length of the invalid sequence there — 0 when the bytes end inside a
1202 sequence — the two facts `Utf8Error` reports. */
1203static int logos_utf8_valid(const unsigned char *v, long long n, long long *valid_up_to, int *error_len) {
1204 long long i = 0;
1205 while (i < n) {
1206 long long start = i;
1207 unsigned first = v[i], b;
1208#define LOGOS_UTF8_FAIL(len) do { *valid_up_to = start; *error_len = (len); return 0; } while (0)
1209#define LOGOS_UTF8_NEXT() do { if (++i >= n) LOGOS_UTF8_FAIL(0); b = v[i]; } while (0)
1210#define LOGOS_UTF8_CONT(len) do { LOGOS_UTF8_NEXT(); if (b < 0x80 || b >= 0xC0) LOGOS_UTF8_FAIL(len); } while (0)
1211 if (first >= 0x80) {
1212 switch (logos_utf8_width(first)) {
1213 case 2:
1214 LOGOS_UTF8_CONT(1);
1215 break;
1216 case 3:
1217 LOGOS_UTF8_NEXT();
1218 if (!((first == 0xE0 && b >= 0xA0 && b <= 0xBF) || (first >= 0xE1 && first <= 0xEC && b >= 0x80 && b <= 0xBF)
1219 || (first == 0xED && b >= 0x80 && b <= 0x9F) || (first >= 0xEE && b >= 0x80 && b <= 0xBF)))
1220 LOGOS_UTF8_FAIL(1);
1221 LOGOS_UTF8_CONT(2);
1222 break;
1223 case 4:
1224 LOGOS_UTF8_NEXT();
1225 if (!((first == 0xF0 && b >= 0x90 && b <= 0xBF) || (first >= 0xF1 && first <= 0xF3 && b >= 0x80 && b <= 0xBF)
1226 || (first == 0xF4 && b >= 0x80 && b <= 0x8F)))
1227 LOGOS_UTF8_FAIL(1);
1228 LOGOS_UTF8_CONT(2);
1229 LOGOS_UTF8_CONT(3);
1230 break;
1231 default:
1232 LOGOS_UTF8_FAIL(1);
1233 }
1234 }
1235#undef LOGOS_UTF8_CONT
1236#undef LOGOS_UTF8_NEXT
1237#undef LOGOS_UTF8_FAIL
1238 i++;
1239 }
1240 return 1;
1241}
1242
1243/* `text_from_bytes(bytes)` — the low byte of each element, as a Text when they are UTF-8. Otherwise the
1244 program stops with `logos_utf8_invalid_refusal` (of the invalid sequence's length and where it starts) or
1245 `logos_utf8_incomplete_refusal` (of where the unfinished sequence starts). */
1246char *logos_text_from_bytes(const logos_seq *b) {
1247 unsigned char *bytes = (unsigned char *)lc_gc_alloc_atomic((size_t)b->len + 1);
1248 for (long long i = 0; i < b->len; i++) bytes[i] = (unsigned char)(b->data[i] & 0xff);
1249 bytes[b->len] = '\0';
1250 long long at;
1251 int len;
1252 if (!logos_utf8_valid(bytes, b->len, &at, &len)) {
1253 char why[512];
1254 if (len) snprintf(why, sizeof why, logos_utf8_invalid_refusal, len, at);
1255 else snprintf(why, sizeof why, logos_utf8_incomplete_refusal, at);
1256 logos_fatal(lc_gc_strdup(why));
1257 }
1258 return (char *)bytes;
1259}
1260
1261void logos_seq_show_int(const logos_seq *a) {
1262 putchar('[');
1263 for (long long i = 0; i < a->len; i++) { if (i) fputs(", ", stdout); printf("%lld", a->data[i]); }
1264 fputs("]\n", stdout);
1265}
1266
1267void logos_seq_show_bool(const logos_seq *a) {
1268 putchar('[');
1269 for (long long i = 0; i < a->len; i++) { if (i) fputs(", ", stdout); fputs(a->data[i] ? "true" : "false", stdout); }
1270 fputs("]\n", stdout);
1271}
1272
1273void logos_seq_show_float(const logos_seq *a) {
1274 putchar('[');
1275 for (long long i = 0; i < a->len; i++) {
1276 if (i) fputs(", ", stdout);
1277 double f; memcpy(&f, &a->data[i], 8);
1278 char *s = logos_fmt_f64(f); fputs(s, stdout); lc_gc_free(s);
1279 }
1280 fputs("]\n", stdout);
1281}
1282
1283/* Text elements render UNQUOTED inside a list, like the VM's `[foo, bar]`. */
1284void logos_seq_show_text(const logos_seq *a) {
1285 putchar('[');
1286 for (long long i = 0; i < a->len; i++) { if (i) fputs(", ", stdout); fputs((const char *)a->data[i], stdout); }
1287 fputs("]\n", stdout);
1288}
1289
1290/* ---- Map (insertion-ordered key→value; parallel key/value arrays of uniform 8-byte cells) ---- */
1291typedef struct { long long len, cap; long long *keys; long long *vals; } logos_map;
1292
1293logos_map *logos_map_new(void) {
1294 logos_map *m = (logos_map *)lc_gc_alloc(sizeof(logos_map));
1295 m->len = 0; m->cap = 0; m->keys = NULL; m->vals = NULL;
1296 return m;
1297}
1298
1299logos_map *logos_map_new_cap(long long capacity) {
1300 if (capacity < 0) capacity = 0;
1301 logos_map *m = (logos_map *)lc_gc_alloc(sizeof(logos_map));
1302 m->len = 0; m->cap = capacity;
1303 m->keys = capacity ? (long long *)lc_gc_alloc((size_t)capacity * 8) : NULL;
1304 m->vals = capacity ? (long long *)lc_gc_alloc((size_t)capacity * 8) : NULL;
1305 return m;
1306}
1307
1308static logos_map *logos_map_dup(const logos_map *a) {
1309 logos_map *m = (logos_map *)lc_gc_alloc(sizeof(logos_map));
1310 m->len = a->len; m->cap = a->len;
1311 m->keys = a->len ? (long long *)lc_gc_alloc((size_t)a->len * 8) : NULL;
1312 m->vals = a->len ? (long long *)lc_gc_alloc((size_t)a->len * 8) : NULL;
1313 if (a->len) { memcpy(m->keys, a->keys, (size_t)a->len * 8); memcpy(m->vals, a->vals, (size_t)a->len * 8); }
1314 return m;
1315}
1316
1317/* `Set item key of m to val` — insert or update (insertion-ordered), in the register's own map (a copy
1318 when its owned flag is clear; see the Seq section). `key_kind` selects key compare. */
1319void logos_map_set_own(logos_map **slot, long long *owned, long long key, long long val, int key_kind) {
1320 if (!*owned) { *slot = logos_map_dup(*slot); *owned = 1; }
1321 logos_map *m = *slot;
1322 for (long long i = 0; i < m->len; i++) {
1323 if (logos_cell_eq(m->keys[i], key, key_kind)) { m->vals[i] = val; return; }
1324 }
1325 if (m->len == m->cap) {
1326 m->cap = m->cap ? m->cap * 2 : 4;
1327 m->keys = (long long *)lc_gc_realloc(m->keys, (size_t)m->cap * 8);
1328 m->vals = (long long *)lc_gc_realloc(m->vals, (size_t)m->cap * 8);
1329 }
1330 m->keys[m->len] = key; m->vals[m->len] = val; m->len++;
1331}
1332
1333/* The key-wise merge of two maps (the VM's `crdt_merge_field` on maps): `a`'s entries in its order, each key
1334 `b` also holds taking the merge of the two values — Ints summed (`value_rule` 0) or `b`'s value (1) — then
1335 `b`'s other keys in its order. A fresh map. */
1336logos_map *logos_map_merge(const logos_map *a, const logos_map *b, int key_kind, int value_rule) {
1337 logos_map *out = logos_map_dup(a);
1338 for (long long i = 0; i < b->len; i++) {
1339 long long j = 0;
1340 while (j < out->len && !logos_cell_eq(out->keys[j], b->keys[i], key_kind)) j++;
1341 if (j < out->len) {
1342 out->vals[j] = value_rule == 0
1343 ? (long long)((unsigned long long)out->vals[j] + (unsigned long long)b->vals[i])
1344 : b->vals[i];
1345 continue;
1346 }
1347 if (out->len == out->cap) {
1348 out->cap = out->cap ? out->cap * 2 : 4;
1349 out->keys = (long long *)lc_gc_realloc(out->keys, (size_t)out->cap * 8);
1350 out->vals = (long long *)lc_gc_realloc(out->vals, (size_t)out->cap * 8);
1351 }
1352 out->keys[out->len] = b->keys[i];
1353 out->vals[out->len] = b->vals[i];
1354 out->len++;
1355 }
1356 return out;
1357}
1358
1359/* `Merge`/`Converge` of cell `cell` (1-based) of two records of one type, for the fields the emitter does
1360 not merge inline: what the VM's `crdt_merge_field` makes of the target's value and the source's, by the
1361 emitter's rule — 3 a fresh counter holding the per-replica maximum, 4 the union of sets of element kind
1362 `a`, 5 the key-wise merge of maps keyed by kind `a` under value rule `b`. */
1363long long logos_merge_cell(const logos_seq *target, const logos_seq *source, long long cell, int how, int a, int b) {
1364 long long mine = target->data[cell - 1], theirs = source->data[cell - 1];
1365 switch (how) {
1366 case 3: {
1367 logos_crdt *merged = logos_crdt_clone((const logos_crdt *)mine);
1368 logos_crdt_merge(merged, (const logos_crdt *)theirs);
1369 return (long long)merged;
1370 }
1371 case 4: return (long long)logos_set_union((const logos_seq *)mine, (const logos_seq *)theirs, a);
1372 case 5: return (long long)logos_map_merge((const logos_map *)mine, (const logos_map *)theirs, a, b);
1373 default: abort();
1374 }
1375}
1376
1377/* `item key of m` — lookup; a missing key stops the program with the VM's sentence, naming the key. */
1378long long logos_map_get(const logos_map *a, long long key, int key_kind) {
1379 for (long long i = 0; i < a->len; i++) {
1380 if (logos_cell_eq(a->keys[i], key, key_kind)) return a->vals[i];
1381 }
1382 char *shown = logos_val_to_str(key, key_kind);
1383 size_t n = strlen(logos_missing_key_refusal) + strlen(shown) + 1;
1384 char *why = (char *)lc_gc_alloc_atomic(n);
1385 snprintf(why, n, logos_missing_key_refusal, shown);
1386 logos_fatal(why);
1387}
1388
1389/* `length of map` — the entry count. */
1390long long logos_map_len(const logos_map *a) { return a->len; }
1391
1392/* `map contains key` — key membership (never errors on a missing key), matching the VM's `Contains`. */
1393long long logos_map_has(const logos_map *a, long long key, int key_kind) {
1394 for (long long i = 0; i < a->len; i++) {
1395 if (logos_cell_eq(a->keys[i], key, key_kind)) return 1;
1396 }
1397 return 0;
1398}
1399
1400/* `Repeat for (k, v) in m` — the map's entries in order, each a `(key, value)` tuple. */
1401logos_seq *logos_map_pairs(const logos_map *m) {
1402 logos_seq *out = logos_seq_new();
1403 for (long long i = 0; i < m->len; i++) {
1404 logos_seq *pair = logos_seq_new();
1405 logos_seq_push_inplace(pair, m->keys[i]);
1406 logos_seq_push_inplace(pair, m->vals[i]);
1407 logos_seq_push_inplace(out, (long long)pair);
1408 }
1409 return out;
1410}
1411
1412/* `Show` of a map — `{k: v, ...}` (insertion order; keys/values formatted by kind, text unquoted). */
1413void logos_map_show(const logos_map *a, int key_kind, int val_kind) {
1414 putchar('{');
1415 for (long long i = 0; i < a->len; i++) {
1416 if (i) fputs(", ", stdout);
1417 char *k = logos_val_to_str(a->keys[i], key_kind);
1418 char *v = logos_val_to_str(a->vals[i], val_kind);
1419 printf("%s: %s", k, v);
1420 lc_gc_free(k); lc_gc_free(v);
1421 }
1422 fputs("}\n", stdout);
1423}
1424
1425/* ---- Deep copy and structural equality — the VM's `deep_clone` and `values_equal`, by the shape the emitter
1426 gives each kind. A shape is `[form, n, parts]`: forms 0 (Int), 7 (Text), 8 (Float) and 9 (Rational)
1427 are copied as values; 1 is a sequence with one element shape; 10 a set; 2 a map (key and value shapes);
1428 3 a declared enum with tagged constructor payload shapes; 6 a counter. */
1429/* Forms 11 (tuple) and 12 (record) use the sequence layout without collection identity.
1430 Forms 13/14 are Option/Result constructors. Forms 16..20 distinguish Bool, Date, Moment,
1431 Word32 and opaque identities from Int; 21 is a peer; 22 a cell of the exact extension; 23 `nothing`; 24 a span,
1432 its months and days packed into one word. */
1433typedef struct { long long form, n; const void *const *parts; } logos_shape;
1434typedef struct { long long tag, n; const logos_shape *const *parts; } logos_variant;
1435
1436/* Form 22 is a cell of the exact extension, which the extension's adapter copies and compares (two values of which
1437 one is a cell, each by its own shape), set when the shim embeds it. */
1438static long long (*logos_tower_copy_hook)(long long);
1439static int (*logos_tower_equal_hook)(long long, const logos_shape *, long long, const logos_shape *);
1440
1441long long logos_deep_copy(long long v, const logos_shape *s) {
1442 if (s->form == 22) return logos_tower_copy_hook(v);
1443 if (s->form == 0 || (s->form >= 7 && s->form <= 9) || s->form >= 16 || !v) return v;
1444 switch (s->form) {
1445 case 1:
1446 case 11:
1447 case 12:
1448 case 10: {
1449 const logos_seq *a = (const logos_seq *)v;
1450 logos_seq *c = logos_seq_new();
1451 for (long long i = 0; i < a->len; i++) {
1452 const logos_shape *cell = (const logos_shape *)s->parts[s->n == 1 ? 0 : i];
1453 logos_seq_push_inplace(c, logos_deep_copy(a->data[i], cell));
1454 }
1455 return (long long)c;
1456 }
1457 case 2: {
1458 const logos_map *a = (const logos_map *)v;
1459 logos_map *c = logos_map_dup(a);
1460 for (long long i = 0; i < a->len; i++) {
1461 c->keys[i] = logos_deep_copy(a->keys[i], (const logos_shape *)s->parts[0]);
1462 c->vals[i] = logos_deep_copy(a->vals[i], (const logos_shape *)s->parts[1]);
1463 }
1464 return (long long)c;
1465 }
1466 case 3:
1467 case 13:
1468 case 14: {
1469 const logos_seq *a = (const logos_seq *)v;
1470 const logos_variant *ctor = 0;
1471 for (long long i = 0; i < s->n; i++) {
1472 const logos_variant *r = (const logos_variant *)s->parts[i];
1473 if (r->tag == a->data[0]) ctor = r;
1474 }
1475 logos_seq *c = logos_seq_new();
1476 for (long long i = 0; i < a->len; i++) {
1477 long long cell = a->data[i];
1478 if (i > 0 && ctor && i - 1 < ctor->n) cell = logos_deep_copy(cell, ctor->parts[i - 1]);
1479 logos_seq_push_inplace(c, cell);
1480 }
1481 return (long long)c;
1482 }
1483 default: return (long long)logos_crdt_clone((const logos_crdt *)v);
1484 }
1485}
1486
1487/* Whether `a` and `b`, two values of shape `s`, are equal by structure. */
1488int logos_deep_eq(long long a, long long b, const logos_shape *s) {
1489 switch (s->form) {
1490 case 0: case 16: case 17: case 18: case 19: case 20: case 23: case 24: return a == b;
1491 case 22: return logos_tower_equal_hook(a, s, b, s);
1492 case 7: case 21: return strcmp((const char *)a, (const char *)b) == 0;
1493 case 8: { double x, y; memcpy(&x, &a, 8); memcpy(&y, &b, 8); return x == y; }
1494 case 9: return logos_ldec_cmp(a, 1, b, 1) == 0;
1495 }
1496 /* Only the VM's identity-bearing collections short-circuit on identity. A record, tuple or
1497 enum still compares its fields, so an aliased NaN payload is unequal to itself. */
1498 if (a == b && (s->form == 1 || s->form == 2 || s->form == 10)) return 1;
1499 if (!a || !b) return 0;
1500 switch (s->form) {
1501 case 1:
1502 case 11:
1503 case 12: {
1504 const logos_seq *x = (const logos_seq *)a, *y = (const logos_seq *)b;
1505 if (x->len != y->len) return 0;
1506 for (long long i = 0; i < x->len; i++) {
1507 if (!logos_deep_eq(x->data[i], y->data[i], (const logos_shape *)s->parts[s->n == 1 ? 0 : i])) return 0;
1508 }
1509 return 1;
1510 }
1511 case 10: {
1512 const logos_seq *x = (const logos_seq *)a, *y = (const logos_seq *)b;
1513 if (x->len != y->len) return 0;
1514 for (long long i = 0; i < x->len; i++) {
1515 long long j = 0;
1516 while (j < y->len && !logos_deep_eq(x->data[i], y->data[j], (const logos_shape *)s->parts[0])) j++;
1517 if (j == y->len) return 0;
1518 }
1519 return 1;
1520 }
1521 case 2: {
1522 const logos_map *x = (const logos_map *)a, *y = (const logos_map *)b;
1523 if (x->len != y->len) return 0;
1524 for (long long i = 0; i < x->len; i++) {
1525 long long j = 0;
1526 while (j < y->len && !logos_deep_eq(x->keys[i], y->keys[j], (const logos_shape *)s->parts[0])) j++;
1527 if (j == y->len || !logos_deep_eq(x->vals[i], y->vals[j], (const logos_shape *)s->parts[1])) return 0;
1528 }
1529 return 1;
1530 }
1531 case 3:
1532 case 13:
1533 case 14: {
1534 const logos_seq *x = (const logos_seq *)a, *y = (const logos_seq *)b;
1535 if (x->data[0] != y->data[0] || x->len != y->len) return 0;
1536 const logos_variant *ctor = 0;
1537 for (long long i = 0; i < s->n; i++) {
1538 const logos_variant *r = (const logos_variant *)s->parts[i];
1539 if (r->tag == x->data[0]) ctor = r;
1540 }
1541 for (long long i = 1; i < x->len; i++) {
1542 if (ctor && i - 1 < ctor->n ? !logos_deep_eq(x->data[i], y->data[i], ctor->parts[i - 1]) : x->data[i] != y->data[i]) return 0;
1543 }
1544 return 1;
1545 }
1546 default: return logos_crdt_sum((const logos_crdt *)a) == logos_crdt_sum((const logos_crdt *)b);
1547 }
1548}
1549
1550/* Cross-kind structural equality retains both layouts. Int and Float cells use exact numeric
1551 comparison, never an interpretation of one kind's bits as another kind's value. */
1552int logos_deep_eq_pair(long long a, long long b, const logos_shape *s, const logos_shape *t) {
1553 if (s == t) return logos_deep_eq(a, b, s);
1554 if (s->form == 22 || t->form == 22) return logos_tower_equal_hook(a, s, b, t);
1555 if (s->form == 6) return logos_deep_eq_pair(logos_crdt_sum((const logos_crdt *)a), b, &(logos_shape){0, 0, 0}, t);
1556 if (t->form == 6) return logos_deep_eq_pair(a, logos_crdt_sum((const logos_crdt *)b), s, &(logos_shape){0, 0, 0});
1557 if (s->form == 0 && t->form == 0) return a == b;
1558 if ((s->form == 0 && t->form == 8) || (s->form == 8 && t->form == 0)) {
1559 long long bits = s->form == 8 ? a : b;
1560 double f; memcpy(&f, &bits, 8);
1561 return logos_cmp_i64_f64_rel(s->form == 0 ? a : b, f, 0, 4);
1562 }
1563 if ((s->form == 9 && t->form == 0) || (s->form == 0 && t->form == 9)) {
1564 return logos_ldec_cmp(a, s->form == 9, b, t->form == 9) == 0;
1565 }
1566 if ((s->form == 9 && t->form == 8) || (s->form == 8 && t->form == 9)) {
1567 long long bits = s->form == 8 ? a : b;
1568 double f; memcpy(&f, &bits, 8);
1569 return logos_ldec_cmp_f64(s->form == 9 ? a : b, f) == 0;
1570 }
1571 if (s->form != t->form || !a || !b) return 0;
1572 switch (s->form) {
1573 case 1:
1574 case 11: {
1575 const logos_seq *x = (const logos_seq *)a, *y = (const logos_seq *)b;
1576 if (x->len != y->len) return 0;
1577 for (long long i = 0; i < x->len; i++) {
1578 const logos_shape *u = (const logos_shape *)s->parts[s->n == 1 ? 0 : i];
1579 const logos_shape *v = (const logos_shape *)t->parts[t->n == 1 ? 0 : i];
1580 if (!logos_deep_eq_pair(x->data[i], y->data[i], u, v)) return 0;
1581 }
1582 return 1;
1583 }
1584 case 2: {
1585 const logos_map *x = (const logos_map *)a, *y = (const logos_map *)b;
1586 if (x->len != y->len) return 0;
1587 for (long long i = 0; i < x->len; i++) {
1588 long long j = 0;
1589 while (j < y->len && !logos_deep_eq_pair(x->keys[i], y->keys[j],
1590 (const logos_shape *)s->parts[0], (const logos_shape *)t->parts[0])) j++;
1591 if (j == y->len || !logos_deep_eq_pair(x->vals[i], y->vals[j],
1592 (const logos_shape *)s->parts[1], (const logos_shape *)t->parts[1])) return 0;
1593 }
1594 return 1;
1595 }
1596 case 13:
1597 case 14: {
1598 const logos_seq *x = (const logos_seq *)a, *y = (const logos_seq *)b;
1599 if (x->len != y->len || x->data[0] != y->data[0]) return 0;
1600 const logos_variant *u = 0, *v = 0;
1601 for (long long i = 0; i < s->n; i++) {
1602 const logos_variant *r = (const logos_variant *)s->parts[i];
1603 if (r->tag == x->data[0]) u = r;
1604 }
1605 for (long long i = 0; i < t->n; i++) {
1606 const logos_variant *r = (const logos_variant *)t->parts[i];
1607 if (r->tag == y->data[0]) v = r;
1608 }
1609 if (!u || !v || u->n != v->n || x->len != u->n + 1) return 0;
1610 for (long long i = 0; i < u->n; i++) {
1611 if (!logos_deep_eq_pair(x->data[i+1], y->data[i+1], u->parts[i], v->parts[i])) return 0;
1612 }
1613 return 1;
1614 }
1615 /* Different nominal records/enums or different scalar kinds cannot compare equal. */
1616 default: return 0;
1617 }
1618}
1619
1620/* How a value of shape `s` displays, the VM's `to_display_string`: a scalar as `Show` prints it, a sequence as
1621 `[a, b]`, a set as `{a, b}`, a tuple as `(a, b)` and a map as `{k: v}`, each part by its own shape. */
1622typedef struct { char *text; size_t len, cap; } logos_text_buf;
1623
1624static void logos_buf_put(logos_text_buf *b, const char *s) {
1625 size_t n = strlen(s);
1626 if (b->len + n + 1 > b->cap) {
1627 size_t cap = b->cap * 2 > b->len + n + 1 ? b->cap * 2 : b->len + n + 1;
1628 b->text = (char *)lc_gc_realloc_atomic(b->text, cap);
1629 b->cap = cap;
1630 }
1631 memcpy(b->text + b->len, s, n + 1);
1632 b->len += n;
1633}
1634
1635static void logos_shaped_into(logos_text_buf *b, long long v, const logos_shape *s);
1636
1637static void logos_parts_into(logos_text_buf *b, const logos_seq *a, const logos_shape *s, const char *open, const char *close) {
1638 logos_buf_put(b, open);
1639 for (long long i = 0; i < a->len; i++) {
1640 if (i) logos_buf_put(b, ", ");
1641 logos_shaped_into(b, a->data[i], (const logos_shape *)s->parts[s->n == 1 ? 0 : i]);
1642 }
1643 logos_buf_put(b, close);
1644}
1645
1646static void logos_shaped_into(logos_text_buf *b, long long v, const logos_shape *s) {
1647 char *made = 0;
1648 switch (s->form) {
1649 case 0: case 19: made = logos_val_to_str(v, 0); break;
1650 case 16: made = logos_val_to_str(v, 2); break;
1651 case 7: logos_buf_put(b, (const char *)v); return;
1652 case 8: made = logos_val_to_str(v, 1); break;
1653 case 9: made = logos_val_to_str(v, 4); break;
1654 case 17: made = logos_val_to_str(v, 5); break;
1655 case 18: made = logos_val_to_str(v, 6); break;
1656 case 21: made = logos_val_to_str(v, 8); break;
1657 case 6: made = logos_val_to_str(logos_crdt_sum((const logos_crdt *)v), 0); break;
1658 case 22: made = logos_tower_text_hook(v); break;
1659 case 23: logos_buf_put(b, "nothing"); return;
1660 case 1: logos_parts_into(b, (const logos_seq *)v, s, "[", "]"); return;
1661 case 10: logos_parts_into(b, (const logos_seq *)v, s, "{", "}"); return;
1662 case 11: logos_parts_into(b, (const logos_seq *)v, s, "(", ")"); return;
1663 case 2: {
1664 const logos_map *m = (const logos_map *)v;
1665 logos_buf_put(b, "{");
1666 for (long long i = 0; i < m->len; i++) {
1667 if (i) logos_buf_put(b, ", ");
1668 logos_shaped_into(b, m->keys[i], (const logos_shape *)s->parts[0]);
1669 logos_buf_put(b, ": ");
1670 logos_shaped_into(b, m->vals[i], (const logos_shape *)s->parts[1]);
1671 }
1672 logos_buf_put(b, "}");
1673 return;
1674 }
1675 default: logos_fatalf("the RISC-V runtime has no display for a value of shape form %lld", s->form);
1676 }
1677 logos_buf_put(b, made);
1678 lc_gc_free(made);
1679}
1680
1681char *logos_shaped_str(long long v, const logos_shape *s) {
1682 logos_text_buf b = {0, 0, 0};
1683 logos_buf_put(&b, "");
1684 logos_shaped_into(&b, v, s);
1685 return b.text;
1686}
1687
1688void logos_show_shaped(long long v, const logos_shape *s) {
1689 char *text = logos_shaped_str(v, s);
1690 printf("%s\n", text);
1691 lc_gc_free(text);
1692}
1693
1694/* Snapshot iterator stack (for `Repeat for x in xs`). A snapshot is just the handle: the iterated
1695 * register's handle escapes into the iterator, which clears the register's owned flag, so a mutation of it
1696 * inside the loop copies first and the iterated list never changes under the iterator. Each task iterates
1697 * on a stack of its own, so a task that yields inside a loop resumes it where it left off (`logos_iters`). */
1698typedef struct { logos_seq *seq[256]; long long idx[256]; int top; } logos_iter_stack;
1699static logos_iter_stack *logos_iters(void);
1700void logos_iter_begin(logos_seq *s) {
1701 logos_iter_stack *it = logos_iters();
1702 it->seq[it->top] = s;
1703 it->idx[it->top] = 0;
1704 it->top++;
1705}
1706int logos_iter_has(void) {
1707 logos_iter_stack *it = logos_iters();
1708 int t = it->top - 1;
1709 return it->idx[t] < it->seq[t]->len;
1710}
1711long long logos_iter_take(void) {
1712 logos_iter_stack *it = logos_iters();
1713 int t = it->top - 1;
1714 return it->seq[t]->data[it->idx[t]++];
1715}
1716void logos_iter_end(void) { logos_iters()->top--; }
1717
1718/* ---- Deterministic cooperative concurrency: the C family's scheduler core under `ucontext` ---- */
1719"#,
1720 "#define LOGOS_FN static __attribute__((unused))\n",
1721 "#define LOGOS_VAR static\n",
1722 "#define LOGOS_API\n",
1723 "#define logos_alloc(n) lc_gc_calloc(1, (n))\n",
1724 "#define logos_release(p) lc_gc_free(p)\n",
1725 "#define LOGOS_STACK_ENTER(lo, hi) lc_gc_stack_enter((lo), (hi))\n",
1726 "#define LOGOS_STACK_LEAVE() lc_gc_stack_leave()\n",
1727 "#define LOGOS_TASK_TIER ucontext_t ctx; void *stack; void *fn; long long nargs; long long *args; logos_iter_stack *iters;\n",
1728 "static long long logos_riscv_call(void *fn, long long n, const long long *a);\n",
1729 "#define LOGOS_TASK_CALL(t) logos_riscv_call((t)->fn, (t)->nargs, (t)->args)\n",
1730 include_str!("../codegen_c/sched_core.c"),
1731 include_str!("../codegen_c/host_seed.c"),
1732 include_str!("../codegen_c/opt.c"),
1733 include_str!("../codegen_c/ucontext_tier.c"),
1734 r#"
1735/* A task body is an emitted `logos_fn_N`, which takes its arguments in a0…a7, as every call does. */
1736static long long logos_riscv_call(void *fn, long long n, const long long *a) {
1737 switch (n) {
1738 case 0: return ((long long (*)(void))fn)();
1739 case 1: return ((long long (*)(long long))fn)(a[0]);
1740 case 2: return ((long long (*)(long long, long long))fn)(a[0], a[1]);
1741 case 3: return ((long long (*)(long long, long long, long long))fn)(a[0], a[1], a[2]);
1742 case 4: return ((long long (*)(long long, long long, long long, long long))fn)(a[0], a[1], a[2], a[3]);
1743 case 5: return ((long long (*)(long long, long long, long long, long long, long long))fn)(a[0], a[1], a[2], a[3], a[4]);
1744 case 6: return ((long long (*)(long long, long long, long long, long long, long long, long long))fn)(a[0], a[1], a[2], a[3], a[4], a[5]);
1745 case 7: return ((long long (*)(long long, long long, long long, long long, long long, long long, long long))fn)(a[0], a[1], a[2], a[3], a[4], a[5], a[6]);
1746 default: return ((long long (*)(long long, long long, long long, long long, long long, long long, long long, long long))fn)(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]);
1747 }
1748}
1749
1750/* The running task's iterator stack; the program's own when no task runs. */
1751static logos_iter_stack logos_iters_outside;
1752static logos_iter_stack *logos_iters(void) {
1753 if (!logos_current) return &logos_iters_outside;
1754 if (!logos_current->iters) logos_current->iters = (logos_iter_stack *)lc_gc_calloc(1, sizeof(logos_iter_stack));
1755 return logos_current->iters;
1756}
1757
1758extern void *logos_fn_dispatch[];
1759
1760/* `Launch a task to f …` from the emitted code: the body by its index in `logos_fn_dispatch`, the
1761 arguments as the launching frame's contiguous slots. */
1762long long logos_spawn_fn(long long func_idx, long long nargs, const long long *args) {
1763 return logos_spawn(logos_fn_dispatch[func_idx], nargs, args);
1764}
1765
1766/* The box a select's receive arm resolves into: the emitted code reads it back at the wait. */
1767logos_opt *logos_opt_nothing(void) { return (logos_opt *)lc_gc_calloc(1, sizeof(logos_opt)); }
1768
1769/* ---- Networking — the VM's offline `NetInbox`: one node, no relay ----
1770 `Listen` names this node's inbox. `Send`/`Stream` put the message, stamped with the sender's inbox (empty
1771 before any `Listen`), on the loopback outbox under the topic it is addressed to. `Await` needs an inbox: it
1772 moves each outboxed message into the received queue when it is addressed to the inbox, dropping it
1773 otherwise, then takes the first received message (or batch) from the peer it names. A lone node sends
1774 nothing while it waits, so when that peer's message is not there the program waits for ever, as the VM's
1775 `Await` polls for ever. A topic is canonical: `logos_net_topic`, the relay's `canonical_topic`. */
1776
1777/* Whether `s` begins with `prefix`, ASCII case folded (`to_ascii_lowercase().starts_with`). */
1778static int logos_ascii_prefix(const char *s, const char *prefix) {
1779 for (; *prefix; s++, prefix++) {
1780 char c = *s >= 'A' && *s <= 'Z' ? (char)(*s - 'A' + 'a') : *s;
1781 if (c != *prefix) return 0;
1782 }
1783 return 1;
1784}
1785
1786/* Whether the `n` bytes at `s` parse as a `u16` (`str::parse::<u16>`: an optional `+`, then digits). */
1787static int logos_is_u16(const char *s, size_t n) {
1788 if (n && s[0] == '+') { s++; n--; }
1789 if (!n) return 0;
1790 unsigned long v = 0;
1791 for (size_t i = 0; i < n; i++) {
1792 if (s[i] < '0' || s[i] > '9') return 0;
1793 v = v * 10 + (unsigned long)(s[i] - '0');
1794 if (v > 65535) return 0;
1795 }
1796 return 1;
1797}
1798
1799/* `multiaddr_to_ws_url` — the `ws://` URL a libp2p multiaddr (or a ws/wss URL) names, or NULL when it names
1800 none. */
1801static char *logos_multiaddr_url(const char *addr) {
1802 char *t = logos_trim(addr);
1803 if (!*t) return NULL;
1804 if (logos_ascii_prefix(t, "ws://") || logos_ascii_prefix(t, "wss://")) return t;
1805 if (t[0] != '/') return NULL;
1806 long long ns = 1;
1807 for (const char *p = t + 1; *p; p++) ns += *p == '/';
1808 const char **seg = (const char **)lc_gc_alloc((size_t)ns * sizeof *seg);
1809 size_t *len = (size_t *)lc_gc_alloc_atomic((size_t)ns * sizeof *len);
1810 const char *p = t + 1;
1811 for (long long k = 0; k < ns; k++) {
1812 const char *q = strchr(p, '/');
1813 seg[k] = p;
1814 len[k] = q ? (size_t)(q - p) : strlen(p);
1815 p = q ? q + 1 : p + len[k];
1816 }
1817#define LOGOS_SEG(k, name) (len[k] == strlen(name) && strncmp(seg[k], name, len[k]) == 0)
1818 const char *host = NULL, *port = NULL, *scheme = "ws";
1819 size_t host_len = 0, port_len = 0;
1820 int v6 = 0;
1821 long long i = 0;
1822 while (i < ns) {
1823 if (LOGOS_SEG(i, "ip4") || LOGOS_SEG(i, "dns") || LOGOS_SEG(i, "dns4") || LOGOS_SEG(i, "dns6")
1824 || LOGOS_SEG(i, "dnsaddr") || LOGOS_SEG(i, "ip6")) {
1825 if (i + 1 >= ns || len[i + 1] == 0) return NULL;
1826 v6 = LOGOS_SEG(i, "ip6");
1827 host = seg[i + 1];
1828 host_len = len[i + 1];
1829 i += 2;
1830 } else if (LOGOS_SEG(i, "tcp")) {
1831 if (i + 1 >= ns || len[i + 1] == 0 || !logos_is_u16(seg[i + 1], len[i + 1])) return NULL;
1832 port = seg[i + 1];
1833 port_len = len[i + 1];
1834 i += 2;
1835 } else if (LOGOS_SEG(i, "ws")) {
1836 scheme = "ws";
1837 i += 1;
1838 } else if (LOGOS_SEG(i, "wss") || LOGOS_SEG(i, "tls")) {
1839 scheme = "wss";
1840 i += 1;
1841 } else if (LOGOS_SEG(i, "p2p")) {
1842 i += 2;
1843 } else if (LOGOS_SEG(i, "p2p-circuit")) {
1844 i += 1;
1845 } else {
1846 return NULL;
1847 }
1848 }
1849#undef LOGOS_SEG
1850 if (!host || !port) return NULL;
1851 size_t n = strlen(scheme) + host_len + port_len + 8;
1852 char *url = (char *)lc_gc_alloc_atomic(n);
1853 snprintf(url, n, v6 ? "%s://[%.*s]:%.*s" : "%s://%.*s:%.*s", scheme, (int)host_len, host, (int)port_len, port);
1854 return url;
1855}
1856
1857/* `canonical_topic` — the relay topic an address names: its `ws://` URL, else the address trimmed. */
1858char *logos_net_topic(const char *addr) {
1859 char *url = logos_multiaddr_url(addr);
1860 return url ? url : logos_trim(addr);
1861}
1862
1863typedef struct { char *topic; char *from; long long value; int batch; } logos_net_msg;
1864typedef struct { logos_net_msg *at; long long len, cap; } logos_net_queue;
1865static logos_net_queue logos_net_outbox, logos_net_received;
1866static char *logos_net_inbox = NULL;
1867
1868static void logos_net_put(logos_net_queue *q, logos_net_msg m) {
1869 if (q->len == q->cap) {
1870 q->cap = q->cap ? q->cap * 2 : 8;
1871 q->at = (logos_net_msg *)lc_gc_realloc(q->at, (size_t)q->cap * sizeof *q->at);
1872 }
1873 q->at[q->len++] = m;
1874}
1875
1876/* `Listen on <topic>` — `topic` (canonical) becomes this node's inbox. */
1877void logos_net_listen(char *topic) { logos_net_inbox = topic; }
1878
1879/* `Send`/`Stream` to `topic` (canonical): `how` is 0 for a message, 1 for a batch that is the sequence
1880 `value`, 2 for a batch of the one value `value`. */
1881void logos_net_send(const char *topic, long long value, int how) {
1882 if (how == 2) {
1883 logos_seq *one = logos_seq_new();
1884 logos_seq_push_inplace(one, value);
1885 value = (long long)one;
1886 }
1887 logos_net_msg m = { lc_gc_strdup(topic), lc_gc_strdup(logos_net_inbox ? logos_net_inbox : ""), value, how != 0 };
1888 logos_net_put(&logos_net_outbox, m);
1889}
1890
1891/* `Await [stream] from <want>` (canonical) — the first message (`batch` 0) or batch (`batch` 1) `want` sent to
1892 this node's inbox. Without an inbox the program stops with `logos_await_without_listen_refusal`. */
1893long long logos_net_await(const char *want, int batch) {
1894 if (!logos_net_inbox) logos_fatal(logos_await_without_listen_refusal);
1895 for (long long i = 0; i < logos_net_outbox.len; i++) {
1896 if (strcmp(logos_net_outbox.at[i].topic, logos_net_inbox) == 0) logos_net_put(&logos_net_received, logos_net_outbox.at[i]);
1897 }
1898 logos_net_outbox.len = 0;
1899 for (long long i = 0; i < logos_net_received.len; i++) {
1900 logos_net_msg m = logos_net_received.at[i];
1901 if (m.batch == batch && strcmp(m.from, want) == 0) {
1902 memmove(&logos_net_received.at[i], &logos_net_received.at[i + 1], (size_t)(logos_net_received.len - i - 1) * sizeof m);
1903 logos_net_received.len--;
1904 return m.value;
1905 }
1906 }
1907 fflush(stdout);
1908 for (;;) pause();
1909}
1910"#,
1911);