#define _GNU_SOURCE
#ifdef CODE_WASM
#include "wasm_shim.h"
#else
#include <dlfcn.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#endif
#include "code_abi.h"
_Static_assert(sizeof(CodeValue) <= CODE_VALUE_SLOT_SIZE,
"CodeValue outgrew codegen.rs's VALUE_SIZE stride");
static CodeValue *slot_at(void *base, long long index) {
return (CodeValue *)((char *)base + index * CODE_VALUE_SLOT_SIZE);
}
_Noreturn void code_runtime_error(const char *message) {
#ifdef CODE_WASM
code_host_error(message, (unsigned int)strlen(message));
__builtin_trap();
#else
fprintf(stderr, "error: %s\n", message);
exit(1);
#endif
}
int code_failed = 0;
static char failure_message[1024];
const char *code_location = NULL;
static void fail(const char *message) {
if (!code_failed) {
snprintf(failure_message, sizeof failure_message, "%s", message);
code_failed = 1;
}
}
_Noreturn void code_abort_failure(void) {
const char *message = code_failed ? failure_message : "unknown runtime error";
if (code_location) {
size_t n = strlen(message) + 1 + strlen(code_location) + 1;
char *located = malloc(n);
if (located) {
snprintf(located, n, "%s\n%s", message, code_location);
code_runtime_error(located);
}
}
code_runtime_error(message);
}
void code_take_failure(CodeValue *out) {
code_make_exception(out, "core",
code_failed ? failure_message : "unknown runtime error", NULL);
code_failed = 0;
}
typedef struct {
long long rc;
long long padding;
} CodeHeader;
static long long live_blocks = 0;
#define code_blocks_add(n) (void)__atomic_add_fetch(&live_blocks, (n), __ATOMIC_RELAXED)
#define code_blocks_read() __atomic_load_n(&live_blocks, __ATOMIC_RELAXED)
static void *heap_alloc(size_t bytes) {
CodeHeader *h = malloc(sizeof(CodeHeader) + bytes);
if (!h) {
code_runtime_error("out of memory");
}
h->rc = 1;
code_blocks_add(1);
return (char *)h + sizeof(CodeHeader);
}
static CodeHeader *header_of(const void *payload) {
return (CodeHeader *)((char *)payload - sizeof(CodeHeader));
}
static void *heap_block(const CodeValue *v) {
switch (v->tag) {
case CODE_STR:
return (void *)v->str;
case CODE_ARRAY:
return v->items;
case CODE_OBJECT:
return (void *)v->keys;
default:
return NULL;
}
}
void code_retain(const CodeValue *v) {
if (v->heap) {
header_of(heap_block(v))->rc++;
}
}
#ifdef CODE_WASM
#define CODE_THREAD_LOCAL
#else
#define CODE_THREAD_LOCAL __thread
#endif
static void *grow(void *buf, size_t *cap, size_t needed, size_t item_size) {
if (*cap >= needed) {
return buf;
}
size_t next = *cap ? *cap * 2 : 64;
while (next < needed) {
next *= 2;
}
void *bigger = realloc(buf, next * item_size);
if (!bigger) {
code_runtime_error("out of memory");
}
*cap = next;
return bigger;
}
static CODE_THREAD_LOCAL CodeValue *dead = NULL;
static CODE_THREAD_LOCAL size_t dead_cap = 0;
void code_release(CodeValue *v) {
if (!v->heap) {
return;
}
if (--header_of(heap_block(v))->rc != 0) {
return;
}
size_t len = 0;
dead = grow(dead, &dead_cap, len + 1, sizeof(CodeValue));
dead[len++] = *v;
while (len > 0) {
CodeValue current = dead[--len];
if (current.tag == CODE_ARRAY || current.tag == CODE_OBJECT) {
for (long long i = 0; i < current.len; i++) {
const CodeValue *child = slot_at(current.items, i);
if (child->heap && --header_of(heap_block(child))->rc == 0) {
dead = grow(dead, &dead_cap, len + 1, sizeof(CodeValue));
dead[len++] = *child;
}
}
}
free(header_of(heap_block(¤t)));
code_blocks_add(-1);
}
}
void code_clear(CodeValue *v) {
code_release(v);
memset(v, 0, sizeof *v);
}
void code_check_leaks(void) {
if (!getenv("CODE_CHECK_LEAKS")) {
return;
}
long long leaked = code_blocks_read();
if (leaked != 0) {
char msg[96];
snprintf(msg, sizeof msg, "%lld heap block(s) leaked", leaked);
code_runtime_error(msg);
}
}
void code_number(CodeValue *out, double n) {
code_release(out);
out->tag = CODE_NUMBER;
out->heap = 0;
out->number = n;
}
void code_str(CodeValue *out, const char *s) {
code_release(out);
out->tag = CODE_STR;
out->heap = 0;
out->str = s;
}
void code_bool(CodeValue *out, int b) {
code_release(out);
out->tag = CODE_BOOL;
out->heap = 0;
out->boolean = b;
}
void code_null(CodeValue *out) {
code_release(out);
out->tag = CODE_NULL;
out->heap = 0;
}
void code_array(CodeValue *out, void *items, long long len) {
void *buf = NULL;
if (len > 0) {
buf = heap_alloc((size_t)len * CODE_VALUE_SLOT_SIZE);
for (long long i = 0; i < len; i++) {
const CodeValue *src = slot_at(items, i);
code_retain(src);
*slot_at(buf, i) = *src;
}
}
code_release(out);
out->tag = CODE_ARRAY;
out->heap = len > 0;
out->items = buf;
out->len = len;
}
static const char *copy_key(char **chars, const char *key) {
size_t n = (key ? strlen(key) : 0) + 1;
if (key) {
memcpy(*chars, key, n);
} else {
(*chars)[0] = '\0';
}
const char *placed = *chars;
*chars += n;
return placed;
}
void code_object(CodeValue *out, const char **keys, void *values, long long len) {
const char **key_buf = NULL;
void *value_buf = NULL;
if (len > 0) {
size_t keys_bytes = (size_t)len * sizeof(const char *);
size_t slots_bytes = (size_t)len * CODE_VALUE_SLOT_SIZE;
size_t chars_bytes = 0;
for (long long i = 0; i < len; i++) {
chars_bytes += (keys[i] ? strlen(keys[i]) : 0) + 1;
}
key_buf = heap_alloc(keys_bytes + slots_bytes + chars_bytes);
value_buf = (char *)key_buf + keys_bytes;
char *chars = (char *)value_buf + slots_bytes;
for (long long i = 0; i < len; i++) {
key_buf[i] = copy_key(&chars, keys[i]);
const CodeValue *src = slot_at(values, i);
code_retain(src);
*slot_at(value_buf, i) = *src;
}
}
code_release(out);
out->tag = CODE_OBJECT;
out->heap = len > 0;
out->keys = key_buf;
out->items = value_buf;
out->len = len;
}
const char *code_str_text(const CodeValue *v) {
return v->tag == CODE_STR && v->str ? v->str : "";
}
void code_copy(CodeValue *out, const CodeValue *src) {
code_retain(src);
code_release(out);
*out = *src;
}
static const char *article_for(const CodeValue *v) {
return (v->tag == CODE_ARRAY || v->tag == CODE_OBJECT) ? "an" : "a";
}
static const char *type_name(const CodeValue *v) {
switch (v->tag) {
case CODE_NUMBER: return "number";
case CODE_STR: return "string";
case CODE_BOOL: return "boolean";
case CODE_NULL: return "null";
case CODE_ARRAY: return "array";
case CODE_OBJECT: return "object";
}
return "value";
}
static void operand_message(char *buf, size_t n, const char *requirement, const CodeValue *v) {
snprintf(buf, n, "%s, found %s %s", requirement, article_for(v), type_name(v));
}
static void fail_operand(const char *requirement, const CodeValue *v) {
char msg[192];
operand_message(msg, sizeof msg, requirement, v);
fail(msg);
}
static void fail_binary(const char *op, const CodeValue *a, const CodeValue *b) {
char msg[192];
snprintf(msg, sizeof msg, "cannot apply '%s' to %s %s and %s %s", op, article_for(a),
type_name(a), article_for(b), type_name(b));
fail(msg);
}
void code_field(CodeValue *out, const CodeValue *obj, const char *field) {
if (obj->tag != CODE_OBJECT) {
char msg[128];
snprintf(msg, sizeof msg,
"cannot read field '%s' of %s %s — '.' requires an object", field,
article_for(obj), type_name(obj));
fail(msg);
return;
}
for (long long i = 0; i < obj->len; i++) {
if (strcmp(obj->keys[i], field) == 0) {
code_copy(out, slot_at(obj->items, i));
return;
}
}
code_null(out);
}
static size_t char_offset(const char *s, long long n) {
size_t i = 0;
long long seen = 0;
while (s[i] && seen < n) {
i++;
while ((s[i] & 0xC0) == 0x80) {
i++;
}
seen++;
}
return i;
}
static void str_owned_n(CodeValue *out, const char *s, size_t n) {
char *buf = heap_alloc(n + 1);
memcpy(buf, s, n);
buf[n] = '\0';
code_release(out);
out->tag = CODE_STR;
out->heap = 1;
out->str = buf;
}
void code_length_of(CodeValue *out, const CodeValue *value) {
if (value->tag == CODE_ARRAY || value->tag == CODE_OBJECT) {
code_number(out, (double)value->len);
return;
}
if (value->tag == CODE_STR) {
long long chars = 0;
for (const char *p = value->str; *p; p++) {
if (((unsigned char)*p & 0xC0) != 0x80) {
chars++;
}
}
code_number(out, (double)chars);
return;
}
char msg[160];
snprintf(msg, sizeof msg,
"cannot take the length of %s %s — 'length' needs an array, an object or a string",
article_for(value), type_name(value));
fail(msg);
}
void code_slice(CodeValue *out, const CodeValue *value, const CodeValue *from,
const CodeValue *to) {
if (from->tag != CODE_NUMBER) {
char msg[128];
snprintf(msg, sizeof msg, "a range's start must be a number, found %s %s",
article_for(from), type_name(from));
fail(msg);
return;
}
if (to->tag != CODE_NUMBER) {
char msg[128];
snprintf(msg, sizeof msg, "a range's end must be a number, found %s %s",
article_for(to), type_name(to));
fail(msg);
return;
}
if (value->tag == CODE_STR) {
double chars = 0;
for (const char *p = value->str; *p; p++) {
if (((unsigned char)*p & 0xC0) != 0x80) {
chars++;
}
}
double slo = from->number < 0 ? 0 : (from->number > chars ? chars : from->number);
double shi = to->number < 0 ? 0 : (to->number > chars ? chars : to->number);
if (slo >= shi) {
code_str(out, "");
return;
}
size_t begin = char_offset(value->str, (long long)slo);
size_t end = char_offset(value->str, (long long)shi);
str_owned_n(out, value->str + begin, end - begin);
return;
}
if (value->tag != CODE_ARRAY) {
char msg[160];
snprintf(msg, sizeof msg,
"cannot take a range of %s %s — '[from, to]' requires an array or a string",
article_for(value), type_name(value));
fail(msg);
return;
}
double len = (double)value->len;
double lo = from->number < 0 ? 0 : (from->number > len ? len : from->number);
double hi = to->number < 0 ? 0 : (to->number > len ? len : to->number);
long long start = (long long)lo;
long long stop = (long long)hi;
if (start >= stop) {
code_array(out, NULL, 0);
return;
}
code_array(out, (char *)value->items + (size_t)start * CODE_VALUE_SLOT_SIZE, stop - start);
}
void code_index(CodeValue *out, const CodeValue *arr, const CodeValue *index) {
if (arr->tag == CODE_ARRAY) {
if (index->tag == CODE_NUMBER) {
double n = index->number;
long long i = (long long)n;
if ((double)i == n && i >= 0 && i < arr->len) {
code_copy(out, slot_at(arr->items, i));
return;
}
}
code_null(out);
return;
}
if (arr->tag == CODE_OBJECT) {
if (index->tag == CODE_STR) {
for (long long i = 0; i < arr->len; i++) {
if (strcmp(arr->keys[i], index->str) == 0) {
code_copy(out, slot_at(arr->items, i));
return;
}
}
}
code_null(out);
return;
}
if (arr->tag == CODE_STR) {
if (index->tag == CODE_NUMBER) {
double n = index->number;
long long i = (long long)n;
if ((double)i == n && i >= 0) {
size_t begin = char_offset(arr->str, i);
if (arr->str[begin]) {
size_t end = char_offset(arr->str, i + 1);
str_owned_n(out, arr->str + begin, end - begin);
return;
}
}
}
code_null(out);
return;
}
char msg[112];
snprintf(msg, sizeof msg,
"cannot index %s %s — '[]' requires an array, an object or a string",
article_for(arr), type_name(arr));
fail(msg);
}
static const CodeValue *find_field(const CodeValue *obj, const char *key) {
for (long long i = 0; i < obj->len; i++) {
if (strcmp(obj->keys[i], key) == 0) {
return slot_at(obj->items, i);
}
}
return NULL;
}
static void code_make_result(CodeValue *out, const char *class_name, const CodeValue *value) {
const char *keys[2] = {"_class", "value"};
_Alignas(8) char slots[2 * CODE_VALUE_SLOT_SIZE] = {0};
code_str(slot_at(slots, 0), class_name);
code_copy(slot_at(slots, 1), value);
code_object(out, keys, slots, 2);
code_release(slot_at(slots, 0));
code_release(slot_at(slots, 1));
}
void code_make_exception(CodeValue *out, const char *source, const char *message,
const CodeValue *inner) {
const char *keys[4] = {"_class", "source", "message", "innerException"};
_Alignas(8) char slots[4 * CODE_VALUE_SLOT_SIZE] = {0};
code_str(slot_at(slots, 0), "Exception");
code_str_owned(slot_at(slots, 1), source);
code_str_owned(slot_at(slots, 2), message);
if (inner) {
code_copy(slot_at(slots, 3), inner);
} else {
code_null(slot_at(slots, 3));
}
code_object(out, keys, slots, 4);
for (int i = 0; i < 4; i++) {
code_release(slot_at(slots, i));
}
}
static int code_linked = 0;
void code_set_linked(void) { code_linked = 1; }
void code_core_dispatch(CodeValue *out, const CodeValue *particle) {
if (particle->tag != CODE_OBJECT) {
code_null(out);
return;
}
const CodeValue *class_val = find_field(particle, "_class");
if (!class_val || class_val->tag != CODE_STR) {
code_null(out);
return;
}
if (strcmp(class_val->str, "Linked") == 0) {
CodeValue answer = {0};
code_bool(&answer, code_linked != 0);
code_make_result(out, "LinkedResult", &answer);
code_release(&answer);
return;
}
if (strcmp(class_val->str, "Timestamp") == 0) {
CodeValue ts = {0};
#ifdef CODE_WASM
code_number(&ts, code_host_now());
#else
code_number(&ts, (double)time(NULL));
#endif
code_make_result(out, "TimestampResult", &ts);
return;
}
if (strcmp(class_val->str, "TimezoneOffset") == 0) {
CodeValue off = {0};
#ifdef CODE_WASM
code_number(&off, code_host_tz_offset());
#else
time_t now = time(NULL);
struct tm local;
if (localtime_r(&now, &local) == NULL) {
code_number(&off, 0.0);
} else {
code_number(&off, (double)local.tm_gmtoff / 60.0);
}
#endif
code_make_result(out, "TimezoneOffsetResult", &off);
code_release(&off);
return;
}
if (strcmp(class_val->str, "Length") == 0) {
static const CodeValue absent = {.tag = CODE_NULL};
const CodeValue *value = find_field(particle, "value");
if (!value) {
value = &absent;
}
CodeValue count = {0};
if (value->tag == CODE_ARRAY) {
code_number(&count, (double)value->len);
code_make_result(out, "LengthResult", &count);
return;
}
if (value->tag == CODE_STR) {
long long chars = 0;
for (const char *p = value->str; *p; p++) {
if (((unsigned char)*p & 0xC0) != 0x80) {
chars++;
}
}
code_number(&count, (double)chars);
code_make_result(out, "LengthResult", &count);
return;
}
char msg[192];
operand_message(msg, sizeof msg, "Length requires an array or string 'value'", value);
code_make_exception(out, "core", msg, NULL);
return;
}
code_null(out);
}
#ifdef CODE_WASM
typedef int CodeMutex;
#define code_mutex_init(m) ((void)(m))
#define code_mutex_lock(m) ((void)(m))
#define code_mutex_unlock(m) ((void)(m))
#else
typedef pthread_mutex_t CodeMutex;
#define code_mutex_init(m) pthread_mutex_init((m), NULL)
#define code_mutex_lock(m) pthread_mutex_lock(m)
#define code_mutex_unlock(m) pthread_mutex_unlock(m)
#endif
typedef struct {
void (*dispatch)(CodeValue *out, const CodeValue *particle);
void (*release)(CodeValue *v);
CodeInboundReplyFn reply;
const CodeVarList *(*vars)(void);
CodeValue inbound[CODE_INBOUND_CAPACITY];
int inbound_head;
int inbound_count;
CodeMutex lock;
int (*serving)(void);
int has_inbound;
int image_fd;
void *lib;
int from_host;
CodeHostModule host;
void (*module_release)(void);
void (*module_drain)(void);
int closed;
} NativeHandle;
void code_static_module_check(uint32_t version, const char *what) {
if (version != CODE_ABI_VERSION) {
char msg[256];
snprintf(msg, sizeof msg, "native module '%s' has ABI version %u (expected %u)", what,
(unsigned)version, (unsigned)CODE_ABI_VERSION);
code_runtime_error(msg);
}
}
void code_emit_inbound(void *queue, const CodeValue *value);
static const CodeHostVtable *code_host = NULL;
static void *code_host_ctx = NULL;
void code_module_set_host(const CodeHostVtable *host, void *host_ctx) {
code_host = host;
code_host_ctx = host_ctx;
}
static NativeHandle *host_native(const CodeHostModule *supplied, char *err, size_t errlen) {
if (!supplied->dispatch || !supplied->release) {
snprintf(err, errlen, "the host offered a module with no dispatch");
return NULL;
}
NativeHandle *nh = malloc(sizeof(NativeHandle));
if (!nh) {
code_runtime_error("out of memory");
}
memset(nh, 0, sizeof *nh);
nh->from_host = 1;
nh->host = *supplied;
nh->has_inbound = 0;
code_mutex_init(&nh->lock);
return nh;
}
static int module_image(const char *path) {
#if defined(__linux__) && !defined(CODE_WASM)
int src = open(path, O_RDONLY | O_CLOEXEC);
if (src < 0) {
return -1;
}
int image = memfd_create("code-module", MFD_CLOEXEC);
if (image < 0) {
close(src);
return -1;
}
char buf[65536];
for (;;) {
ssize_t n = read(src, buf, sizeof buf);
if (n == 0) {
break;
}
if (n < 0 || write(image, buf, (size_t)n) != n) {
close(src);
close(image);
return -1;
}
}
close(src);
return image;
#else
(void)path;
return -1;
#endif
}
static NativeHandle *open_native(const char *path, char *err, size_t errlen) {
if (code_host && code_host->resolve) {
CodeHostModule supplied = {0};
if (code_host->resolve(code_host_ctx, path, &supplied)) {
return host_native(&supplied, err, errlen);
}
}
#ifdef CODE_WASM
(void)path;
snprintf(err, errlen, "native modules are not available in a wasm build");
return NULL;
#else
int image = module_image(path);
void *handle = NULL;
if (image >= 0) {
char proc[64];
snprintf(proc, sizeof proc, "/proc/self/fd/%d", image);
handle = dlopen(proc, RTLD_NOW);
if (!handle) {
close(image);
image = -1;
}
}
if (!handle) {
handle = dlopen(path, RTLD_NOW);
}
if (!handle) {
snprintf(err, errlen, "cannot load native module '%s': %s", path, dlerror());
return NULL;
}
uint32_t (*version_fn)(void) = (uint32_t (*)(void))dlsym(handle, "code_module_abi_version");
if (!version_fn) {
snprintf(err, errlen, "native module '%s' missing 'code_module_abi_version'", path);
dlclose(handle);
return NULL;
}
uint32_t version = version_fn();
if (version != CODE_ABI_VERSION) {
snprintf(err, errlen, "native module '%s' has ABI version %u (expected %u)", path,
(unsigned)version, (unsigned)CODE_ABI_VERSION);
dlclose(handle);
return NULL;
}
NativeHandle *nh = malloc(sizeof(NativeHandle));
if (!nh) {
code_runtime_error("out of memory");
}
memset(nh, 0, sizeof *nh);
nh->image_fd = image;
nh->lib = handle;
nh->dispatch = (void (*)(CodeValue *, const CodeValue *))dlsym(handle, "code_module_dispatch");
nh->release = (void (*)(CodeValue *))dlsym(handle, "code_release");
if (!nh->dispatch || !nh->release) {
snprintf(err, errlen, "native module '%s' missing 'code_module_dispatch' or 'code_release'",
path);
free(nh);
dlclose(handle);
if (image >= 0) {
close(image);
}
return NULL;
}
nh->vars = (const CodeVarList *(*)(void))dlsym(handle, "code_module_vars");
nh->reply = (CodeInboundReplyFn)dlsym(handle, "code_module_inbound_reply");
nh->serving = (int (*)(void))dlsym(handle, "code_module_serving");
nh->module_release = (void (*)(void))dlsym(handle, "code_module_release");
nh->module_drain = (void (*)(void))dlsym(handle, "code_module_drain");
memset(nh->inbound, 0, sizeof nh->inbound);
nh->inbound_head = 0;
nh->inbound_count = 0;
nh->closed = 0;
code_mutex_init(&nh->lock);
void (*set_inbound)(void *, CodeEmitFn) =
(void (*)(void *, CodeEmitFn))dlsym(handle, "code_module_set_inbound");
nh->has_inbound = set_inbound != NULL;
if (set_inbound) {
set_inbound(nh, code_emit_inbound);
}
return nh;
#endif
}
void *code_native_open(const char *path) {
char err[256];
NativeHandle *nh = open_native(path, err, sizeof err);
if (!nh) {
code_runtime_error(err);
}
return nh;
}
void *code_static_open(void) {
NativeHandle *nh = malloc(sizeof(NativeHandle));
if (!nh) {
code_runtime_error("out of memory");
}
memset(nh, 0, sizeof *nh);
nh->image_fd = -1;
nh->dispatch = NULL;
nh->release = NULL;
nh->vars = NULL;
nh->reply = NULL;
nh->serving = NULL;
nh->lib = NULL;
nh->module_release = NULL;
nh->from_host = 0;
memset(&nh->host, 0, sizeof nh->host);
memset(nh->inbound, 0, sizeof nh->inbound);
nh->inbound_head = 0;
nh->inbound_count = 0;
nh->closed = 0;
nh->has_inbound = 1;
code_mutex_init(&nh->lock);
return nh;
}
void code_str_owned(CodeValue *out, const char *s) {
size_t n = strlen(s);
char *buf = heap_alloc(n + 1);
memcpy(buf, s, n + 1);
code_release(out);
out->tag = CODE_STR;
out->heap = 1;
out->str = buf;
}
static void code_native_copy_in(CodeValue *out, const CodeValue *from) {
switch (from->tag) {
case CODE_NUMBER:
code_number(out, from->number);
return;
case CODE_STR:
code_str_owned(out, from->str);
return;
case CODE_BOOL:
code_bool(out, from->boolean);
return;
case CODE_NULL:
code_null(out);
return;
case CODE_ARRAY: {
void *slots = from->len > 0 ? calloc((size_t)from->len, CODE_VALUE_SLOT_SIZE) : NULL;
for (long long i = 0; i < from->len; i++) {
code_native_copy_in(slot_at(slots, i), slot_at(from->items, i));
}
code_array(out, slots, from->len);
for (long long i = 0; i < from->len; i++) {
code_release(slot_at(slots, i));
}
free(slots);
return;
}
case CODE_OBJECT: {
const char **keys = from->len > 0 ? malloc((size_t)from->len * sizeof(const char *)) : NULL;
void *slots = from->len > 0 ? calloc((size_t)from->len, CODE_VALUE_SLOT_SIZE) : NULL;
for (long long i = 0; i < from->len; i++) {
keys[i] = from->keys[i];
code_native_copy_in(slot_at(slots, i), slot_at(from->items, i));
}
code_object(out, keys, slots, from->len);
for (long long i = 0; i < from->len; i++) {
code_release(slot_at(slots, i));
}
free(keys);
free(slots);
return;
}
}
}
#define CODE_SERVING_RECHECK_SECONDS 1
#ifndef CODE_WASM
static pthread_mutex_t code_wakeup_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t code_wakeup_cond = PTHREAD_COND_INITIALIZER;
static unsigned long code_wakeups = 0;
#endif
void code_emit_inbound(void *queue, const CodeValue *value) {
if (!queue || !value) {
return;
}
NativeHandle *nh = (NativeHandle *)queue;
code_mutex_lock(&nh->lock);
if (nh->closed) {
code_mutex_unlock(&nh->lock);
return;
}
int slot;
if (nh->inbound_count == CODE_INBOUND_CAPACITY) {
slot = nh->inbound_head;
code_release(&nh->inbound[slot]);
memset(&nh->inbound[slot], 0, sizeof(CodeValue));
nh->inbound_head = (nh->inbound_head + 1) % CODE_INBOUND_CAPACITY;
} else {
slot = (nh->inbound_head + nh->inbound_count) % CODE_INBOUND_CAPACITY;
nh->inbound_count++;
}
code_native_copy_in(&nh->inbound[slot], value);
code_mutex_unlock(&nh->lock);
#ifndef CODE_WASM
pthread_mutex_lock(&code_wakeup_lock);
code_wakeups++;
pthread_cond_broadcast(&code_wakeup_cond);
pthread_mutex_unlock(&code_wakeup_lock);
#endif
if (code_host && code_host->wake) {
code_host->wake(code_host_ctx);
}
}
int code_native_serving(void *handle) {
if (!handle) {
return 0;
}
NativeHandle *nh = (NativeHandle *)handle;
return nh->serving ? nh->serving() : 0;
}
void code_host_park(void) {
#ifndef CODE_WASM
struct timespec deadline;
clock_gettime(CLOCK_REALTIME, &deadline);
deadline.tv_sec += CODE_SERVING_RECHECK_SECONDS;
pthread_mutex_lock(&code_wakeup_lock);
while (code_wakeups == 0) {
if (pthread_cond_timedwait(&code_wakeup_cond, &code_wakeup_lock, &deadline) != 0) {
break;
}
}
code_wakeups = 0;
pthread_mutex_unlock(&code_wakeup_lock);
#endif
}
int code_poll_inbound(void *queue, CodeValue *out) {
if (!queue) {
return 0;
}
NativeHandle *nh = (NativeHandle *)queue;
code_mutex_lock(&nh->lock);
if (nh->inbound_count == 0) {
code_mutex_unlock(&nh->lock);
return 0;
}
int slot = nh->inbound_head;
code_copy(out, &nh->inbound[slot]);
code_release(&nh->inbound[slot]);
memset(&nh->inbound[slot], 0, sizeof(CodeValue));
nh->inbound_head = (nh->inbound_head + 1) % CODE_INBOUND_CAPACITY;
nh->inbound_count--;
code_mutex_unlock(&nh->lock);
return 1;
}
void code_native_reply(void *handle, const CodeValue *particle, const CodeValue *result) {
if (!handle) {
return;
}
NativeHandle *nh = (NativeHandle *)handle;
if (nh->reply) {
nh->reply(particle, result);
}
}
void code_native_close(void *handle) {
if (!handle) {
return;
}
NativeHandle *nh = (NativeHandle *)handle;
code_mutex_lock(&nh->lock);
for (int i = 0; i < nh->inbound_count; i++) {
code_release(&nh->inbound[(nh->inbound_head + i) % CODE_INBOUND_CAPACITY]);
}
nh->inbound_count = 0;
nh->closed = 1;
int has_inbound = nh->has_inbound;
code_mutex_unlock(&nh->lock);
if (has_inbound) {
return;
}
free(nh);
}
void code_native_dispatch(void *handle, CodeValue *out, const CodeValue *particle) {
code_null(out);
if (handle && ((NativeHandle *)handle)->from_host) {
NativeHandle *nh = (NativeHandle *)handle;
CodeValue result = {0};
nh->host.dispatch(nh->host.ctx, &result, particle);
code_native_copy_in(out, &result);
nh->host.release(nh->host.ctx, &result);
return;
}
if (!handle) {
fail("this module was released");
return;
}
NativeHandle *nh = (NativeHandle *)handle;
CodeValue result = {0};
nh->dispatch(&result, particle);
code_native_copy_in(out, &result);
nh->release(&result);
}
typedef struct HostedGuest HostedGuest;
typedef struct {
NativeHandle *handle;
char *path;
long long guest;
} RuntimeModule;
static RuntimeModule *runtime_modules = NULL;
static long long runtime_module_count = 0;
static long long runtime_module_cap = 0;
#define CODE_MODULE_FIELD "_module"
static long long module_row(const CodeValue *address) {
if (address->tag != CODE_OBJECT) {
fail("expected a module address (from a 'link' inside a handler)");
return -1;
}
const CodeValue *row = find_field(address, CODE_MODULE_FIELD);
if (!row || row->tag != CODE_NUMBER || row->number < 0) {
fail("expected a module address (from a 'link' inside a handler)");
return -1;
}
return (long long)row->number;
}
static NativeHandle *module_at(const CodeValue *address) {
long long row = module_row(address);
if (row < 0) {
return NULL;
}
if (row >= runtime_module_count || !runtime_modules[row].handle) {
fail("this module has been unlinked");
return NULL;
}
return runtime_modules[row].handle;
}
static void module_address(CodeValue *out, long long row) {
const char *keys[1] = {CODE_MODULE_FIELD};
_Alignas(8) char slots[CODE_VALUE_SLOT_SIZE] = {0};
code_number(slot_at(slots, 0), (double)row);
code_object(out, keys, slots, 1);
code_release(slot_at(slots, 0));
}
struct HostedGuest {
char *app;
};
typedef struct {
long long guest;
char *name;
int offered;
} HostedModule;
static HostedGuest *hosted_guests = NULL;
static long long hosted_guest_count = 0;
static long long hosted_guest_cap = 0;
static HostedModule *hosted_modules = NULL;
static long long hosted_module_count = 0;
static long long hosted_module_cap = 0;
static void *row_handle(long long row) { return (void *)(uintptr_t)(row + 1); }
static long long handle_row(void *handle) { return (long long)(uintptr_t)handle - 1; }
static void (*code_program_dispatch)(CodeValue *out, const CodeValue *particle) = NULL;
void code_set_program_dispatch(void (*fn)(CodeValue *out, const CodeValue *particle)) {
code_program_dispatch = fn;
}
#define CODE_EVENT_CAP 65536
static char code_event_buf[CODE_EVENT_CAP + 1];
char *code_event_text(void) { return code_event_buf; }
long long code_event_text_capacity(void) { return CODE_EVENT_CAP; }
static double number_parse(const char *text, size_t len);
typedef struct {
const char *at;
const char *end;
int failed;
} JsonReader;
static int json_value(JsonReader *r, CodeValue *out);
static void json_space(JsonReader *r) {
while (r->at < r->end && (*r->at == ' ' || *r->at == '\t' || *r->at == '\n' || *r->at == '\r')) {
r->at++;
}
}
static int json_char(JsonReader *r, char c) {
json_space(r);
if (r->at < r->end && *r->at == c) {
r->at++;
return 1;
}
return 0;
}
static void json_utf8(char **w, unsigned int cp) {
if (cp < 0x80) {
*(*w)++ = (char)cp;
} else if (cp < 0x800) {
*(*w)++ = (char)(0xC0 | (cp >> 6));
*(*w)++ = (char)(0x80 | (cp & 0x3F));
} else {
*(*w)++ = (char)(0xE0 | (cp >> 12));
*(*w)++ = (char)(0x80 | ((cp >> 6) & 0x3F));
*(*w)++ = (char)(0x80 | (cp & 0x3F));
}
}
static unsigned int json_hex4(JsonReader *r) {
unsigned int n = 0;
for (int i = 0; i < 4; i++) {
if (r->at >= r->end) {
r->failed = 1;
return 0;
}
char c = *r->at++;
n <<= 4;
if (c >= '0' && c <= '9') {
n |= (unsigned int)(c - '0');
} else if (c >= 'a' && c <= 'f') {
n |= (unsigned int)(c - 'a' + 10);
} else if (c >= 'A' && c <= 'F') {
n |= (unsigned int)(c - 'A' + 10);
} else {
r->failed = 1;
return 0;
}
}
return n;
}
static char *json_string(JsonReader *r) {
if (!json_char(r, '"')) {
r->failed = 1;
return NULL;
}
char *text = malloc((size_t)(r->end - r->at) + 1);
if (!text) {
r->failed = 1;
return NULL;
}
char *w = text;
while (r->at < r->end) {
char c = *r->at++;
if (c == '"') {
*w = 0;
return text;
}
if (c != '\\') {
*w++ = c;
continue;
}
if (r->at >= r->end) {
break;
}
char esc = *r->at++;
switch (esc) {
case '"': *w++ = '"'; break;
case '\\': *w++ = '\\'; break;
case '/': *w++ = '/'; break;
case 'b': *w++ = '\b'; break;
case 'f': *w++ = '\f'; break;
case 'n': *w++ = '\n'; break;
case 'r': *w++ = '\r'; break;
case 't': *w++ = '\t'; break;
case 'u': {
unsigned int cp = json_hex4(r);
if (r->failed) {
free(text);
return NULL;
}
if (cp >= 0xD800 && cp <= 0xDBFF && r->end - r->at >= 6 && r->at[0] == '\\' &&
r->at[1] == 'u') {
const char *save = r->at;
r->at += 2;
unsigned int low = json_hex4(r);
if (!r->failed && low >= 0xDC00 && low <= 0xDFFF) {
cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00);
*w++ = (char)(0xF0 | (cp >> 18));
*w++ = (char)(0x80 | ((cp >> 12) & 0x3F));
*w++ = (char)(0x80 | ((cp >> 6) & 0x3F));
*w++ = (char)(0x80 | (cp & 0x3F));
break;
}
r->failed = 0;
r->at = save;
}
json_utf8(&w, cp);
break;
}
default:
free(text);
r->failed = 1;
return NULL;
}
}
free(text);
r->failed = 1;
return NULL;
}
static int json_literal(JsonReader *r, const char *word) {
size_t n = strlen(word);
if ((size_t)(r->end - r->at) < n) {
return 0;
}
for (size_t i = 0; i < n; i++) {
if (r->at[i] != word[i]) {
return 0;
}
}
r->at += n;
return 1;
}
typedef struct {
void *values;
const char **keys;
long long len;
long long cap;
} JsonList;
static int json_list_room(JsonList *list, int with_keys) {
if (list->len < list->cap) {
return 1;
}
long long cap = list->cap ? list->cap * 2 : 8;
void *values = realloc(list->values, (size_t)cap * CODE_VALUE_SLOT_SIZE);
if (!values) {
return 0;
}
list->values = values;
memset((char *)list->values + (size_t)list->len * CODE_VALUE_SLOT_SIZE, 0,
(size_t)(cap - list->len) * CODE_VALUE_SLOT_SIZE);
if (with_keys) {
const char **keys = realloc(list->keys, (size_t)cap * sizeof(const char *));
if (!keys) {
return 0;
}
list->keys = keys;
}
list->cap = cap;
return 1;
}
static void json_list_free(JsonList *list, int with_keys) {
for (long long i = 0; i < list->len; i++) {
code_release(slot_at(list->values, i));
if (with_keys) {
free((void *)list->keys[i]);
}
}
free(list->values);
free(list->keys);
}
static int json_array(JsonReader *r, CodeValue *out) {
JsonList list = {0};
if (json_char(r, ']')) {
code_array(out, NULL, 0);
return 1;
}
for (;;) {
if (!json_list_room(&list, 0)) {
r->failed = 1;
break;
}
if (!json_value(r, slot_at(list.values, list.len))) {
break;
}
list.len++;
if (json_char(r, ',')) {
continue;
}
if (json_char(r, ']')) {
code_array(out, list.values, list.len);
json_list_free(&list, 0);
return 1;
}
r->failed = 1;
break;
}
json_list_free(&list, 0);
return 0;
}
static int json_object(JsonReader *r, CodeValue *out) {
JsonList list = {0};
if (json_char(r, '}')) {
code_object(out, NULL, NULL, 0);
return 1;
}
for (;;) {
if (!json_list_room(&list, 1)) {
r->failed = 1;
break;
}
json_space(r);
char *key = json_string(r);
if (!key) {
break;
}
list.keys[list.len] = key;
if (!json_char(r, ':') || !json_value(r, slot_at(list.values, list.len))) {
free(key);
r->failed = 1;
break;
}
list.len++;
if (json_char(r, ',')) {
continue;
}
if (json_char(r, '}')) {
code_object(out, list.keys, list.values, list.len);
json_list_free(&list, 1);
return 1;
}
r->failed = 1;
break;
}
json_list_free(&list, 1);
return 0;
}
static int json_value(JsonReader *r, CodeValue *out) {
json_space(r);
if (r->at >= r->end) {
r->failed = 1;
return 0;
}
char c = *r->at;
if (c == '{') {
r->at++;
return json_object(r, out);
}
if (c == '[') {
r->at++;
return json_array(r, out);
}
if (c == '"') {
char *text = json_string(r);
if (!text) {
return 0;
}
code_str_owned(out, text);
free(text);
return 1;
}
if (json_literal(r, "true")) {
code_bool(out, 1);
return 1;
}
if (json_literal(r, "false")) {
code_bool(out, 0);
return 1;
}
if (json_literal(r, "null")) {
code_null(out);
return 1;
}
if (c == '-' || (c >= '0' && c <= '9')) {
const char *start = r->at;
if (*r->at == '-') {
r->at++;
}
while (r->at < r->end && *r->at >= '0' && *r->at <= '9') {
r->at++;
}
if (r->at < r->end && *r->at == '.') {
r->at++;
while (r->at < r->end && *r->at >= '0' && *r->at <= '9') {
r->at++;
}
}
if (r->at < r->end && (*r->at == 'e' || *r->at == 'E')) {
const char *exp = r->at;
r->at++;
if (r->at < r->end && (*r->at == '+' || *r->at == '-')) {
r->at++;
}
if (r->at < r->end && *r->at >= '0' && *r->at <= '9') {
while (r->at < r->end && *r->at >= '0' && *r->at <= '9') {
r->at++;
}
} else {
r->at = exp;
}
}
if (r->at == start || (r->at == start + 1 && *start == '-')) {
r->failed = 1;
return 0;
}
code_number(out, number_parse(start, (size_t)(r->at - start)));
return 1;
}
r->failed = 1;
return 0;
}
static int code_event_read(long long len, CodeValue *out) {
if (!code_program_dispatch) {
return 0;
}
if (len <= 0) {
return 0;
}
if (len > CODE_EVENT_CAP) {
len = CODE_EVENT_CAP;
}
code_event_buf[len] = 0;
JsonReader reader = {code_event_buf, code_event_buf + len, 0};
CodeValue particle = {0};
if (!json_value(&reader, &particle) || reader.failed) {
code_release(&particle);
return 0;
}
if (particle.tag != CODE_OBJECT) {
code_release(&particle);
return 0;
}
for (long long i = 0; i < particle.len; i++) {
if (strcmp(particle.keys[i], "_class") == 0 &&
slot_at(particle.items, i)->tag == CODE_STR) {
*out = particle;
return 1;
}
}
code_release(&particle);
return 0;
}
void code_event_fire(long long len) {
CodeValue particle = {0};
if (!code_event_read(len, &particle)) {
return;
}
CodeValue answer = {0};
code_program_dispatch(&answer, &particle);
code_release(&answer);
code_release(&particle);
}
long long code_event_ask(long long len) {
CodeValue particle = {0};
if (!code_event_read(len, &particle)) {
return 0;
}
CodeValue answer = {0};
code_program_dispatch(&answer, &particle);
long long written = code_json_write(&answer, code_event_buf, CODE_EVENT_CAP);
code_release(&answer);
code_release(&particle);
return written > 0 ? written : 0;
}
void code_to_text(CodeValue *out, const CodeValue *v);
long long code_json_write(const CodeValue *v, char *out, long long cap) {
if (!out || cap <= 0) {
return -1;
}
CodeValue text = {0};
code_to_text(&text, v);
if (text.tag != CODE_STR || !text.str) {
code_release(&text);
return -1;
}
long long len = (long long)strlen(text.str);
if (len >= cap) {
code_release(&text);
return -1;
}
memcpy(out, text.str, (size_t)len + 1);
code_release(&text);
return len;
}
int code_json_read(const char *text, long long len, CodeValue *out) {
if (!text || len <= 0 || !out) {
return 0;
}
JsonReader reader = {text, text + len, 0};
CodeValue value = {0};
if (!json_value(&reader, &value) || reader.failed) {
code_release(&value);
return 0;
}
code_release(out);
*out = value;
return 1;
}
static void module_stem(const char *ref, char *out, size_t outlen) {
const char *start = ref;
for (const char *c = ref; *c; c++) {
if (*c == '/') start = c + 1;
}
size_t n = strlen(start);
if (n > 3 && strcmp(start + n - 3, ".so") == 0) n -= 3;
for (size_t i = 0; i < n; i++) {
if (start[i] == '-') {
n = i;
break;
}
}
if (n >= outlen) n = outlen - 1;
memcpy(out, start, n);
out[n] = '\0';
}
static void ask_program(CodeValue *out, const char *class_name, const char *app, const char *name,
const CodeValue *extra) {
code_null(out);
if (!code_program_dispatch) return;
const char *keys[4] = {"_class", "app", "name", "particle"};
_Alignas(8) char slots[4 * CODE_VALUE_SLOT_SIZE] = {0};
code_str(slot_at(slots, 0), class_name);
code_str_owned(slot_at(slots, 1), app);
code_str_owned(slot_at(slots, 2), name);
long long len = 3;
if (extra) {
code_native_copy_in(slot_at(slots, 3), extra);
len = 4;
}
CodeValue particle = {0};
code_object(&particle, keys, slots, len);
for (long long i = 0; i < len; i++) code_release(slot_at(slots, i));
code_program_dispatch(out, &particle);
code_release(&particle);
}
static int is_class(const CodeValue *v, const char *class_name) {
if (v->tag != CODE_OBJECT) return 0;
const CodeValue *cls = find_field(v, "_class");
return cls && cls->tag == CODE_STR && cls->str && strcmp(cls->str, class_name) == 0;
}
static void hosted_dispatch(void *ctx, CodeValue *out, const CodeValue *particle) {
long long row = handle_row(ctx);
if (row < 0 || row >= hosted_module_count || !hosted_modules[row].name) {
code_make_exception(out, "host", "this module's application has been stopped", NULL);
return;
}
HostedModule *o = &hosted_modules[row];
if (!o->offered) {
char msg[256];
snprintf(msg, sizeof msg, "module '%s' is not offered by the host", o->name);
code_make_exception(out, "host", msg, NULL);
return;
}
const char *app = hosted_guests[o->guest].app;
ask_program(out, "Module", app ? app : "", o->name, particle);
}
static void hosted_release(void *ctx, CodeValue *v) {
(void)ctx;
code_release(v);
}
static int hosted_resolve(void *host_ctx, const char *ref, CodeHostModule *out) {
long long guest = handle_row(host_ctx);
if (guest < 0 || guest >= hosted_guest_count || !hosted_guests[guest].app) return 0;
char name[128];
module_stem(ref, name, sizeof name);
CodeValue answer = {0};
ask_program(&answer, "Offer", hosted_guests[guest].app, name, NULL);
if (answer.tag == CODE_NULL) {
code_release(&answer);
return 0;
}
int offered = is_class(&answer, "Offered");
code_release(&answer);
if (hosted_module_count == hosted_module_cap) {
long long cap = hosted_module_cap ? hosted_module_cap * 2 : 8;
HostedModule *grown = realloc(hosted_modules, (size_t)cap * sizeof(HostedModule));
if (!grown) code_runtime_error("out of memory");
hosted_modules = grown;
hosted_module_cap = cap;
}
char *kept = malloc(strlen(name) + 1);
if (!kept) code_runtime_error("out of memory");
memcpy(kept, name, strlen(name) + 1);
long long row = hosted_module_count++;
hosted_modules[row].guest = guest;
hosted_modules[row].name = kept;
hosted_modules[row].offered = offered;
out->dispatch = hosted_dispatch;
out->release = hosted_release;
out->vars = NULL;
out->serving = NULL;
out->ctx = row_handle(row);
return 1;
}
static void hosted_wake(void *host_ctx) {
(void)host_ctx;
#ifndef CODE_WASM
pthread_mutex_lock(&code_wakeup_lock);
code_wakeups++;
pthread_cond_broadcast(&code_wakeup_cond);
pthread_mutex_unlock(&code_wakeup_lock);
#endif
}
static const CodeHostVtable hosted_vtable = {hosted_resolve, hosted_wake};
static long long open_hosted_guest(const char *path) {
if (hosted_guest_count == hosted_guest_cap) {
long long cap = hosted_guest_cap ? hosted_guest_cap * 2 : 8;
HostedGuest *grown = realloc(hosted_guests, (size_t)cap * sizeof(HostedGuest));
if (!grown) code_runtime_error("out of memory");
hosted_guests = grown;
hosted_guest_cap = cap;
}
char *kept = malloc(strlen(path) + 1);
if (!kept) code_runtime_error("out of memory");
memcpy(kept, path, strlen(path) + 1);
long long row = hosted_guest_count++;
hosted_guests[row].app = kept;
return row;
}
static void close_hosted_guest(long long guest) {
if (guest < 0 || guest >= hosted_guest_count) return;
for (long long i = 0; i < hosted_module_count; i++) {
if (hosted_modules[i].name && hosted_modules[i].guest == guest) {
free(hosted_modules[i].name);
hosted_modules[i].name = NULL;
}
}
free(hosted_guests[guest].app);
hosted_guests[guest].app = NULL;
}
void code_runtime_link(CodeValue *out, const CodeValue *path) {
code_null(out);
if (path->tag != CODE_STR) {
fail("'link' needs a path");
return;
}
const char *text = path->str ? path->str : "";
size_t n = strlen(text);
if (n < 3 || strcmp(text + n - 3, ".so") != 0) {
char msg[256];
snprintf(msg, sizeof msg,
"'link %s' inside a handler can only open a module ('.so')", text);
fail(msg);
return;
}
char rooted[512];
int has_slash = 0;
for (const char *c = text; *c; c++) {
if (*c == '/') {
has_slash = 1;
break;
}
}
if (!has_slash) {
snprintf(rooted, sizeof rooted, "./%s", text);
text = rooted;
}
char err[256];
NativeHandle *nh = open_native(text, err, sizeof err);
if (!nh) {
fail(err);
return;
}
if (runtime_module_count == runtime_module_cap) {
long long cap = runtime_module_cap ? runtime_module_cap * 2 : 8;
RuntimeModule *grown =
realloc(runtime_modules, (size_t)cap * sizeof(RuntimeModule));
if (!grown) {
code_runtime_error("out of memory");
}
runtime_modules = grown;
runtime_module_cap = cap;
}
long long row = runtime_module_count++;
long long guest = -1;
#ifndef CODE_WASM
void (*set_host)(const CodeHostVtable *, void *) =
(void (*)(const CodeHostVtable *, void *))dlsym(nh->lib, "code_module_set_host");
if (set_host) {
guest = open_hosted_guest(text);
set_host(&hosted_vtable, row_handle(guest));
}
#endif
size_t kept_len = strlen(text);
char *kept = malloc(kept_len + 1);
if (!kept) {
code_runtime_error("out of memory");
}
memcpy(kept, text, kept_len + 1);
runtime_modules[row].handle = nh;
runtime_modules[row].path = kept;
runtime_modules[row].guest = guest;
module_address(out, row);
}
static void release_module(long long row) {
RuntimeModule *slot = &runtime_modules[row];
NativeHandle *nh = slot->handle;
if (nh->module_release) {
nh->module_release();
}
void *lib = nh->lib;
int image = nh->image_fd;
code_native_close(nh);
close_hosted_guest(slot->guest);
free(slot->path);
slot->handle = NULL;
slot->path = NULL;
#ifndef CODE_WASM
if (lib) {
dlclose(lib);
}
if (image >= 0) {
close(image);
}
#else
(void)lib;
(void)image;
#endif
}
void code_runtime_unlink(const CodeValue *address) {
NativeHandle *nh = module_at(address);
if (!nh) {
return;
}
if (code_native_serving(nh)) {
fail("this module is still working — stop what it holds before unlinking it");
return;
}
release_module(module_row(address));
}
void code_runtime_unlink_all(void) {
for (long long i = 0; i < runtime_module_count; i++) {
if (runtime_modules[i].handle && code_native_serving(runtime_modules[i].handle)) {
continue;
}
while (runtime_modules[i].handle) {
release_module(i);
}
}
free(runtime_modules);
runtime_modules = NULL;
runtime_module_count = 0;
runtime_module_cap = 0;
}
void code_runtime_drain_guests(void) {
for (long long i = 0; i < runtime_module_count; i++) {
NativeHandle *nh = runtime_modules[i].handle;
if (nh && nh->module_drain) {
nh->module_drain();
}
}
}
void code_runtime_drain_speakers(void) {
int more = 1;
while (more) {
more = 0;
for (long long i = 0; i < runtime_module_count; i++) {
NativeHandle *nh = runtime_modules[i].handle;
if (!nh || !nh->has_inbound) {
continue;
}
CodeValue particle = {0};
while (code_poll_inbound(nh, &particle)) {
more = 1;
CodeValue answer = {0};
if (code_program_dispatch) {
code_program_dispatch(&answer, &particle);
} else {
code_null(&answer);
}
code_native_reply(nh, &particle, &answer);
code_release(&answer);
code_release(&particle);
memset(&particle, 0, sizeof particle);
}
}
}
}
int code_runtime_any_serving(void) {
for (long long i = 0; i < runtime_module_count; i++) {
NativeHandle *nh = runtime_modules[i].handle;
if (nh && code_native_serving(nh)) {
return 1;
}
}
return 0;
}
void code_runtime_dispatch(CodeValue *out, const CodeValue *address, const CodeValue *particle) {
code_null(out);
NativeHandle *nh = module_at(address);
if (!nh) {
return;
}
code_native_dispatch(nh, out, particle);
}
void code_native_vars_object(void *handle, CodeValue *out) {
NativeHandle *nh = (NativeHandle *)handle;
const CodeVarList *list = nh->vars ? nh->vars() : NULL;
long long count = list ? list->count : 0;
if (count < 0) {
code_runtime_error("native module reports a negative variable count");
}
const char **keys = NULL;
void *values = NULL;
if (count > 0) {
keys = (const char **)malloc((size_t)count * sizeof(const char *));
values = calloc((size_t)count, CODE_VALUE_SLOT_SIZE);
for (long long i = 0; i < count; i++) {
keys[i] = list->names[i];
code_native_copy_in(slot_at(values, i), slot_at(list->values, i));
}
}
code_object(out, keys, values, count);
if (count > 0) {
for (long long i = 0; i < count; i++) {
code_release(slot_at(values, i));
}
free(values);
}
free(keys);
}
void code_static_vars_object(const CodeVarList *list, CodeValue *out) {
long long count = list ? list->count : 0;
if (count < 0) {
code_runtime_error("native module reports a negative variable count");
}
const char **keys = NULL;
void *values = NULL;
if (count > 0) {
keys = (const char **)malloc((size_t)count * sizeof(const char *));
values = malloc((size_t)count * CODE_VALUE_SLOT_SIZE);
for (long long i = 0; i < count; i++) {
keys[i] = list->names[i];
CodeValue *slot = slot_at(values, i);
*slot = *slot_at(list->values, i);
code_retain(slot);
}
}
code_object(out, keys, values, count);
if (count > 0) {
for (long long i = 0; i < count; i++) {
code_release(slot_at(values, i));
}
free(values);
}
free(keys);
}
long long code_iter_len(const CodeValue *v) {
if (v->tag != CODE_ARRAY && v->tag != CODE_OBJECT) {
fail_operand("loop requires an array or object", v);
return 0;
}
return v->len;
}
void code_iter_at(CodeValue *out, const CodeValue *arr, long long i) {
code_copy(out, slot_at(arr->items, i));
}
void code_iter_key(CodeValue *out, const CodeValue *v, long long i) {
if (v->tag == CODE_OBJECT) {
code_str_owned(out, v->keys[i]);
return;
}
code_number(out, (double)i);
}
void code_check_emittable(const CodeValue *v) {
if (v->tag == CODE_OBJECT) {
for (long long i = 0; i < v->len; i++) {
if (strcmp(v->keys[i], "_class") == 0) {
return;
}
}
}
char msg[160];
snprintf(msg, sizeof msg,
"emit requires a particle — an object with a '_class' field — found %s %s",
article_for(v), type_name(v));
fail(msg);
}
void code_check_particle(const CodeValue *v) {
if (v->tag == CODE_OBJECT) {
for (long long i = 0; i < v->len; i++) {
if (strcmp(v->keys[i], "_class") == 0) {
return;
}
}
}
char msg[128];
snprintf(msg, sizeof msg,
"a handler must return a particle — an object with a '_class' field — found %s %s",
article_for(v), type_name(v));
fail(msg);
}
typedef struct {
char *buf;
size_t len;
size_t cap;
} TextBuf;
static void text_push(TextBuf *t, const char *s, size_t n) {
if (t->len + n + 1 > t->cap) {
size_t next = t->cap ? t->cap : 64;
while (next < t->len + n + 1) {
next *= 2;
}
char *bigger = realloc(t->buf, next);
if (!bigger) {
code_runtime_error("out of memory");
}
t->buf = bigger;
t->cap = next;
}
memcpy(t->buf + t->len, s, n);
t->len += n;
}
static void text_push_str(TextBuf *t, const char *s) { text_push(t, s, strlen(s)); }
static void number_exact(char *out, size_t cap, double d) {
#ifdef CODE_WASM
int written = code_host_number_exact(d, out, (unsigned int)cap);
if (written < 0 || (size_t)written >= cap) {
code_runtime_error("the host could not render a number as text");
}
out[written] = '\0';
#else
snprintf(out, cap, "%.40e", d);
#endif
}
static double number_parse(const char *text, size_t len) {
#ifdef CODE_WASM
return code_host_number_parse(text, (unsigned int)len);
#else
(void)len;
return strtod(text, NULL);
#endif
}
static void text_push_number(TextBuf *t, double d) {
char tmp[512];
if (d == (double)(long long)d && d >= -9007199254740992.0 && d <= 9007199254740992.0) {
if (d == 0.0 && 1.0 / d < 0.0) {
text_push_str(t, "-0");
return;
}
snprintf(tmp, sizeof tmp, "%lld", (long long)d);
text_push_str(t, tmp);
return;
}
char exact[80];
number_exact(exact, sizeof exact, d);
const char *p = exact;
int negative = (*p == '-');
if (negative) {
p++;
}
char full[48];
size_t nfull = 0;
for (; *p && *p != 'e'; p++) {
if (*p != '.') {
full[nfull++] = *p;
}
}
int fullexp = (int)strtol(p + 1, NULL, 10);
char m[48];
size_t n = 1;
int exp10 = fullexp;
for (int len = 1; len <= 17; len++) {
n = (size_t)len;
exp10 = fullexp;
memcpy(m, full, n);
if (nfull > n && full[n] >= '5') {
size_t i = n;
while (i > 0) {
if (m[i - 1] == '9') {
m[i - 1] = '0';
i--;
} else {
m[i - 1]++;
break;
}
}
if (i == 0) {
memmove(m + 1, m, n);
m[0] = '1';
exp10++;
}
}
char sci[64];
size_t o = 0;
if (negative) {
sci[o++] = '-';
}
sci[o++] = m[0];
if (n > 1) {
sci[o++] = '.';
memcpy(sci + o, m + 1, n - 1);
o += n - 1;
}
o += (size_t)snprintf(sci + o, sizeof sci - o, "e%d", exp10);
sci[o] = '\0';
if (number_parse(sci, o) == d) {
break;
}
}
while (n > 1 && m[n - 1] == '0') {
n--;
}
size_t out = 0;
if (negative) {
tmp[out++] = '-';
}
if (exp10 >= (int)n - 1) {
memcpy(tmp + out, m, n);
out += n;
for (int i = 0; i < exp10 - (int)n + 1; i++) {
tmp[out++] = '0';
}
} else if (exp10 >= 0) {
memcpy(tmp + out, m, (size_t)exp10 + 1);
out += (size_t)exp10 + 1;
tmp[out++] = '.';
memcpy(tmp + out, m + exp10 + 1, n - (size_t)exp10 - 1);
out += n - (size_t)exp10 - 1;
} else {
tmp[out++] = '0';
tmp[out++] = '.';
for (int i = 0; i < -exp10 - 1; i++) {
tmp[out++] = '0';
}
memcpy(tmp + out, m, n);
out += n;
}
text_push(t, tmp, out);
}
static void text_push_json_string(TextBuf *t, const char *s) {
text_push(t, "\"", 1);
for (const char *p = s; *p; p++) {
switch (*p) {
case '"': text_push(t, "\\\"", 2); break;
case '\\': text_push(t, "\\\\", 2); break;
case '\n': text_push(t, "\\n", 2); break;
case '\t': text_push(t, "\\t", 2); break;
default: text_push(t, p, 1); break;
}
}
text_push(t, "\"", 1);
}
typedef struct {
const CodeValue *value;
const char *punct;
int is_key;
} TextStep;
static TextStep *steps = NULL;
static size_t steps_cap = 0;
void code_to_text(CodeValue *out, const CodeValue *v) {
TextBuf t = {NULL, 0, 0};
size_t len = 0;
steps = grow(steps, &steps_cap, len + 1, sizeof(TextStep));
steps[len++] = (TextStep){v, NULL, 0};
int top_level = 1;
while (len > 0) {
TextStep step = steps[--len];
if (!step.value) {
if (step.is_key) {
text_push_json_string(&t, step.punct);
text_push(&t, ":", 1);
} else {
text_push_str(&t, step.punct);
}
continue;
}
const CodeValue *current = step.value;
switch (current->tag) {
case CODE_NUMBER:
text_push_number(&t, current->number);
break;
case CODE_STR:
if (top_level) {
text_push_str(&t, current->str);
} else {
text_push_json_string(&t, current->str);
}
break;
case CODE_BOOL:
text_push_str(&t, current->boolean ? "true" : "false");
break;
case CODE_NULL:
text_push_str(&t, "null");
break;
case CODE_ARRAY:
text_push(&t, "[", 1);
steps = grow(steps, &steps_cap, len + 1, sizeof(TextStep));
steps[len++] = (TextStep){NULL, "]", 0};
for (long long i = current->len - 1; i >= 0; i--) {
steps = grow(steps, &steps_cap, len + 2, sizeof(TextStep));
steps[len++] = (TextStep){slot_at(current->items, i), NULL, 0};
if (i > 0) {
steps[len++] = (TextStep){NULL, ",", 0};
}
}
break;
case CODE_OBJECT:
text_push(&t, "{", 1);
steps = grow(steps, &steps_cap, len + 1, sizeof(TextStep));
steps[len++] = (TextStep){NULL, "}", 0};
for (long long i = current->len - 1; i >= 0; i--) {
steps = grow(steps, &steps_cap, len + 3, sizeof(TextStep));
steps[len++] = (TextStep){slot_at(current->items, i), NULL, 0};
steps[len++] = (TextStep){NULL, current->keys[i], 1};
if (i > 0) {
steps[len++] = (TextStep){NULL, ",", 0};
}
}
break;
}
top_level = 0;
}
text_push(&t, "", 0);
t.buf[t.len] = '\0';
char *owned = heap_alloc(t.len + 1);
memcpy(owned, t.buf, t.len + 1);
free(t.buf);
code_release(out);
out->tag = CODE_STR;
out->heap = 1;
out->str = owned;
}
void code_add(CodeValue *out, const CodeValue *a, const CodeValue *b) {
if (a->tag == CODE_NUMBER && b->tag == CODE_NUMBER) {
code_number(out, a->number + b->number);
return;
}
if (a->tag == CODE_STR && b->tag == CODE_STR) {
size_t la = strlen(a->str);
size_t lb = strlen(b->str);
char *buf = heap_alloc(la + lb + 1);
memcpy(buf, a->str, la);
memcpy(buf + la, b->str, lb);
buf[la + lb] = '\0';
code_release(out);
out->tag = CODE_STR;
out->heap = 1;
out->str = buf;
return;
}
if (a->tag == CODE_ARRAY || b->tag == CODE_ARRAY) {
long long na = (a->tag == CODE_ARRAY) ? a->len : 1;
long long nb = (b->tag == CODE_ARRAY) ? b->len : 1;
long long total = na + nb;
void *buf = NULL;
if (total > 0) {
buf = heap_alloc((size_t)total * CODE_VALUE_SLOT_SIZE);
for (long long i = 0; i < na; i++) {
const CodeValue *src = (a->tag == CODE_ARRAY) ? slot_at(a->items, i) : a;
code_retain(src);
*slot_at(buf, i) = *src;
}
for (long long i = 0; i < nb; i++) {
const CodeValue *src = (b->tag == CODE_ARRAY) ? slot_at(b->items, i) : b;
code_retain(src);
*slot_at(buf, na + i) = *src;
}
}
code_release(out);
out->tag = CODE_ARRAY;
out->heap = total > 0;
out->items = buf;
out->len = total;
return;
}
if (a->tag == CODE_OBJECT && b->tag == CODE_OBJECT) {
long long total = a->len;
for (long long j = 0; j < b->len; j++) {
if (find_field(a, b->keys[j]) == NULL) {
total++;
}
}
const char **key_buf = NULL;
void *value_buf = NULL;
if (total > 0) {
size_t keys_bytes = (size_t)total * sizeof(const char *);
size_t slots_bytes = (size_t)total * CODE_VALUE_SLOT_SIZE;
size_t chars_bytes = 0;
for (long long i = 0; i < a->len; i++) {
chars_bytes += (a->keys[i] ? strlen(a->keys[i]) : 0) + 1;
}
for (long long j = 0; j < b->len; j++) {
if (find_field(a, b->keys[j]) == NULL) {
chars_bytes += (b->keys[j] ? strlen(b->keys[j]) : 0) + 1;
}
}
key_buf = heap_alloc(keys_bytes + slots_bytes + chars_bytes);
value_buf = (char *)key_buf + keys_bytes;
char *chars = (char *)value_buf + slots_bytes;
long long n = 0;
for (long long i = 0; i < a->len; i++) {
const CodeValue *override_val = find_field(b, a->keys[i]);
const CodeValue *src = override_val ? override_val : slot_at(a->items, i);
key_buf[n] = copy_key(&chars, a->keys[i]);
code_retain(src);
*slot_at(value_buf, n) = *src;
n++;
}
for (long long j = 0; j < b->len; j++) {
if (find_field(a, b->keys[j]) != NULL) {
continue;
}
key_buf[n] = copy_key(&chars, b->keys[j]);
const CodeValue *src = slot_at(b->items, j);
code_retain(src);
*slot_at(value_buf, n) = *src;
n++;
}
}
code_release(out);
out->tag = CODE_OBJECT;
out->heap = total > 0;
out->keys = key_buf;
out->items = value_buf;
out->len = total;
return;
}
if ((a->tag == CODE_STR || b->tag == CODE_STR)
&& a->tag != CODE_ARRAY && b->tag != CODE_ARRAY
&& a->tag != CODE_OBJECT && b->tag != CODE_OBJECT) {
CodeValue ta = {0};
CodeValue tb = {0};
code_to_text(&ta, a);
code_to_text(&tb, b);
size_t la = strlen(ta.str);
size_t lb = strlen(tb.str);
char *buf = heap_alloc(la + lb + 1);
memcpy(buf, ta.str, la);
memcpy(buf + la, tb.str, lb);
buf[la + lb] = '\0';
code_release(&ta);
code_release(&tb);
code_release(out);
out->tag = CODE_STR;
out->heap = 1;
out->str = buf;
return;
}
fail_binary("+", a, b);
}
void code_sub(CodeValue *out, const CodeValue *a, const CodeValue *b) {
if (a->tag == CODE_NUMBER && b->tag == CODE_NUMBER) {
code_number(out, a->number - b->number);
return;
}
fail_binary("-", a, b);
}
void code_mul(CodeValue *out, const CodeValue *a, const CodeValue *b) {
if (a->tag == CODE_NUMBER && b->tag == CODE_NUMBER) {
code_number(out, a->number * b->number);
return;
}
fail_binary("*", a, b);
}
void code_div(CodeValue *out, const CodeValue *a, const CodeValue *b) {
if (a->tag == CODE_NUMBER && b->tag == CODE_NUMBER) {
if (b->number == 0.0) {
fail("division by zero");
return;
}
code_number(out, a->number / b->number);
return;
}
fail_binary("/", a, b);
}
long long code_compare(const CodeValue *a, const CodeValue *b, const char *op) {
if (a->tag == CODE_NUMBER && b->tag == CODE_NUMBER) {
if (a->number < b->number) {
return -1;
}
return a->number > b->number ? 1 : 0;
}
fail_binary(op, a, b);
return 0;
}
void code_neg(CodeValue *out, const CodeValue *a) {
if (a->tag == CODE_NUMBER) {
code_number(out, -a->number);
return;
}
char msg[96];
snprintf(msg, sizeof msg, "cannot negate %s %s", article_for(a), type_name(a));
fail(msg);
}
void code_not(CodeValue *out, const CodeValue *a) {
if (a->tag == CODE_BOOL) {
code_bool(out, !a->boolean);
return;
}
fail_operand("'not' requires a boolean", a);
}
int code_is_kind(const CodeValue *a, int tag) {
return a->tag == (CodeTag)tag ? 1 : 0;
}
int code_is_particle(const CodeValue *a, const char *name) {
if (a->tag != CODE_OBJECT) {
return 0;
}
const CodeValue *class_val = find_field(a, "_class");
if (!class_val || class_val->tag != CODE_STR) {
return 0;
}
return strcmp(class_val->str, name) == 0 ? 1 : 0;
}
int code_bool_value(const CodeValue *v, const char *requirement) {
if (v->tag != CODE_BOOL) {
fail_operand(requirement, v);
return 0;
}
return v->boolean;
}
typedef struct {
const CodeValue *a;
const CodeValue *b;
} Pair;
static Pair *pending = NULL;
static size_t pending_cap = 0;
int code_values_equal(const CodeValue *a, const CodeValue *b) {
size_t len = 0;
pending = grow(pending, &pending_cap, len + 1, sizeof(Pair));
pending[len].a = a;
pending[len].b = b;
len++;
while (len > 0) {
Pair pair = pending[--len];
const CodeValue *x = pair.a;
const CodeValue *y = pair.b;
if (x->tag != y->tag) {
return 0;
}
switch (x->tag) {
case CODE_NUMBER:
if (x->number != y->number) {
return 0;
}
break;
case CODE_STR:
if (strcmp(x->str, y->str) != 0) {
return 0;
}
break;
case CODE_BOOL:
if (x->boolean != y->boolean) {
return 0;
}
break;
case CODE_NULL:
break;
case CODE_ARRAY:
if (x->len != y->len) {
return 0;
}
pending = grow(pending, &pending_cap, len + (size_t)x->len, sizeof(Pair));
for (long long i = 0; i < x->len; i++) {
pending[len].a = slot_at(x->items, i);
pending[len].b = slot_at(y->items, i);
len++;
}
break;
case CODE_OBJECT:
if (x->len != y->len) {
return 0;
}
pending = grow(pending, &pending_cap, len + (size_t)x->len, sizeof(Pair));
for (long long i = 0; i < x->len; i++) {
long long seen = 0;
for (long long p = 0; p < i; p++) {
if (strcmp(x->keys[p], x->keys[i]) == 0) {
seen++;
}
}
long long found = -1;
for (long long q = 0; q < y->len; q++) {
if (strcmp(y->keys[q], x->keys[i]) == 0) {
if (seen == 0) {
found = q;
break;
}
seen--;
}
}
if (found < 0) {
return 0;
}
pending[len].a = slot_at(x->items, i);
pending[len].b = slot_at(y->items, found);
len++;
}
break;
}
}
return 1;
}
void code_assert(const CodeValue *v) {
if (v->tag != CODE_BOOL) {
fail_operand("assert requires a boolean", v);
return;
}
if (!v->boolean) {
fail("assertion failed");
}
}
#ifndef CODE_WASM
typedef struct {
long long depth;
char *target, *particle, *answer;
} CodeTraceEvent;
static CodeTraceEvent *trace_events;
static size_t trace_len, trace_cap;
static long long trace_depth;
static FILE *trace_file;
static char *trace_json(const CodeValue *v) {
if (v->tag == CODE_STR) {
TextBuf t = {NULL, 0, 0};
text_push_json_string(&t, v->str);
t.buf[t.len] = '\0';
return t.buf;
}
CodeValue text = {0};
code_to_text(&text, v);
char *result = strdup(text.str);
code_release(&text);
if (!result) code_runtime_error("cannot allocate execution trace");
return result;
}
static void trace_write(void) {
fputs("{\"schema_version\":1,\"entry\":\"\",\"events\":[", trace_file);
for (size_t i = 0; i < trace_len; i++) {
CodeTraceEvent *event = &trace_events[i];
fprintf(trace_file, "%s{\"sequence\":%zu,\"depth\":%lld,\"target\":%s,"
"\"particle_class\":\"\",\"particle\":%s,\"answer\":%s}",
i ? "," : "", i, event->depth, event->target,
event->particle, event->answer ? event->answer : "null");
free(event->target);
free(event->particle);
free(event->answer);
}
fputs("]}", trace_file);
int failed = ferror(trace_file);
if (fclose(trace_file)) failed = 1;
free(trace_events);
if (failed) {
fputs("error: cannot write execution trace\n", stderr);
_Exit(1);
}
}
void code_trace_init(void) {
const char *path = getenv("CODE_TRACE_FILE");
if (!path || !(trace_file = fopen(path, "w")))
code_runtime_error("cannot open execution trace");
if (atexit(trace_write)) code_runtime_error("cannot register execution trace writer");
}
void code_trace_enter(void) { trace_depth++; }
void code_trace_leave(void) { trace_depth--; }
long long code_trace_begin(const char *target, const CodeValue *particle) {
trace_events = grow(trace_events, &trace_cap, trace_len + 1, sizeof(CodeTraceEvent));
CodeValue name = {0};
name.tag = CODE_STR;
name.str = (char *)target;
trace_events[trace_len] = (CodeTraceEvent){trace_depth, trace_json(&name), trace_json(particle), NULL};
return (long long)trace_len++;
}
void code_trace_finish(long long sequence, const CodeValue *answer) {
trace_events[sequence].answer = trace_json(answer);
}
#endif