#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>
#ifdef __GLIBC__
#include <malloc.h>
#endif
#endif
#include "code_abi.h"
#define LEN(v) code_list_len(v)
#define ITEMS(v) code_list_items(v)
#define KEYS(v) code_list_keys(v)
#define CODE_MOST_LIVE 64
_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 text_length;
} CodeHeader;
typedef struct {
long long shape;
size_t mask;
long long positions[];
} ObjectIndex;
static CodeHeader *header_of(const void *payload);
static ObjectIndex *object_index(const CodeValue *v) {
if (!v->heap) return NULL;
size_t word = (size_t)header_of(v->list)->text_length;
return (word & 1) ? (ObjectIndex *)(word & ~(size_t)1) : NULL;
}
static long long object_shape(const CodeValue *v) {
ObjectIndex *index = object_index(v);
return index ? index->shape : header_of(v->list)->text_length;
}
#define SHAPE_OF(v) object_shape(v)
static void drop_object_index(const CodeValue *v);
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;
h->text_length = 0;
code_blocks_add(1);
return (char *)h + sizeof(CodeHeader);
}
static CodeHeader *header_of(const void *payload) {
return (CodeHeader *)((char *)payload - sizeof(CodeHeader));
}
static void drop_object_index(const CodeValue *v) {
ObjectIndex *index = object_index(v);
if (index) {
header_of(v->list)->text_length = index->shape;
free(index);
code_blocks_add(-1);
}
}
static void *heap_block(const CodeValue *v) {
switch (v->tag) {
case CODE_STR:
return (void *)v->str;
case CODE_ARRAY:
case CODE_OBJECT:
return v->list;
default:
return NULL;
}
}
static CodeList *list_alloc(long long len, size_t extra) {
CodeList *list = heap_alloc(sizeof(CodeList) + (size_t)len * CODE_VALUE_SLOT_SIZE + extra);
list->len = len;
list->keys = NULL;
return list;
}
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 < LEN(¤t); i++) {
const CodeValue *child = slot_at(ITEMS(¤t), i);
if (child->heap && --header_of(heap_block(child))->rc == 0) {
dead = grow(dead, &dead_cap, len + 1, sizeof(CodeValue));
dead[len++] = *child;
}
}
}
if (current.tag == CODE_OBJECT) drop_object_index(¤t);
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) {
CodeList *list = NULL;
if (len > 0) {
list = list_alloc(len, 0);
void *buf = list + 1;
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->list = list;
}
#define KEY_HASH_BYTES 4
static unsigned int key_hash(const char *key) {
unsigned int hash = 2166136261u;
for (const unsigned char *c = (const unsigned char *)key; *c; c++) {
hash = (hash ^ *c) * 16777619u;
}
return hash;
}
static unsigned int stored_hash(const char *key) {
unsigned int hash;
memcpy(&hash, key - KEY_HASH_BYTES, sizeof hash);
return hash;
}
static long long scan_field_position(const CodeValue *obj, const char *key, unsigned int hash) {
long long len = LEN(obj);
const char **keys = KEYS(obj);
for (long long i = 0; i < len; i++) {
if (stored_hash(keys[i]) == hash && strcmp(keys[i], key) == 0) {
return i;
}
}
return -1;
}
static long long object_index_threshold = 0;
static int object_index_check;
static long long field_position(const CodeValue *obj, const char *key, unsigned int hash) {
if (LEN(obj) <= 8 || !obj->heap) return scan_field_position(obj, key, hash);
if (!object_index_threshold) {
const char *setting = getenv("CODE_OBJECT_INDEX_THRESHOLD");
object_index_threshold = setting ? strtol(setting, NULL, 10) : 16;
if (object_index_threshold < 1) object_index_threshold = 16;
object_index_check = getenv("CODE_OBJECT_INDEX_CHECK") != NULL;
}
if (LEN(obj) <= object_index_threshold) return scan_field_position(obj, key, hash);
ObjectIndex *index = object_index(obj);
const char **keys = KEYS(obj);
if (!index) {
size_t buckets = 32;
while (buckets < (size_t)LEN(obj) * 2) buckets *= 2;
index = calloc(1, sizeof *index + buckets * sizeof(long long));
if (!index) code_runtime_error("out of memory");
code_blocks_add(1);
index->shape = SHAPE_OF(obj);
index->mask = buckets - 1;
for (long long i = 0; i < LEN(obj); i++) {
size_t bucket = stored_hash(keys[i]) & index->mask;
while (index->positions[bucket]) {
long long at = index->positions[bucket] - 1;
if (strcmp(keys[at], keys[i]) == 0) break;
bucket = (bucket + 1) & index->mask;
}
if (!index->positions[bucket]) index->positions[bucket] = i + 1;
}
header_of(obj->list)->text_length = (long long)((size_t)index | 1);
}
size_t bucket = hash & index->mask;
long long found = -1;
while (index->positions[bucket]) {
long long at = index->positions[bucket] - 1;
if (stored_hash(keys[at]) == hash && strcmp(keys[at], key) == 0) {
found = at;
break;
}
bucket = (bucket + 1) & index->mask;
}
if (object_index_check && found != scan_field_position(obj, key, hash)) __builtin_trap();
return found;
}
static const CodeValue *own_field(const CodeValue *obj, const char *key) {
long long at = field_position(obj, key, key_hash(key));
return at < 0 ? NULL : slot_at(ITEMS(obj), at);
}
static const CodeValue *own_field_of_key(const CodeValue *obj, const char *key) {
long long at = field_position(obj, key, stored_hash(key));
return at < 0 ? NULL : slot_at(ITEMS(obj), at);
}
static const char *copy_key(char **chars, const char *key) {
const char *text = key ? key : "";
size_t n = strlen(text) + 1;
unsigned int hash = key_hash(text);
memcpy(*chars, &hash, KEY_HASH_BYTES);
memcpy(*chars + KEY_HASH_BYTES, text, n);
const char *placed = *chars + KEY_HASH_BYTES;
*chars += KEY_HASH_BYTES + n;
return placed;
}
static const char *copy_own_key(char **chars, const char *key) {
size_t n = KEY_HASH_BYTES + strlen(key) + 1;
memcpy(*chars, key - KEY_HASH_BYTES, n);
const char *placed = *chars + KEY_HASH_BYTES;
*chars += n;
return placed;
}
static void build_object(CodeValue *out, const char **keys, void *values, long long len,
int keys_hashed);
void code_object(CodeValue *out, const char **keys, void *values, long long len) {
build_object(out, keys, values, len, 0);
}
void code_object_literal(CodeValue *out, const char **keys, void *values, long long len) {
build_object(out, keys, values, len, 1);
if (len > 0) {
header_of(out->list)->text_length = (long long)(size_t)keys;
}
}
void code_object_template(CodeValue *out, const char **shape, void *values, long long len,
const unsigned char *data, long long bytes) {
CodeList *list = NULL;
if (len > 0) {
size_t offsets = (size_t)len * sizeof(unsigned int);
size_t chars_bytes = (size_t)bytes - offsets;
list = list_alloc(len, (size_t)len * sizeof(const char *) + chars_bytes);
void *items = list + 1;
list->keys = (const char **)((char *)items + (size_t)len * CODE_VALUE_SLOT_SIZE);
char *chars = (char *)(list->keys + len);
memcpy(chars, data + offsets, chars_bytes);
for (long long i = 0; i < len; i++) {
unsigned int offset;
memcpy(&offset, data + (size_t)i * sizeof offset, sizeof offset);
list->keys[i] = chars + offset;
CodeValue *src = slot_at(values, i);
*slot_at(items, i) = *src;
memset(src, 0, sizeof *src);
}
header_of(list)->text_length = (long long)(size_t)shape;
}
code_release(out);
out->tag = CODE_OBJECT;
out->heap = len > 0;
out->list = list;
}
static void build_object(CodeValue *out, const char **keys, void *values, long long len,
int keys_hashed) {
CodeList *list = NULL;
if (len > 0) {
size_t keys_bytes = (size_t)len * sizeof(const char *);
size_t chars_bytes = 0;
for (long long i = 0; i < len; i++) {
chars_bytes += KEY_HASH_BYTES + (keys[i] ? strlen(keys[i]) : 0) + 1;
}
list = list_alloc(len, keys_bytes + chars_bytes);
void *value_buf = list + 1;
const char **key_buf = (const char **)((char *)value_buf + (size_t)len * CODE_VALUE_SLOT_SIZE);
list->keys = key_buf;
char *chars = (char *)key_buf + keys_bytes;
for (long long i = 0; i < len; i++) {
key_buf[i] = keys_hashed ? copy_own_key(&chars, keys[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->list = list;
}
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) {
CodeValue value = *src;
code_retain(&value);
code_release(out);
*out = value;
}
void code_move(CodeValue *out, CodeValue *src) {
if (out == src) {
return;
}
code_release(out);
*out = *src;
memset(src, 0, sizeof *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 < LEN(obj); i++) {
if (strcmp(KEYS(obj)[i], field) == 0) {
code_copy(out, slot_at(ITEMS(obj), 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)LEN(value));
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)LEN(value);
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 *)ITEMS(value) + (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 < LEN(arr)) {
code_copy(out, slot_at(ITEMS(arr), i));
return;
}
}
code_null(out);
return;
}
if (arr->tag == CODE_OBJECT) {
const CodeValue *found = index->tag == CODE_STR ? own_field(arr, index->str) : NULL;
if (found) {
code_copy(out, found);
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) {
long long len = LEN(obj);
const char **keys = KEYS(obj);
for (long long i = 0; i < len; i++) {
if (strcmp(keys[i], key) == 0) {
return slot_at(ITEMS(obj), i);
}
}
return NULL;
}
static const CodeValue no_value = { CODE_NULL, 0, { 0 } };
const CodeValue *code_field_ref(const CodeValue *obj, const char *field, unsigned int hash,
long long *guess) {
if (obj->tag != CODE_OBJECT) {
CodeValue unused;
memset(&unused, 0, sizeof unused);
code_field(&unused, obj, field);
return &no_value;
}
long long at = *guess;
if (at < LEN(obj) && stored_hash(KEYS(obj)[at]) == hash && strcmp(KEYS(obj)[at], field) == 0) {
return slot_at(ITEMS(obj), at);
}
at = field_position(obj, field, hash);
if (at < 0) {
return &no_value;
}
*guess = at;
return slot_at(ITEMS(obj), at);
}
const CodeValue *code_index_ref(CodeValue *scratch, 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 < LEN(arr)) {
return slot_at(ITEMS(arr), i);
}
}
return &no_value;
}
if (arr->tag == CODE_OBJECT) {
const CodeValue *found = index->tag == CODE_STR ? own_field(arr, index->str) : NULL;
return found ? found : &no_value;
}
code_index(scratch, arr, index);
return scratch;
}
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;
}
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 *instance;
void (*instance_dispatch)(void *instance, CodeValue *out, const CodeValue *particle);
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;
}
#define STATELESS_EXPORT "code_module_stateless"
#define INSTANCE_EXPORT "code_module_instance_new"
enum { MARKED_STATELESS = 1, MARKED_INSTANCES = 2 };
static int module_image(const char *path, int *marked) {
*marked = 0;
#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;
}
enum {
CARRY = sizeof INSTANCE_EXPORT > sizeof STATELESS_EXPORT ? sizeof INSTANCE_EXPORT - 1
: sizeof STATELESS_EXPORT - 1
};
char buf[CARRY + 65536];
size_t carried = 0;
for (;;) {
ssize_t n = read(src, buf + carried, sizeof buf - carried);
if (n == 0) {
break;
}
if (n < 0 || write(image, buf + carried, (size_t)n) != n) {
close(src);
close(image);
return -1;
}
size_t have = carried + (size_t)n;
if (memmem(buf, have, STATELESS_EXPORT, sizeof STATELESS_EXPORT - 1)) {
*marked |= MARKED_STATELESS;
}
if (memmem(buf, have, INSTANCE_EXPORT, sizeof INSTANCE_EXPORT - 1)) {
*marked |= MARKED_INSTANCES;
}
carried = have < CARRY ? have : CARRY;
memmove(buf, buf + have - carried, carried);
}
close(src);
return image;
#else
(void)path;
return -1;
#endif
}
#ifndef CODE_WASM
static int has_no_life_of_its_own(void *lib) {
return !dlsym(lib, "code_module_set_inbound") && !dlsym(lib, "code_module_serving")
&& !dlsym(lib, "code_module_release") && !dlsym(lib, "code_module_drain");
}
static int shares_one_instance(void *lib) {
int (*stateless)(void) = (int (*)(void))dlsym(lib, STATELESS_EXPORT);
return stateless && stateless() && has_no_life_of_its_own(lib);
}
static int offers_instances(void *lib) {
int pushes = dlsym(lib, "code_module_set_inbound") != NULL;
return dlsym(lib, INSTANCE_EXPORT) && dlsym(lib, "code_module_instance_dispatch")
&& (!pushes || dlsym(lib, "code_module_instance_set_inbound"))
&& !dlsym(lib, "code_module_serving") && !dlsym(lib, "code_module_release")
&& !dlsym(lib, "code_module_drain");
}
#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 marked = 0;
int image = module_image(path, &marked);
void *handle = NULL;
if (marked) {
void *shared = dlopen(path, RTLD_NOW);
int fits = shared && (((marked & MARKED_INSTANCES) && offers_instances(shared))
|| ((marked & MARKED_STATELESS) && shares_one_instance(shared)));
if (fits) {
handle = shared;
if (image >= 0) {
close(image);
image = -1;
}
} else if (shared) {
dlclose(shared);
}
}
if (!handle && 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");
if (image < 0 && offers_instances(handle)) {
void *(*make)(void) = (void *(*)(void))dlsym(handle, INSTANCE_EXPORT);
nh->instance = make();
nh->instance_dispatch = (void (*)(void *, CodeValue *, const CodeValue *))dlsym(
handle, "code_module_instance_dispatch");
if (!nh->instance) {
snprintf(err, errlen, "native module '%s' could not make an instance", path);
free(nh);
return NULL;
}
}
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;
void (*instance_set_inbound)(void *, void *, CodeEmitFn) =
nh->instance ? (void (*)(void *, void *, CodeEmitFn))dlsym(
handle, "code_module_instance_set_inbound")
: NULL;
if (instance_set_inbound) {
instance_set_inbound(nh->instance, nh, code_emit_inbound);
} else 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 = LEN(from) > 0 ? calloc((size_t)LEN(from), CODE_VALUE_SLOT_SIZE) : NULL;
for (long long i = 0; i < LEN(from); i++) {
code_native_copy_in(slot_at(slots, i), slot_at(ITEMS(from), i));
}
code_array(out, slots, LEN(from));
for (long long i = 0; i < LEN(from); i++) {
code_release(slot_at(slots, i));
}
free(slots);
return;
}
case CODE_OBJECT: {
const char **keys = LEN(from) > 0 ? malloc((size_t)LEN(from) * sizeof(const char *)) : NULL;
void *slots = LEN(from) > 0 ? calloc((size_t)LEN(from), CODE_VALUE_SLOT_SIZE) : NULL;
for (long long i = 0; i < LEN(from); i++) {
keys[i] = KEYS(from)[i];
code_native_copy_in(slot_at(slots, i), slot_at(ITEMS(from), i));
}
code_object(out, keys, slots, LEN(from));
for (long long i = 0; i < LEN(from); 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};
if (nh->instance_dispatch) {
nh->instance_dispatch(nh->instance, &result, particle);
} else {
nh->dispatch(&result, particle);
}
code_native_copy_in(out, &result);
nh->release(&result);
}
int code_most_live(void) { return CODE_MOST_LIVE; }
#ifndef CODE_WASM
static int flows_hosted;
long long code_flow_begin(const char *target, const CodeValue *particle, const char *root_from);
void code_flow_finish(long long id, const CodeValue *answer);
#endif
void code_dispatch_copied(void (*chain)(CodeValue *out, const CodeValue *particle), CodeValue *out,
const CodeValue *particle) {
CodeValue own = {0};
code_native_copy_in(&own, particle);
#ifndef CODE_WASM
long long flow = flows_hosted ? code_flow_begin("this", &own, "host") : -1;
#endif
chain(out, &own);
#ifndef CODE_WASM
code_flow_finish(flow, out);
#endif
code_release(&own);
}
typedef struct HostedGuest HostedGuest;
typedef struct {
NativeHandle *handle;
char *path;
long long guest;
const char *name;
void (*flows_take)(CodeValue *out);
} 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;
}
#ifndef CODE_WASM
#define FLOW_MOST_STEPS 500
typedef struct {
long long parent;
char *target, *class_name;
char *program;
double at, ms;
int ok;
} FlowStep;
static int flows_on, flows_delivering, flow_running;
static FlowStep flow_steps[FLOW_MOST_STEPS];
static long long flow_open[FLOW_MOST_STEPS];
static long long flow_len, flow_depth, flow_dropped;
static double flow_started, flow_clock;
static char *flow_from;
static CodeValue flow_particle;
static const char **flow_handled;
static size_t flow_handled_len, flow_handled_cap;
static CodeValue *flow_outbox;
static size_t flow_outbox_len, flow_outbox_cap;
static CodeValue *flow_pending;
static size_t flow_pending_len, flow_pending_cap;
static double flow_mono_ms(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (double)t.tv_sec * 1000.0 + (double)t.tv_nsec / 1e6;
}
static double flow_unix_ms(void) {
struct timespec t;
clock_gettime(CLOCK_REALTIME, &t);
return (double)t.tv_sec * 1000.0 + (double)t.tv_nsec / 1e6;
}
static const char *flow_class(const CodeValue *particle) {
if (particle->tag != CODE_OBJECT) return "";
const CodeValue *c = find_field(particle, "_class");
return c && c->tag == CODE_STR && c->str ? c->str : "";
}
static double flow_round(double ms) { return floor(ms * 1000.0 + 0.5) / 1000.0; }
void code_flow_on(void) { flows_on = 1; }
void code_flow_handles(const char *class_name) {
flow_handled = grow(flow_handled, &flow_handled_cap, flow_handled_len + 1, sizeof(const char *));
flow_handled[flow_handled_len++] = class_name;
}
static int flow_is_handled(const CodeValue *particle) {
const char *class_name = flow_class(particle);
for (size_t i = 0; i < flow_handled_len; i++) {
if (strcmp(flow_handled[i], class_name) == 0) return 1;
}
return 0;
}
long long code_flow_begin(const char *target, const CodeValue *particle, const char *root_from) {
if (!flows_on || flows_delivering) return -1;
if (!flow_running) {
if (!root_from) return -1;
flow_running = 1;
flow_started = flow_unix_ms();
flow_clock = flow_mono_ms();
flow_from = strdup(root_from);
memset(&flow_particle, 0, sizeof flow_particle);
code_copy(&flow_particle, particle);
flow_len = flow_depth = flow_dropped = 0;
}
if (flow_len >= FLOW_MOST_STEPS) {
flow_dropped++;
return -2;
}
long long id = flow_len++;
flow_steps[id] = (FlowStep){
flow_depth ? flow_open[flow_depth - 1] : -1,
strdup(target),
strdup(flow_class(particle)),
NULL,
flow_mono_ms() - flow_clock,
0.0,
1,
};
flow_open[flow_depth++] = id;
return id;
}
long long code_flow_begin_push(const char *from, const CodeValue *particle) {
if (!flows_on || flow_running || !flow_is_handled(particle)) return -1;
return code_flow_begin("this", particle, from);
}
static void flow_hand_over(CodeValue trace) {
if (flows_hosted) {
flow_outbox = grow(flow_outbox, &flow_outbox_cap, flow_outbox_len + 1, sizeof(CodeValue));
flow_outbox[flow_outbox_len++] = trace;
return;
}
if (code_program_dispatch) {
CodeValue answer = {0};
flows_delivering = 1;
code_program_dispatch(&answer, &trace);
flows_delivering = 0;
code_release(&answer);
}
code_release(&trace);
}
static void flow_deliver_pending(void) {
size_t next = 0;
while (!flow_running && next < flow_pending_len) {
flow_hand_over(flow_pending[next++]);
}
if (next == flow_pending_len) {
flow_pending_len = 0;
} else {
memmove(flow_pending, flow_pending + next, (flow_pending_len - next) * sizeof(CodeValue));
flow_pending_len -= next;
}
}
static const CodeValue *flow_field(const CodeValue *v, const char *name) {
return v && v->tag == CODE_OBJECT ? find_field(v, name) : NULL;
}
static double flow_number(const CodeValue *v) { return v && v->tag == CODE_NUMBER ? v->number : 0.0; }
static const char *flow_text(const CodeValue *v) {
return v && v->tag == CODE_STR && v->str ? v->str : "";
}
static void flow_stamped(CodeValue *out, const CodeValue *obj, const char *program) {
if (obj->tag != CODE_OBJECT) {
code_copy(out, obj);
return;
}
char *values = calloc((size_t)(LEN(obj) ? LEN(obj) : 1), CODE_VALUE_SLOT_SIZE);
if (!values) code_runtime_error("out of memory");
for (long long i = 0; i < LEN(obj); i++) {
const CodeValue *v = slot_at(ITEMS(obj), i);
if (strcmp(KEYS(obj)[i], "program") == 0 && v->tag == CODE_NULL)
code_str_owned(slot_at(values, i), program);
else
code_copy(slot_at(values, i), v);
}
code_object(out, KEYS(obj), values, LEN(obj));
for (long long i = 0; i < LEN(obj); i++) code_release(slot_at(values, i));
free(values);
}
static void flow_splice(long long parent, const CodeValue *trace, const char *program) {
const CodeValue *steps = flow_field(trace, "steps");
double offset = flow_steps[parent].at;
long long base = flow_len;
for (long long i = 0; steps && steps->tag == CODE_ARRAY && i < LEN(steps); i++) {
const CodeValue *step = slot_at(ITEMS(steps), i);
if (flow_len >= FLOW_MOST_STEPS) {
flow_dropped++;
continue;
}
const CodeValue *own_parent = flow_field(step, "parent");
const CodeValue *own_program = flow_field(step, "program");
const CodeValue *ok = flow_field(step, "ok");
flow_steps[flow_len++] = (FlowStep){
own_parent && own_parent->tag == CODE_NUMBER ? base + (long long)own_parent->number : parent,
strdup(flow_text(flow_field(step, "target"))),
strdup(flow_text(flow_field(step, "class"))),
strdup(own_program && own_program->tag == CODE_STR ? flow_text(own_program) : program),
offset + flow_number(flow_field(step, "at")),
flow_number(flow_field(step, "ms")),
ok && ok->tag == CODE_BOOL && ok->boolean,
};
}
flow_dropped += (long long)flow_number(flow_field(trace, "dropped"));
}
static void flow_absorb(const CodeValue *traces, const char *program, int called) {
for (long long i = 0; traces->tag == CODE_ARRAY && i < LEN(traces); i++) {
const CodeValue *trace = slot_at(ITEMS(traces), i);
int from_host = strcmp(flow_text(flow_field(flow_field(trace, "root"), "from")), "host") == 0;
if (from_host && called && flow_running && flow_depth > 0) {
flow_splice(flow_open[flow_depth - 1], trace, program);
continue;
}
if (from_host) continue;
const char *keys[7];
char *fields = calloc((size_t)(LEN(trace) ? LEN(trace) : 1), CODE_VALUE_SLOT_SIZE);
if (!fields) code_runtime_error("out of memory");
for (long long f = 0; f < LEN(trace) && f < 7; f++) {
const CodeValue *v = slot_at(ITEMS(trace), f);
keys[f] = KEYS(trace)[f];
if (strcmp(KEYS(trace)[f], "root") == 0) {
flow_stamped(slot_at(fields, f), v, program);
} else if (strcmp(KEYS(trace)[f], "steps") == 0 && v->tag == CODE_ARRAY) {
char *items = calloc((size_t)(LEN(v) ? LEN(v) : 1), CODE_VALUE_SLOT_SIZE);
if (!items) code_runtime_error("out of memory");
for (long long s = 0; s < LEN(v); s++)
flow_stamped(slot_at(items, s), slot_at(ITEMS(v), s), program);
code_array(slot_at(fields, f), items, LEN(v));
for (long long s = 0; s < LEN(v); s++) code_release(slot_at(items, s));
free(items);
} else {
code_copy(slot_at(fields, f), v);
}
}
CodeValue stamped = {0};
long long n = LEN(trace) < 7 ? LEN(trace) : 7;
code_object(&stamped, keys, fields, n);
for (long long f = 0; f < n; f++) code_release(slot_at(fields, f));
free(fields);
flow_pending = grow(flow_pending, &flow_pending_cap, flow_pending_len + 1, sizeof(CodeValue));
flow_pending[flow_pending_len++] = stamped;
}
}
static void flow_collect(void (*take)(CodeValue *), void (*release)(CodeValue *),
const char *program, int called) {
if (!flows_on || !take) return;
CodeValue theirs = {0};
take(&theirs);
CodeValue ours = {0};
code_native_copy_in(&ours, &theirs);
release(&theirs);
if (ours.tag == CODE_ARRAY && LEN(&ours) > 0) flow_absorb(&ours, program, called);
code_release(&ours);
}
static void flow_deliver(void) {
long long n = flow_len;
char *items = calloc((size_t)(n ? n : 1), CODE_VALUE_SLOT_SIZE);
if (!items) code_runtime_error("out of memory");
for (long long i = 0; i < n; i++) {
FlowStep *step = &flow_steps[i];
const char *keys[8] = {"id", "parent", "target", "class", "at", "ms", "ok", "program"};
char fields[8 * CODE_VALUE_SLOT_SIZE] = {0};
code_number(slot_at(fields, 0), (double)i);
if (step->parent < 0) code_null(slot_at(fields, 1));
else code_number(slot_at(fields, 1), (double)step->parent);
code_str_owned(slot_at(fields, 2), step->target);
code_str_owned(slot_at(fields, 3), step->class_name);
code_number(slot_at(fields, 4), flow_round(step->at));
code_number(slot_at(fields, 5), flow_round(step->ms));
code_bool(slot_at(fields, 6), step->ok);
if (step->program) code_str_owned(slot_at(fields, 7), step->program);
else code_null(slot_at(fields, 7));
code_object(slot_at(items, i), keys, fields, 8);
for (int f = 0; f < 8; f++) code_release(slot_at(fields, f));
free(step->target);
free(step->class_name);
free(step->program);
}
char root_fields[4 * CODE_VALUE_SLOT_SIZE] = {0};
const char *root_keys[4] = {"from", "class", "particle", "program"};
code_str_owned(slot_at(root_fields, 0), flow_from);
code_str_owned(slot_at(root_fields, 1), flow_class(&flow_particle));
code_copy(slot_at(root_fields, 2), &flow_particle);
code_null(slot_at(root_fields, 3));
const char *keys[7] = {"_class", "root", "started", "ms", "ok", "steps", "dropped"};
char fields[7 * CODE_VALUE_SLOT_SIZE] = {0};
code_str(slot_at(fields, 0), "Trace");
code_object(slot_at(fields, 1), root_keys, root_fields, 4);
code_number(slot_at(fields, 2), floor(flow_started));
code_number(slot_at(fields, 3), n ? flow_round(flow_steps[0].ms) : 0.0);
code_bool(slot_at(fields, 4), n ? flow_steps[0].ok : 1);
code_array(slot_at(fields, 5), items, n);
code_number(slot_at(fields, 6), (double)flow_dropped);
CodeValue trace = {0};
code_object(&trace, keys, fields, 7);
for (int f = 0; f < 7; f++) code_release(slot_at(fields, f));
for (int f = 0; f < 4; f++) code_release(slot_at(root_fields, f));
for (long long i = 0; i < n; i++) code_release(slot_at(items, i));
free(items);
free(flow_from);
flow_from = NULL;
code_release(&flow_particle);
memset(&flow_particle, 0, sizeof flow_particle);
flow_running = 0;
flow_len = flow_depth = flow_dropped = 0;
flow_hand_over(trace);
flow_deliver_pending();
}
void code_module_flows(void) {
flows_on = 1;
flows_hosted = 1;
}
void code_module_flows_take(CodeValue *out) {
char *items = calloc(flow_outbox_len ? flow_outbox_len : 1, CODE_VALUE_SLOT_SIZE);
if (!items) code_runtime_error("out of memory");
for (size_t i = 0; i < flow_outbox_len; i++) *slot_at(items, (long long)i) = flow_outbox[i];
memset(out, 0, sizeof *out);
code_array(out, items, (long long)flow_outbox_len);
for (size_t i = 0; i < flow_outbox_len; i++) code_release(slot_at(items, (long long)i));
free(items);
flow_outbox_len = 0;
}
void code_flow_finish(long long id, const CodeValue *answer) {
if (id < 0 || !flow_running) return;
double now = flow_mono_ms() - flow_clock;
while (flow_depth > 0) {
long long top = flow_open[--flow_depth];
FlowStep *step = &flow_steps[top];
step->ms = now - step->at;
if (top == id) {
step->ok = strcmp(flow_class(answer), "Exception") != 0;
break;
}
step->ok = 0;
}
if (id == 0) flow_deliver();
}
#endif
#define CODE_EVENT_MIN 65536
static char *code_event_buf = NULL;
static long long code_event_cap = 0;
char *code_event_text_reserve(long long n) {
if (n < CODE_EVENT_MIN) {
n = CODE_EVENT_MIN;
}
if (n > code_event_cap) {
char *grown = realloc(code_event_buf, (size_t)n + 1);
if (grown) {
code_event_buf = grown;
code_event_cap = n;
}
}
return code_event_buf;
}
char *code_event_text(void) { return code_event_text_reserve(0); }
long long code_event_text_capacity(void) {
code_event_text_reserve(0);
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 (!code_event_buf) {
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 < LEN(&particle); i++) {
if (strcmp(KEYS(&particle)[i], "_class") == 0 &&
slot_at(ITEMS(&particle), 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;
}
static char *code_web_grow(char **buf, long long *cap, long long n) {
if (n < CODE_EVENT_MIN) {
n = CODE_EVENT_MIN;
}
if (n > *cap) {
char *grown = realloc(*buf, (size_t)n + 1);
if (grown) {
*buf = grown;
*cap = n;
}
}
return *buf;
}
static char *code_web_asked_buf = NULL;
static long long code_web_asked_cap = 0;
static char *code_web_answer_buf = NULL;
static long long code_web_answer_cap = 0;
long long code_web_asked_write(const CodeValue *v) {
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);
char *buf = code_web_grow(&code_web_asked_buf, &code_web_asked_cap, len);
if (!buf || len > code_web_asked_cap) {
code_release(&text);
return -1;
}
memcpy(buf, text.str, (size_t)len + 1);
code_release(&text);
return len;
}
char *code_web_asked_text(void) { return code_web_asked_buf; }
char *code_web_answer_reserve(long long n) {
return code_web_grow(&code_web_answer_buf, &code_web_answer_cap, n);
}
long long code_web_answer_capacity(void) {
code_web_grow(&code_web_answer_buf, &code_web_answer_cap, 0);
return code_web_answer_cap;
}
char *code_web_answer_text(void) { return code_web_answer_buf; }
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, const char *name) {
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;
runtime_modules[row].name = name;
runtime_modules[row].flows_take = NULL;
#ifndef CODE_WASM
if (flows_on) {
void (*record)(void) = (void (*)(void))dlsym(nh->lib, "code_module_flows");
if (record) {
record();
runtime_modules[row].flows_take =
(void (*)(CodeValue *))dlsym(nh->lib, "code_module_flows_take");
}
}
#endif
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();
#ifndef CODE_WASM
if (runtime_modules[i].flows_take) {
flow_collect(runtime_modules[i].flows_take, nh->release, runtime_modules[i].path, 0);
}
#endif
}
}
#ifndef CODE_WASM
flow_deliver_pending();
#endif
}
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};
#ifndef CODE_WASM
long long flow = code_flow_begin_push(
runtime_modules[i].name ? runtime_modules[i].name : "", &particle);
#endif
if (code_program_dispatch) {
code_program_dispatch(&answer, &particle);
} else {
code_null(&answer);
}
#ifndef CODE_WASM
code_flow_finish(flow, &answer);
#endif
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);
#ifndef CODE_WASM
for (long long i = 0; i < runtime_module_count; i++) {
if (runtime_modules[i].handle == nh && runtime_modules[i].flows_take) {
flow_collect(runtime_modules[i].flows_take, nh->release, runtime_modules[i].path, 1);
}
}
#endif
}
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 LEN(v);
}
void code_iter_at(CodeValue *out, const CodeValue *arr, long long i) {
code_copy(out, slot_at(ITEMS(arr), i));
}
void code_iter_key(CodeValue *out, const CodeValue *v, long long i) {
if (v->tag == CODE_OBJECT) {
code_str_owned(out, KEYS(v)[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 < LEN(v); i++) {
if (strcmp(KEYS(v)[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 < LEN(v); i++) {
if (strcmp(KEYS(v)[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);
}
#define TEXT_LENGTH_MASK ((1ULL << 58) - 1)
static void *room_for(void *payload, size_t bytes);
static size_t block_room(void *block);
static void remember_text(char *buf, size_t len, size_t cap) {
unsigned long long exponent = 0;
while (cap) { exponent++; cap >>= 1; }
header_of(buf)->text_length = (long long)((exponent << 58) | (len + 1));
}
static size_t text_capacity(const CodeValue *v) {
unsigned long long word = (unsigned long long)header_of(v->str)->text_length;
unsigned int exponent = (unsigned int)(word >> 58);
if (!exponent) {
size_t cap = block_room(header_of(v->str));
cap = cap > sizeof(CodeHeader) ? cap - sizeof(CodeHeader) : 0;
size_t len = word ? (size_t)(word - 1) : strlen(v->str);
remember_text((char *)v->str, len, cap);
word = (unsigned long long)header_of(v->str)->text_length;
exponent = (unsigned int)(word >> 58);
}
return exponent ? (size_t)(1ULL << (exponent - 1)) : 0;
}
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 = t->buf ? room_for(t->buf, next) : heap_alloc(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 int number_shortest(double d, char *digits, unsigned int cap) {
#ifdef CODE_WASM
int n = code_host_number_shortest(d, digits, cap);
return (n >= 1 && n <= 17) ? n : 0;
#else
(void)d;
(void)digits;
(void)cap;
return 0;
#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 != d) { text_push_str(t, "NaN"); return; }
if (d > 1.7976931348623157e308) { text_push_str(t, "inf"); return; }
if (d < -1.7976931348623157e308) { text_push_str(t, "-inf"); return; }
if (d >= -9007199254740992.0 && d <= 9007199254740992.0 && d == (double)(long long)d) {
if (d == 0.0 && 1.0 / d < 0.0) {
text_push_str(t, "-0");
return;
}
unsigned long long magnitude = (unsigned long long)(d < 0 ? -d : d);
char *end = tmp + sizeof tmp;
char *at = end;
do {
*--at = (char)('0' + magnitude % 10);
magnitude /= 10;
} while (magnitude);
if (d < 0) *--at = '-';
text_push(t, at, (size_t)(end - at));
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;
char shortest[24];
int nshort = number_shortest(d, shortest, sizeof shortest);
for (int len = nshort > 0 ? nshort : 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 (len == nshort) {
size_t same = 0;
while (same < n && m[same] == shortest[same]) {
same++;
}
if (same == n) {
break;
}
}
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;
static void render_text(TextBuf *t, const CodeValue *v) {
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 = LEN(current) - 1; i >= 0; i--) {
steps = grow(steps, &steps_cap, len + 2, sizeof(TextStep));
steps[len++] = (TextStep){slot_at(ITEMS(current), 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 = LEN(current) - 1; i >= 0; i--) {
steps = grow(steps, &steps_cap, len + 3, sizeof(TextStep));
steps[len++] = (TextStep){slot_at(ITEMS(current), i), NULL, 0};
steps[len++] = (TextStep){NULL, KEYS(current)[i], 1};
if (i > 0) {
steps[len++] = (TextStep){NULL, ",", 0};
}
}
break;
}
top_level = 0;
}
}
static void finish_text(CodeValue *out, TextBuf *t, int reused) {
text_push(t, "", 0);
t->buf[t->len] = '\0';
remember_text(t->buf, t->len, t->cap);
if (!reused) code_release(out);
out->tag = CODE_STR;
out->heap = 1;
out->str = t->buf;
}
void code_to_text(CodeValue *out, const CodeValue *v) {
TextBuf t = {NULL, 0, 0};
render_text(&t, v);
finish_text(out, &t, 0);
}
void code_text_parts(CodeValue *out, const CodeValue *const *parts, long long count) {
TextBuf t = {NULL, 0, 0};
for (long long i = 0; i < count; i++) render_text(&t, parts[i]);
finish_text(out, &t, 0);
}
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) ? LEN(a) : 1;
long long nb = (b->tag == CODE_ARRAY) ? LEN(b) : 1;
long long total = na + nb;
CodeList *list = NULL;
if (total > 0) {
list = list_alloc(total, 0);
void *buf = list + 1;
for (long long i = 0; i < na; i++) {
const CodeValue *src = (a->tag == CODE_ARRAY) ? slot_at(ITEMS(a), 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(ITEMS(b), i) : b;
code_retain(src);
*slot_at(buf, na + i) = *src;
}
}
code_release(out);
out->tag = CODE_ARRAY;
out->heap = total > 0;
out->list = list;
return;
}
if (a->tag == CODE_OBJECT && b->tag == CODE_OBJECT) {
long long total = LEN(a);
for (long long j = 0; j < LEN(b); j++) {
if (own_field_of_key(a, KEYS(b)[j]) == NULL) {
total++;
}
}
CodeList *list = NULL;
if (total > 0) {
size_t keys_bytes = (size_t)total * sizeof(const char *);
size_t chars_bytes = 0;
for (long long i = 0; i < LEN(a); i++) {
chars_bytes += KEY_HASH_BYTES + (KEYS(a)[i] ? strlen(KEYS(a)[i]) : 0) + 1;
}
for (long long j = 0; j < LEN(b); j++) {
if (own_field_of_key(a, KEYS(b)[j]) == NULL) {
chars_bytes += KEY_HASH_BYTES + (KEYS(b)[j] ? strlen(KEYS(b)[j]) : 0) + 1;
}
}
list = list_alloc(total, keys_bytes + chars_bytes);
void *value_buf = list + 1;
const char **key_buf =
(const char **)((char *)value_buf + (size_t)total * CODE_VALUE_SLOT_SIZE);
list->keys = key_buf;
char *chars = (char *)key_buf + keys_bytes;
long long n = 0;
for (long long i = 0; i < LEN(a); i++) {
const CodeValue *override_val = own_field_of_key(b, KEYS(a)[i]);
const CodeValue *src = override_val ? override_val : slot_at(ITEMS(a), i);
key_buf[n] = copy_own_key(&chars, KEYS(a)[i]);
code_retain(src);
*slot_at(value_buf, n) = *src;
n++;
}
for (long long j = 0; j < LEN(b); j++) {
if (own_field_of_key(a, KEYS(b)[j]) != NULL) {
continue;
}
key_buf[n] = copy_own_key(&chars, KEYS(b)[j]);
const CodeValue *src = slot_at(ITEMS(b), j);
code_retain(src);
*slot_at(value_buf, n) = *src;
n++;
}
}
long long shape = (total == LEN(a) && a->heap) ? SHAPE_OF(a) : 0;
code_release(out);
out->tag = CODE_OBJECT;
out->heap = total > 0;
out->list = list;
if (total > 0) {
header_of(out->list)->text_length = shape;
}
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_take_last(CodeValue *out, CodeValue *array) {
if (array->tag == CODE_ARRAY && array->heap && header_of(array->list)->rc == 1
&& LEN(array) > 0) {
code_move(out, slot_at(ITEMS(array), LEN(array) - 1));
return;
}
CodeValue length = {0};
code_length_of(&length, array);
if (code_failed) return;
CodeValue index = {0};
index.number = length.number - 1;
code_index(out, array, &index);
}
#ifdef CODE_WASM
static size_t block_room(void *block) {
return code_wasm_size_of(block);
}
#elif defined(__GLIBC__)
static size_t block_room(void *block) {
return malloc_usable_size(block);
}
#else
static size_t block_room(void *block) {
(void)block;
return 0;
}
#endif
static int held_alone(const CodeValue *v) {
return v->heap && header_of(heap_block(v))->rc == 1;
}
static void *room_for(void *payload, size_t bytes) {
CodeHeader *h = header_of(payload);
size_t want = sizeof(CodeHeader) + bytes;
size_t have = block_room(h);
if (have >= want) {
return payload;
}
size_t next = have * 2 > want ? have * 2 : want;
CodeHeader *bigger = realloc(h, next);
if (!bigger) {
code_runtime_error("out of memory");
}
return (char *)bigger + sizeof(CodeHeader);
}
static int append_in_place(CodeValue *dst, const CodeValue *b) {
long long nb = (b->tag == CODE_ARRAY) ? LEN(b) : 1;
if (nb == 0) {
return 1;
}
long long had = LEN(dst);
long long total = had + nb;
CodeList *list;
if (dst->heap) {
list = room_for(dst->list, sizeof(CodeList) + (size_t)total * CODE_VALUE_SLOT_SIZE);
} else {
long long room = total < 4 ? 4 : total;
list = list_alloc(room, 0);
}
for (long long i = 0; i < nb; i++) {
const CodeValue *src = (b->tag == CODE_ARRAY) ? slot_at(ITEMS(b), i) : b;
code_retain(src);
*slot_at(list + 1, had + i) = *src;
}
list->len = total;
dst->list = list;
dst->heap = 1;
return 1;
}
static size_t text_length(const CodeValue *v) {
if (!v->heap) {
return strlen(v->str);
}
CodeHeader *h = header_of(v->str);
unsigned long long word = (unsigned long long)h->text_length;
if (!(word & TEXT_LENGTH_MASK)) {
word |= strlen(v->str) + 1;
h->text_length = (long long)word;
}
return (size_t)((word & TEXT_LENGTH_MASK) - 1);
}
static TextBuf append_buffer(CodeValue *dst) {
size_t len = text_length(dst);
if (dst->heap) return (TextBuf){(char *)dst->str, len, text_capacity(dst)};
size_t cap = len + 64;
char *buf = heap_alloc(cap);
memcpy(buf, dst->str, len);
return (TextBuf){buf, len, cap};
}
static void join_in_place(CodeValue *dst, const CodeValue *b) {
TextBuf t = append_buffer(dst);
text_push(&t, b->str, text_length(b));
finish_text(dst, &t, 1);
}
static int grow_object_in_place(CodeValue *dst, const CodeValue *b) {
long long extra = 0;
size_t extra_chars = 0;
long long local_positions[16];
long long *positions = LEN(b) <= 16 ? local_positions : malloc((size_t)LEN(b) * sizeof(long long));
if (!positions) code_runtime_error("out of memory");
for (long long j = 0; j < LEN(b); j++) {
for (long long k = 0; k < j; k++) {
if (strcmp(KEYS(b)[k], KEYS(b)[j]) == 0) {
if (positions != local_positions) free(positions);
return 0;
}
}
positions[j] = field_position(dst, KEYS(b)[j], stored_hash(KEYS(b)[j]));
if (positions[j] < 0) {
extra++;
extra_chars += KEY_HASH_BYTES + strlen(KEYS(b)[j]) + 1;
}
}
if (extra > 0) {
drop_object_index(dst);
CodeList *base = dst->list;
long long fits = ((char *)base->keys - (char *)(base + 1)) / CODE_VALUE_SLOT_SIZE;
const char *last = base->keys[base->len - 1];
char *used = (char *)last + strlen(last) + 1;
long long room = (long long)block_room(header_of(base)) - (long long)sizeof(CodeHeader)
- (used - (char *)base);
if (base->len + extra > fits || room < (long long)extra_chars) {
long long total = base->len + extra;
long long cap = total < 4 ? 4 : 2 * total;
size_t chars = (size_t)(used - (char *)(base->keys + fits));
size_t char_room = 2 * (chars + extra_chars) + 16;
CodeList *moved = heap_alloc(sizeof(CodeList)
+ (size_t)cap * (CODE_VALUE_SLOT_SIZE + sizeof(const char *)) + char_room);
void *items = moved + 1;
const char **keys = (const char **)((char *)items + (size_t)cap * CODE_VALUE_SLOT_SIZE);
char *cursor = (char *)(keys + cap);
for (long long i = 0; i < base->len; i++) {
keys[i] = copy_own_key(&cursor, base->keys[i]);
*slot_at(items, i) = *slot_at(base + 1, i);
}
moved->len = base->len;
moved->keys = keys;
free(header_of(base));
code_blocks_add(-1);
dst->list = moved;
}
}
long long n = LEN(dst);
char *cursor = NULL;
if (extra > 0) {
const char *last = KEYS(dst)[n - 1];
cursor = (char *)last + strlen(last) + 1;
}
for (long long j = 0; j < LEN(b); j++) {
const CodeValue *src = slot_at(ITEMS(b), j);
CodeValue *slot = positions[j] < 0 ? NULL : slot_at(ITEMS(dst), positions[j]);
code_retain(src);
if (slot) {
code_release(slot);
*slot = *src;
} else {
dst->list->keys[n] = copy_own_key(&cursor, KEYS(b)[j]);
*slot_at(ITEMS(dst), n) = *src;
n++;
}
}
if (extra > 0) {
header_of(dst->list)->text_length = 0;
}
dst->list->len = n;
if (positions != local_positions) free(positions);
return 1;
}
void code_add_into(CodeValue *dst, const CodeValue *b) {
if (b != dst) {
if (dst->tag == CODE_ARRAY && (held_alone(dst) || !dst->heap)) {
if (append_in_place(dst, b)) {
return;
}
}
if (dst->tag == CODE_STR && b->tag == CODE_STR && (held_alone(dst) || !dst->heap)) {
join_in_place(dst, b);
return;
}
if (dst->tag == CODE_OBJECT && b->tag == CODE_OBJECT && held_alone(dst)) {
if (grow_object_in_place(dst, b)) {
return;
}
}
}
code_add(dst, dst, b);
}
void code_add_take(CodeValue *dst, CodeValue *part) {
if (part != dst && dst->tag == CODE_ARRAY && part->tag != CODE_ARRAY
&& (held_alone(dst) || !dst->heap)) {
long long had = LEN(dst);
long long total = had + 1;
CodeList *list = dst->heap
? room_for(dst->list, sizeof(CodeList) + (size_t)total * CODE_VALUE_SLOT_SIZE)
: list_alloc(4, 0);
*slot_at(list + 1, had) = *part;
memset(part, 0, sizeof *part);
list->len = total;
dst->list = list;
dst->heap = 1;
return;
}
code_add_into(dst, part);
code_clear(part);
}
void code_append_text(CodeValue *dst, const CodeValue *const *parts, long long count) {
int can_append = dst->tag == CODE_STR && (!dst->heap || held_alone(dst));
for (long long i = 0; can_append && i < count; i++) {
if (parts[i] == dst || (parts[i]->tag == CODE_STR && parts[i]->str == dst->str))
can_append = 0;
}
if (can_append) {
TextBuf t = append_buffer(dst);
for (long long i = 0; i < count; i++) render_text(&t, parts[i]);
finish_text(dst, &t, 1);
} else {
CodeValue text = {0};
code_text_parts(&text, parts, count);
code_add_into(dst, &text);
code_release(&text);
}
}
void code_slice_into(CodeValue *dst, const CodeValue *from, const CodeValue *to) {
if (dst->tag == CODE_ARRAY && held_alone(dst) && from->tag == CODE_NUMBER
&& to->tag == CODE_NUMBER) {
double len = (double)LEN(dst);
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) {
for (long long i = 0; i < start; i++) {
code_release(slot_at(ITEMS(dst), i));
}
for (long long i = stop; i < LEN(dst); i++) {
code_release(slot_at(ITEMS(dst), i));
}
if (start > 0) {
memmove(ITEMS(dst), slot_at(ITEMS(dst), start),
(size_t)(stop - start) * CODE_VALUE_SLOT_SIZE);
}
dst->list->len = stop - start;
return;
}
CodeValue empty;
memset(&empty, 0, sizeof empty);
code_array(&empty, NULL, 0);
code_move(dst, &empty);
return;
}
CodeValue cut;
memset(&cut, 0, sizeof cut);
code_slice(&cut, dst, from, to);
if (code_failed) {
return;
}
code_move(dst, &cut);
}
static void own_level(CodeValue *v) {
void *block = v->heap ? heap_block(v) : NULL;
if (!block || header_of(block)->rc == 1) {
return;
}
CodeValue copy;
memset(©, 0, sizeof copy);
if (v->tag == CODE_ARRAY) {
code_array(©, ITEMS(v), LEN(v));
} else {
build_object(©, KEYS(v), ITEMS(v), LEN(v), 1);
header_of(copy.list)->text_length = SHAPE_OF(v);
}
code_release(v);
*v = copy;
}
void code_set_at(CodeValue *target, const CodeValue *const *keys, long long count,
const CodeValue *value, const long long *hashes, long long *guesses,
const void *const *shapes, const long long *slots) {
char msg[320];
CodeValue v = *value;
code_retain(&v);
CodeValue *current = target;
for (long long step = 0; step < count; step++) {
const CodeValue *key = keys[step];
if (current->tag == CODE_ARRAY) {
if (key->tag != CODE_NUMBER) {
snprintf(msg, sizeof msg,
"cannot set an array element by %s %s — an array's index is a number",
article_for(key), type_name(key));
goto failed;
}
long long at = (long long)key->number;
if (!((double)at == key->number && at >= 0 && at < LEN(current))) {
snprintf(msg, sizeof msg,
"cannot set that element — an index to set is a whole number below the "
"array's length, %lld",
LEN(current));
goto failed;
}
own_level(current);
current = slot_at(ITEMS(current), at);
} else if (current->tag == CODE_OBJECT) {
if (key->tag != CODE_STR) {
snprintf(msg, sizeof msg,
"cannot set an object field by %s %s — a field is named by a string",
article_for(key), type_name(key));
goto failed;
}
long long at;
if (shapes[step] && current->heap
&& SHAPE_OF(current) == (long long)(size_t)shapes[step]) {
at = slots[step];
} else {
unsigned int hash = (hashes[step] >> 32) ? (unsigned int)hashes[step]
: key_hash(key->str);
at = guesses[step];
if (!(at < LEN(current) && stored_hash(KEYS(current)[at]) == hash
&& strcmp(KEYS(current)[at], key->str) == 0)) {
at = field_position(current, key->str, hash);
}
}
if (at < 0) {
if (step + 1 < count) {
snprintf(msg, sizeof msg,
"cannot set inside field '%s' — the object has no such field",
key->str);
goto failed;
}
own_level(current);
CodeValue one;
memset(&one, 0, sizeof one);
const char *name = key->str;
code_object(&one, &name, &v, 1);
code_add_into(current, &one);
code_release(&one);
code_release(&v);
return;
}
guesses[step] = at;
own_level(current);
current = slot_at(ITEMS(current), at);
} else {
snprintf(msg, sizeof msg,
"cannot set a part of %s %s — only an array's elements and an object's "
"fields can be set",
article_for(current), type_name(current));
goto failed;
}
}
code_release(current);
*current = v;
return;
failed:
code_release(&v);
fail(msg);
}
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);
}
double code_operand_number(const CodeValue *v, const CodeValue *other, int v_on_left,
const char *op) {
if (v->tag == CODE_NUMBER) {
return v->number;
}
CodeValue number;
memset(&number, 0, sizeof number);
number.tag = CODE_NUMBER;
const CodeValue *rest = other ? other : &number;
fail_binary(op, v_on_left ? v : rest, v_on_left ? rest : v);
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;
}
enum {
CODE_TYPE_ANY,
CODE_TYPE_KIND,
CODE_TYPE_STR,
CODE_TYPE_NUMBER,
CODE_TYPE_BOOL,
CODE_TYPE_UNION,
CODE_TYPE_ARRAY_OF,
CODE_TYPE_OBJECT_OF,
CODE_TYPE_FIELDS,
CODE_TYPE_OPEN,
CODE_TYPE_CLASS
};
typedef struct CodeTypeNode {
int op;
int count;
const struct CodeTypeNode *const *children;
const char *const *names;
const char *text;
double number;
} CodeTypeNode;
static int type_holds(const CodeValue *v, const CodeTypeNode *t, int part) {
if (part && v->tag == CODE_NULL) {
return 1;
}
switch (t->op) {
case CODE_TYPE_ANY:
return 1;
case CODE_TYPE_KIND:
return v->tag == (CodeTag)(int)t->number;
case CODE_TYPE_STR:
return v->tag == CODE_STR && strcmp(v->str, t->text) == 0;
case CODE_TYPE_NUMBER:
return v->tag == CODE_NUMBER && v->number == t->number;
case CODE_TYPE_BOOL:
return v->tag == CODE_BOOL && (v->boolean != 0) == (t->number != 0);
case CODE_TYPE_UNION:
for (int i = 0; i < t->count; i++) {
if (type_holds(v, t->children[i], part)) {
return 1;
}
}
return 0;
case CODE_TYPE_ARRAY_OF:
case CODE_TYPE_OBJECT_OF: {
CodeTag tag = t->op == CODE_TYPE_ARRAY_OF ? CODE_ARRAY : CODE_OBJECT;
if (v->tag != tag) {
return 0;
}
for (long long i = 0; i < LEN(v); i++) {
if (!type_holds(slot_at(ITEMS(v), i), t->children[0], 1)) {
return 0;
}
}
return 1;
}
case CODE_TYPE_FIELDS:
case CODE_TYPE_OPEN:
if (v->tag != CODE_OBJECT) {
return 0;
}
for (int f = 0; f < t->count; f++) {
if (strcmp(t->names[f], "_class") == 0) {
const CodeValue *class_val = find_field(v, "_class");
if (!class_val || class_val->tag == CODE_NULL) {
return 0;
}
}
}
for (long long i = 0; i < LEN(v); i++) {
const char *key = KEYS(v)[i];
int found = -1;
for (int f = 0; f < t->count; f++) {
if (strcmp(t->names[f], key) == 0) {
found = f;
break;
}
}
if (found >= 0) {
if (!type_holds(slot_at(ITEMS(v), i), t->children[found], 1)) {
return 0;
}
} else if (t->op == CODE_TYPE_FIELDS && strcmp(key, "_class") != 0) {
return 0;
}
}
return 1;
case CODE_TYPE_CLASS:
return code_is_particle(v, t->text);
default:
return 0;
}
}
int code_is_type(const CodeValue *v, const CodeTypeNode *t) {
return type_holds(v, t, 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 (LEN(x) != LEN(y)) {
return 0;
}
pending = grow(pending, &pending_cap, len + (size_t)LEN(x), sizeof(Pair));
for (long long i = 0; i < LEN(x); i++) {
pending[len].a = slot_at(ITEMS(x), i);
pending[len].b = slot_at(ITEMS(y), i);
len++;
}
break;
case CODE_OBJECT:
if (LEN(x) != LEN(y)) {
return 0;
}
pending = grow(pending, &pending_cap, len + (size_t)LEN(x), sizeof(Pair));
for (long long i = 0; i < LEN(x); i++) {
long long seen = 0;
for (long long p = 0; p < i; p++) {
if (strcmp(KEYS(x)[p], KEYS(x)[i]) == 0) {
seen++;
}
}
long long found = -1;
for (long long q = 0; q < LEN(y); q++) {
if (strcmp(KEYS(y)[q], KEYS(x)[i]) == 0) {
if (seen == 0) {
found = q;
break;
}
seen--;
}
}
if (found < 0) {
return 0;
}
pending[len].a = slot_at(ITEMS(x), i);
pending[len].b = slot_at(ITEMS(y), 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';
char *result = strdup(t.buf);
CodeValue text = {0};
finish_text(&text, &t, 0);
code_release(&text);
if (!result) code_runtime_error("cannot allocate execution trace");
return result;
}
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