#define _GNU_SOURCE
#include <dlfcn.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "code_abi.h"
_Static_assert(sizeof(CodeValue) <= CODE_VALUE_SLOT_SIZE,
"CodeValue outgrew codegen.rs's VALUE_SIZE stride");
static CodeValue *slot_at(void *base, long long index) {
return (CodeValue *)((char *)base + index * CODE_VALUE_SLOT_SIZE);
}
_Noreturn void code_runtime_error(const char *message) {
fprintf(stderr, "error: %s\n", message);
exit(1);
}
typedef struct {
long long rc;
long long padding;
} CodeHeader;
static long long live_blocks = 0;
static void *heap_alloc(size_t bytes) {
CodeHeader *h = malloc(sizeof(CodeHeader) + bytes);
if (!h) {
code_runtime_error("out of memory");
}
h->rc = 1;
live_blocks++;
return (char *)h + sizeof(CodeHeader);
}
static CodeHeader *header_of(const void *payload) {
return (CodeHeader *)((char *)payload - sizeof(CodeHeader));
}
static void *heap_block(const CodeValue *v) {
switch (v->tag) {
case CODE_STR:
return (void *)v->str;
case CODE_ARRAY:
return v->items;
case CODE_OBJECT:
return (void *)v->keys;
default:
return NULL;
}
}
void code_retain(const CodeValue *v) {
if (v->heap) {
header_of(heap_block(v))->rc++;
}
}
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 CodeValue *dead = NULL;
static size_t dead_cap = 0;
void code_release(CodeValue *v) {
if (!v->heap) {
return;
}
if (--header_of(heap_block(v))->rc != 0) {
return;
}
size_t len = 0;
dead = grow(dead, &dead_cap, len + 1, sizeof(CodeValue));
dead[len++] = *v;
while (len > 0) {
CodeValue current = dead[--len];
if (current.tag == CODE_ARRAY || current.tag == CODE_OBJECT) {
for (long long i = 0; i < current.len; i++) {
const CodeValue *child = slot_at(current.items, i);
if (child->heap && --header_of(heap_block(child))->rc == 0) {
dead = grow(dead, &dead_cap, len + 1, sizeof(CodeValue));
dead[len++] = *child;
}
}
}
free(header_of(heap_block(¤t)));
live_blocks--;
}
}
void code_check_leaks(void) {
if (!getenv("CODE_CHECK_LEAKS")) {
return;
}
if (live_blocks != 0) {
char msg[96];
snprintf(msg, sizeof msg, "%lld heap block(s) leaked", live_blocks);
code_runtime_error(msg);
}
}
void code_number(CodeValue *out, double n) {
code_release(out);
out->tag = CODE_NUMBER;
out->heap = 0;
out->number = n;
}
void code_str(CodeValue *out, const char *s) {
code_release(out);
out->tag = CODE_STR;
out->heap = 0;
out->str = s;
}
void code_bool(CodeValue *out, int b) {
code_release(out);
out->tag = CODE_BOOL;
out->heap = 0;
out->boolean = b;
}
void code_null(CodeValue *out) {
code_release(out);
out->tag = CODE_NULL;
out->heap = 0;
}
void code_array(CodeValue *out, void *items, long long len) {
void *buf = NULL;
if (len > 0) {
buf = heap_alloc((size_t)len * CODE_VALUE_SLOT_SIZE);
for (long long i = 0; i < len; i++) {
const CodeValue *src = slot_at(items, i);
code_retain(src);
*slot_at(buf, i) = *src;
}
}
code_release(out);
out->tag = CODE_ARRAY;
out->heap = len > 0;
out->items = buf;
out->len = len;
}
void code_object(CodeValue *out, const char **keys, void *values, long long len) {
const char **key_buf = NULL;
void *value_buf = NULL;
if (len > 0) {
size_t keys_bytes = (size_t)len * sizeof(const char *);
key_buf = heap_alloc(keys_bytes + (size_t)len * CODE_VALUE_SLOT_SIZE);
value_buf = (char *)key_buf + keys_bytes;
for (long long i = 0; i < len; i++) {
key_buf[i] = keys[i];
const CodeValue *src = slot_at(values, i);
code_retain(src);
*slot_at(value_buf, i) = *src;
}
}
code_release(out);
out->tag = CODE_OBJECT;
out->heap = len > 0;
out->keys = key_buf;
out->items = value_buf;
out->len = len;
}
void code_copy(CodeValue *out, const CodeValue *src) {
code_retain(src);
code_release(out);
*out = *src;
}
void code_field(CodeValue *out, const CodeValue *obj, const char *field) {
if (obj->tag == CODE_OBJECT) {
for (long long i = 0; i < obj->len; i++) {
if (strcmp(obj->keys[i], field) == 0) {
code_copy(out, slot_at(obj->items, i));
return;
}
}
}
code_null(out);
}
void code_index(CodeValue *out, const CodeValue *arr, const CodeValue *index) {
if (arr->tag == CODE_ARRAY && index->tag == CODE_NUMBER) {
double n = index->number;
long long i = (long long)n;
if ((double)i == n && i >= 0 && i < arr->len) {
code_copy(out, slot_at(arr->items, i));
return;
}
}
code_null(out);
}
static const CodeValue *find_field(const CodeValue *obj, const char *key) {
for (long long i = 0; i < obj->len; i++) {
if (strcmp(obj->keys[i], key) == 0) {
return slot_at(obj->items, i);
}
}
return NULL;
}
static void code_make_result(CodeValue *out, const char *class_name, const CodeValue *value) {
const char *keys[2] = {"_class", "value"};
_Alignas(8) char slots[2 * CODE_VALUE_SLOT_SIZE] = {0};
code_str(slot_at(slots, 0), class_name);
code_copy(slot_at(slots, 1), value);
code_object(out, keys, slots, 2);
code_release(slot_at(slots, 0));
code_release(slot_at(slots, 1));
}
void code_core_dispatch(CodeValue *out, const CodeValue *particle) {
if (particle->tag != CODE_OBJECT) {
code_runtime_error("emit requires a particle (an object with a \"_class\" field)");
}
const CodeValue *class_val = find_field(particle, "_class");
if (!class_val || class_val->tag != CODE_STR) {
code_runtime_error("emit requires a particle (an object with a \"_class\" field)");
}
if (strcmp(class_val->str, "Length") == 0) {
const CodeValue *value = find_field(particle, "value");
if (!value) {
code_runtime_error("Length { \"value\": ... } requires a 'value' field");
}
CodeValue count = {0};
if (value->tag == CODE_ARRAY) {
code_number(&count, (double)value->len);
code_make_result(out, "LengthResult", &count);
return;
}
if (value->tag == CODE_STR) {
code_number(&count, (double)strlen(value->str));
code_make_result(out, "LengthResult", &count);
return;
}
code_runtime_error("Length requires an array or string 'value'");
}
char msg[96];
snprintf(msg, sizeof msg, "unknown core handler '%s'", class_val->str);
code_runtime_error(msg);
}
typedef struct {
void (*dispatch)(CodeValue *out, const CodeValue *particle);
void (*release)(CodeValue *v);
const CodeVarList *(*vars)(void);
} 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_native_open(const char *path) {
void *handle = dlopen(path, RTLD_NOW);
if (!handle) {
char msg[256];
snprintf(msg, sizeof msg, "cannot load native module '%s': %s", path, dlerror());
code_runtime_error(msg);
}
uint32_t (*version_fn)(void) = (uint32_t (*)(void))dlsym(handle, "code_module_abi_version");
if (!version_fn) {
char msg[256];
snprintf(msg, sizeof msg, "native module '%s' missing 'code_module_abi_version'", path);
code_runtime_error(msg);
}
code_static_module_check(version_fn(), path);
NativeHandle *nh = malloc(sizeof(NativeHandle));
if (!nh) {
code_runtime_error("out of memory");
}
nh->dispatch = (void (*)(CodeValue *, const CodeValue *))dlsym(handle, "code_module_dispatch");
nh->release = (void (*)(CodeValue *))dlsym(handle, "code_release");
if (!nh->dispatch || !nh->release) {
char msg[256];
snprintf(msg, sizeof msg,
"native module '%s' missing 'code_module_dispatch' or 'code_release'", path);
code_runtime_error(msg);
}
nh->vars = (const CodeVarList *(*)(void))dlsym(handle, "code_module_vars");
return nh;
}
static void code_str_owned(CodeValue *out, const char *s) {
size_t n = strlen(s);
char *buf = heap_alloc(n + 1);
memcpy(buf, s, n + 1);
code_release(out);
out->tag = CODE_STR;
out->heap = 1;
out->str = buf;
}
static void code_native_copy_in(CodeValue *out, const CodeValue *from) {
switch (from->tag) {
case CODE_NUMBER:
code_number(out, from->number);
return;
case CODE_STR:
code_str_owned(out, from->str);
return;
case CODE_BOOL:
code_bool(out, from->boolean);
return;
case CODE_NULL:
code_null(out);
return;
case CODE_ARRAY: {
void *slots = from->len > 0 ? calloc((size_t)from->len, CODE_VALUE_SLOT_SIZE) : NULL;
for (long long i = 0; i < from->len; i++) {
code_native_copy_in(slot_at(slots, i), slot_at(from->items, i));
}
code_array(out, slots, from->len);
for (long long i = 0; i < from->len; i++) {
code_release(slot_at(slots, i));
}
free(slots);
return;
}
case CODE_OBJECT: {
const char **keys = from->len > 0 ? malloc((size_t)from->len * sizeof(const char *)) : NULL;
void *slots = from->len > 0 ? calloc((size_t)from->len, CODE_VALUE_SLOT_SIZE) : NULL;
for (long long i = 0; i < from->len; i++) {
keys[i] = from->keys[i];
code_native_copy_in(slot_at(slots, i), slot_at(from->items, i));
}
code_object(out, keys, slots, from->len);
for (long long i = 0; i < from->len; i++) {
code_release(slot_at(slots, i));
}
free(keys);
free(slots);
return;
}
}
}
void code_native_close(void *handle) {
free(handle);
}
void code_native_dispatch(void *handle, CodeValue *out, const CodeValue *particle) {
NativeHandle *nh = (NativeHandle *)handle;
CodeValue result = {0};
nh->dispatch(&result, particle);
code_native_copy_in(out, &result);
nh->release(&result);
}
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) {
code_runtime_error("loop requires an array");
}
return v->len;
}
void code_iter_at(CodeValue *out, const CodeValue *arr, long long i) {
code_copy(out, slot_at(arr->items, i));
}
void code_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->len, nb = b->len;
long long total = na + nb;
void *buf = NULL;
if (total > 0) {
buf = heap_alloc((size_t)total * CODE_VALUE_SLOT_SIZE);
for (long long i = 0; i < na; i++) {
const CodeValue *src = slot_at(a->items, i);
code_retain(src);
*slot_at(buf, i) = *src;
}
for (long long i = 0; i < nb; i++) {
const CodeValue *src = slot_at(b->items, i);
code_retain(src);
*slot_at(buf, na + i) = *src;
}
}
code_release(out);
out->tag = CODE_ARRAY;
out->heap = total > 0;
out->items = buf;
out->len = total;
return;
}
code_runtime_error("cannot apply '+' to these values");
}
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;
}
code_runtime_error("cannot apply '-' to these values");
}
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;
}
code_runtime_error("cannot apply '*' to these values");
}
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) {
code_runtime_error("division by zero");
}
code_number(out, a->number / b->number);
return;
}
code_runtime_error("cannot apply '/' to these values");
}
long long code_compare(const CodeValue *a, const CodeValue *b) {
if (a->tag == CODE_NUMBER && b->tag == CODE_NUMBER) {
if (a->number < b->number) {
return -1;
}
return a->number > b->number ? 1 : 0;
}
code_runtime_error("cannot order these values");
}
void code_neg(CodeValue *out, const CodeValue *a) {
if (a->tag == CODE_NUMBER) {
code_number(out, -a->number);
return;
}
code_runtime_error("cannot negate this value");
}
void code_not(CodeValue *out, const CodeValue *a) {
if (a->tag == CODE_BOOL) {
code_bool(out, !a->boolean);
return;
}
code_runtime_error("'not' requires a boolean");
}
int code_bool_value(const CodeValue *v, const char *op) {
if (v->tag != CODE_BOOL) {
char msg[64];
snprintf(msg, sizeof msg, "'%s' requires booleans", op);
code_runtime_error(msg);
}
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:
case CODE_OBJECT:
if (x->len != y->len) {
return 0;
}
pending = grow(pending, &pending_cap, len + (size_t)x->len, sizeof(Pair));
for (long long i = 0; i < x->len; i++) {
if (x->tag == CODE_OBJECT && strcmp(x->keys[i], y->keys[i]) != 0) {
return 0;
}
pending[len].a = slot_at(x->items, i);
pending[len].b = slot_at(y->items, i);
len++;
}
break;
}
}
return 1;
}
void code_assert(const CodeValue *v) {
if (v->tag != CODE_BOOL) {
code_runtime_error("assert requires a boolean");
}
if (!v->boolean) {
code_runtime_error("assertion failed");
}
}
static void format_number(double n, char *buf, size_t bufsize) {
if (n == (double)(long long)n && fabs(n) < 1e15) {
snprintf(buf, bufsize, "%lld", (long long)n);
return;
}
for (int prec = 1; prec <= 17; prec++) {
snprintf(buf, bufsize, "%.*g", prec, n);
if (strtod(buf, NULL) == n) {
return;
}
}
}
static void print_json_string(const char *s) {
putchar('"');
for (const unsigned char *p = (const unsigned char *)s; *p; p++) {
switch (*p) {
case '"':
fputs("\\\"", stdout);
break;
case '\\':
fputs("\\\\", stdout);
break;
case '\n':
fputs("\\n", stdout);
break;
case '\t':
fputs("\\t", stdout);
break;
default:
putchar(*p);
}
}
putchar('"');
}
typedef struct {
const CodeValue *value;
const char *punct;
int is_key;
} Step;
static Step *steps = NULL;
static size_t steps_cap = 0;
static void push_step(size_t *len, const CodeValue *value, const char *punct, int is_key) {
steps = grow(steps, &steps_cap, *len + 1, sizeof(Step));
steps[*len].value = value;
steps[*len].punct = punct;
steps[*len].is_key = is_key;
(*len)++;
}
static void print_json(const CodeValue *v) {
char buf[64];
size_t len = 0;
push_step(&len, v, NULL, 0);
while (len > 0) {
Step step = steps[--len];
if (!step.value) {
if (step.is_key) {
print_json_string(step.punct);
putchar(':');
} else {
fputs(step.punct, stdout);
}
continue;
}
const CodeValue *current = step.value;
switch (current->tag) {
case CODE_NUMBER:
format_number(current->number, buf, sizeof buf);
fputs(buf, stdout);
break;
case CODE_STR:
print_json_string(current->str);
break;
case CODE_BOOL:
fputs(current->boolean ? "true" : "false", stdout);
break;
case CODE_NULL:
fputs("null", stdout);
break;
case CODE_ARRAY:
putchar('[');
push_step(&len, NULL, "]", 0);
for (long long i = current->len - 1; i >= 0; i--) {
push_step(&len, slot_at(current->items, i), NULL, 0);
if (i > 0) {
push_step(&len, NULL, ",", 0);
}
}
break;
case CODE_OBJECT:
putchar('{');
push_step(&len, NULL, "}", 0);
for (long long i = current->len - 1; i >= 0; i--) {
push_step(&len, slot_at(current->items, i), NULL, 0);
push_step(&len, NULL, current->keys[i], 1);
if (i > 0) {
push_step(&len, NULL, ",", 0);
}
}
break;
}
}
}
void code_dump_bindings(const char **names, void *values, long long count) {
for (long long i = 0; i < count; i++) {
fputs(names[i], stdout);
fputs(" = ", stdout);
print_json(slot_at(values, i));
putchar('\n');
}
}