This commit is contained in:
Lobo 2026-02-06 11:52:53 -03:00
parent 2ac2f85512
commit 90175b7e26
12 changed files with 300 additions and 77 deletions

View file

@ -45,6 +45,7 @@ growl = executable(
)
growlnext_sources = [
'next/core/alien.c',
'next/core/arena.c',
'next/core/callable.c',
'next/core/compiler.c',
@ -53,6 +54,7 @@ growlnext_sources = [
'next/core/sleb128.c',
'next/core/string.c',
'next/core/tuple.c',
'next/core/value.c',
'next/core/vm.c',
'next/main.c',
]

23
next/core/alien.c Normal file
View file

@ -0,0 +1,23 @@
#include <growl.h>
Growl growl_make_alien(GrowlVM *vm, GrowlAlienType *type, void *data) {
size_t size = sizeof(GrowlObjectHeader) + sizeof(GrowlAlien);
GrowlObjectHeader *hdr = growl_gc_alloc(vm, size);
hdr->type = GROWL_TYPE_ALIEN;
GrowlAlien *alien = (GrowlAlien *)(hdr + 1);
alien->type = type;
alien->data = data;
return GROWL_BOX(hdr);
}
GrowlAlien *growl_unwrap_alien(Growl obj, GrowlAlienType *type) {
if (obj == GROWL_NIL || GROWL_IMM(obj))
return NULL;
GrowlObjectHeader *hdr = GROWL_UNBOX(obj);
if (hdr->type != GROWL_TYPE_ALIEN)
return NULL;
GrowlAlien *alien = (GrowlAlien *)(hdr + 1);
if (alien->type != type)
return NULL;
return alien;
}

View file

@ -16,11 +16,11 @@ void growl_arena_free(GrowlGCArena *arena) {
void *growl_arena_alloc(GrowlGCArena *arena, size_t size, size_t align,
size_t count) {
ptrdiff_t padding = -(uintptr_t)arena->start & (align - 1);
ptrdiff_t available = arena->end - arena->start - padding;
ptrdiff_t padding = -(uintptr_t)arena->free & (align - 1);
ptrdiff_t available = arena->end - arena->free - padding;
if (available < 0 || count > available / size)
abort();
void *p = arena->start + padding;
arena->start += padding + count * size;
void *p = arena->free + padding;
arena->free += padding + count * size;
return memset(p, 0, count * size);
}

View file

@ -6,9 +6,9 @@ int growl_callable(Growl obj) {
return 0;
GrowlObjectHeader *hdr = GROWL_UNBOX(obj);
switch (hdr->type) {
case GROWL_QUOTATION:
case GROWL_COMPOSE:
case GROWL_CURRY:
case GROWL_TYPE_QUOTATION:
case GROWL_TYPE_COMPOSE:
case GROWL_TYPE_CURRY:
return 1;
default:
return 0;
@ -21,7 +21,7 @@ Growl growl_make_quotation(GrowlVM *vm, const uint8_t *code, size_t code_size,
constants_size * sizeof(Growl);
GrowlObjectHeader *constants_hdr =
growl_gc_alloc_tenured(vm, constants_obj_size);
constants_hdr->type = GROWL_TUPLE;
constants_hdr->type = GROWL_TYPE_TUPLE;
GrowlTuple *constants_tuple = (GrowlTuple *)(constants_hdr + 1);
constants_tuple->count = constants_size;
@ -33,7 +33,7 @@ Growl growl_make_quotation(GrowlVM *vm, const uint8_t *code, size_t code_size,
sizeof(GrowlObjectHeader) + sizeof(GrowlQuotation) + code_size;
GrowlObjectHeader *quotation_hdr =
growl_gc_alloc_tenured(vm, quotation_obj_size);
quotation_hdr->type = GROWL_QUOTATION;
quotation_hdr->type = GROWL_TYPE_QUOTATION;
GrowlQuotation *quotation = (GrowlQuotation *)(quotation_hdr + 1);
quotation->constants = GROWL_BOX(constants_hdr);
@ -47,7 +47,7 @@ GrowlQuotation *growl_unwrap_quotation(Growl obj) {
if (obj == GROWL_NIL || GROWL_IMM(obj))
return NULL;
GrowlObjectHeader *hdr = GROWL_UNBOX(obj);
if (hdr->type != GROWL_QUOTATION)
if (hdr->type != GROWL_TYPE_QUOTATION)
return NULL;
return (GrowlQuotation *)(hdr + 1);
}
@ -59,7 +59,7 @@ Growl growl_compose(GrowlVM *vm, Growl first, Growl second) {
return GROWL_NIL;
size_t size = sizeof(GrowlObjectHeader) + sizeof(GrowlCompose);
GrowlObjectHeader *hdr = growl_gc_alloc(vm, size);
hdr->type = GROWL_COMPOSE;
hdr->type = GROWL_TYPE_COMPOSE;
GrowlCompose *comp = (GrowlCompose *)(hdr + 1);
comp->first = first;
comp->second = second;
@ -70,7 +70,7 @@ GrowlCompose *growl_unwrap_compose(Growl obj) {
if (obj == GROWL_NIL || GROWL_IMM(obj))
return NULL;
GrowlObjectHeader *hdr = GROWL_UNBOX(obj);
if (hdr->type != GROWL_COMPOSE)
if (hdr->type != GROWL_TYPE_COMPOSE)
return NULL;
return (GrowlCompose *)(hdr + 1);
}
@ -80,7 +80,7 @@ Growl growl_curry(GrowlVM *vm, Growl value, Growl callable) {
return GROWL_NIL;
size_t size = sizeof(GrowlObjectHeader) + sizeof(GrowlCurry);
GrowlObjectHeader *hdr = growl_gc_alloc(vm, size);
hdr->type = GROWL_CURRY;
hdr->type = GROWL_TYPE_CURRY;
GrowlCurry *comp = (GrowlCurry *)(hdr + 1);
comp->value = value;
comp->callable = callable;
@ -91,7 +91,7 @@ GrowlCurry *growl_unwrap_curry(Growl obj) {
if (obj == GROWL_NIL || GROWL_IMM(obj))
return NULL;
GrowlObjectHeader *hdr = GROWL_UNBOX(obj);
if (hdr->type != GROWL_CURRY)
if (hdr->type != GROWL_TYPE_CURRY)
return NULL;
return (GrowlCurry *)(hdr + 1);
}

View file

@ -67,38 +67,40 @@ GrowlObjectHeader *growl_gc_alloc_tenured(GrowlVM *vm, size_t size) {
static void scan(GrowlVM *vm, GrowlObjectHeader *hdr) {
switch (hdr->type) {
case GROWL_STRING:
case GROWL_TYPE_STRING:
break;
case GROWL_LIST: {
case GROWL_TYPE_LIST: {
GrowlList *list = (GrowlList *)(hdr + 1);
list->head = forward(vm, list->head);
list->tail = forward(vm, list->tail);
break;
}
case GROWL_TUPLE: {
case GROWL_TYPE_TUPLE: {
GrowlTuple *tuple = (GrowlTuple *)(hdr + 1);
for (size_t i = 0; i < tuple->count; ++i) {
tuple->data[i] = forward(vm, tuple->data[i]);
}
break;
}
case GROWL_QUOTATION: {
case GROWL_TYPE_QUOTATION: {
GrowlQuotation *quot = (GrowlQuotation *)(hdr + 1);
quot->constants = forward(vm, quot->constants);
break;
}
case GROWL_COMPOSE: {
case GROWL_TYPE_COMPOSE: {
GrowlCompose *comp = (GrowlCompose *)(hdr + 1);
comp->first = forward(vm, comp->first);
comp->second = forward(vm, comp->second);
break;
}
case GROWL_CURRY: {
case GROWL_TYPE_CURRY: {
GrowlCurry *comp = (GrowlCurry *)(hdr + 1);
comp->value = forward(vm, comp->value);
comp->callable = forward(vm, comp->callable);
break;
}
case GROWL_TYPE_ALIEN:
break;
case UINT32_MAX:
fprintf(stderr, "gc: fwd pointer during scan\n");
abort();
@ -109,15 +111,22 @@ static void scan(GrowlVM *vm, GrowlObjectHeader *hdr) {
}
static void gc_print_stats(GrowlVM *vm, const char *label) {
size_t used = vm->from.free - vm->from.start;
size_t total = vm->from.end - vm->from.start;
fprintf(stderr, "[%s] used=%zu/%zu bytes (%.1f%%)\n", label, used, total,
(double)used / (double)total * 100.0);
size_t nursery_used = vm->from.free - vm->from.start;
size_t nursery_total = vm->from.end - vm->from.start;
size_t tenured_used = vm->arena.free - vm->arena.start;
size_t tenured_total = vm->arena.end - vm->arena.start;
fprintf(stderr, "%s:\n", label);
fprintf(stderr, " tenured: %zu/%zu bytes (%.1f%%)\n", tenured_used,
nursery_total, (double)tenured_used / (double)tenured_total * 100.0);
fprintf(stderr, " nursery: %zu/%zu bytes (%.1f%%)\n", nursery_used,
nursery_total, (double)nursery_used / (double)nursery_total * 100.0);
}
void growl_gc_collect(GrowlVM *vm) {
uint8_t *gc_scan = vm->to.free;
fprintf(stderr, ">>> starting garbage collection\n");
gc_print_stats(vm, "before GC");
for (size_t i = 0; i < GROWL_STACK_SIZE; ++i) {
@ -141,6 +150,26 @@ void growl_gc_collect(GrowlVM *vm) {
gc_scan += ALIGN(hdr->size);
}
gc_scan = vm->from.start;
while (gc_scan < vm->from.free) {
GrowlObjectHeader *hdr = (GrowlObjectHeader *)gc_scan;
if (hdr->type != UINT32_MAX) {
switch (hdr->type) {
case GROWL_TYPE_ALIEN: {
GrowlAlien *alien = (GrowlAlien *)(hdr + 1);
if (alien->type->finalizer != NULL) {
alien->type->finalizer(alien->data);
alien->data = NULL;
}
break;
}
default:
break;
}
}
gc_scan += ALIGN(hdr->size);
}
GrowlGCArena tmp = vm->from;
vm->from = vm->to;
vm->to = tmp;
@ -148,6 +177,7 @@ void growl_gc_collect(GrowlVM *vm) {
vm->scratch.free = vm->scratch.start;
gc_print_stats(vm, "after GC");
fprintf(stderr, ">>> garbage collection finished\n");
}
void growl_gc_root(GrowlVM *vm, Growl *ptr) {

View file

@ -1,11 +1,25 @@
#ifndef GROWL_OPCODES_H
#define GROWL_OPCODES_H
enum {
enum GrowlOpcode {
GOP_NOP = 0,
GOP_PUSH_NIL,
GOP_PUSH_CONSTANT,
GOP_DROP,
GOP_DUP,
GOP_SWAP,
GOP_2DROP,
GOP_2DUP,
GOP_2SWAP,
GOP_NIP,
GOP_OVER,
GOP_BURY,
GOP_DIG,
GOP_TO_RETAIN,
GOP_FROM_RETAIN,
GOP_CALL,
GOP_CALL_NEXT,
GOP_TAIL_CALL,
GOP_RETURN,
};

View file

@ -16,7 +16,7 @@ intptr_t growl_sleb128_decode(uint8_t **ptr) {
shift += 7;
} while (byte & 0x80);
if ((shift < 64) && (byte & 0x40)) {
if (shift < 64 && byte & 0x40) {
result |= -(1LL << shift);
}

View file

@ -4,7 +4,7 @@
Growl growl_make_string(GrowlVM *vm, size_t len) {
size_t size = sizeof(GrowlObjectHeader) + sizeof(GrowlString) + len;
GrowlObjectHeader *hdr = growl_gc_alloc(vm, size);
hdr->type = GROWL_STRING;
hdr->type = GROWL_TYPE_STRING;
GrowlString *str = (GrowlString *)(hdr + 1);
str->len = len;
memset(str->data, 0, len);
@ -15,7 +15,7 @@ Growl growl_wrap_string(GrowlVM *vm, const char *cstr) {
size_t len = strlen(cstr);
size_t size = sizeof(GrowlObjectHeader) + sizeof(GrowlString) + len + 1;
GrowlObjectHeader *hdr = growl_gc_alloc(vm, size);
hdr->type = GROWL_STRING;
hdr->type = GROWL_TYPE_STRING;
GrowlString *str = (GrowlString *)(hdr + 1);
str->len = len;
memcpy(str->data, cstr, len);
@ -27,7 +27,7 @@ GrowlString *growl_unwrap_string(Growl obj) {
if (obj == 0 || GROWL_IMM(obj))
return NULL;
GrowlObjectHeader *hdr = GROWL_UNBOX(obj);
if (hdr->type != GROWL_STRING)
if (hdr->type != GROWL_TYPE_STRING)
return NULL;
return (GrowlString *)(hdr + 1);
}

View file

@ -4,7 +4,7 @@ GrowlTuple *growl_unwrap_tuple(Growl obj) {
if (obj == 0 || GROWL_IMM(obj))
return NULL;
GrowlObjectHeader *hdr = GROWL_UNBOX(obj);
if (hdr->type != GROWL_TUPLE)
if (hdr->type != GROWL_TYPE_TUPLE)
return NULL;
return (GrowlTuple *)(hdr + 1);
}

11
next/core/value.c Normal file
View file

@ -0,0 +1,11 @@
#include <growl.h>
uint32_t growl_type(Growl obj) {
if (obj == GROWL_NIL)
return GROWL_TYPE_NIL;
if (GROWL_IMM(obj))
return GROWL_TYPE_NUMBER;
GrowlObjectHeader *hdr = GROWL_UNBOX(obj);
return hdr->type;
}

View file

@ -33,6 +33,8 @@ GrowlVM *growl_vm_init(void) {
mem->root_count = 0;
mem->root_capacity = 0;
// TODO: initialize compose trampoline
return mem;
}
@ -63,6 +65,12 @@ void growl_push(GrowlVM *vm, Growl obj) {
*vm->sp++ = obj;
}
Growl growl_peek(GrowlVM *vm, size_t depth) {
if (vm->sp <= vm->wst + depth)
vm_error(vm, "work stack underflow");
return vm->sp[-(depth + 1)];
}
Growl growl_pop(GrowlVM *vm) {
if (vm->sp <= vm->wst)
vm_error(vm, "work stack underflow");
@ -85,19 +93,47 @@ Growl growl_rpop(GrowlVM *vm) {
return obj;
}
static void push_call(GrowlVM *vm, GrowlQuotation *q, uint8_t *ip) {
static void callstack_push(GrowlVM *vm, GrowlQuotation *q, uint8_t *ip) {
if (vm->csp >= vm->cst + GROWL_CALL_STACK_SIZE)
vm_error(vm, "call stack overflow");
vm->csp->quot = q;
vm->csp->ip = ip;
vm->csp++;
}
static GrowlFrame pop_call(GrowlVM *vm) {
static GrowlFrame callstack_pop(GrowlVM *vm) {
if (vm->csp <= vm->cst)
vm_error(vm, "call stack underflow");
return *--vm->csp;
}
static inline void dispatch(GrowlVM *vm, Growl obj) {
for (;;) {
switch (growl_type(obj)) {
case GROWL_TYPE_QUOTATION: {
GrowlQuotation *q = (GrowlQuotation *)(GROWL_UNBOX(obj) + 1);
vm->quotation = q;
vm->ip = q->data;
return;
}
case GROWL_TYPE_COMPOSE: {
GrowlCompose *c = (GrowlCompose *)(GROWL_UNBOX(obj) + 1);
callstack_push(vm, vm->compose_trampoline, vm->compose_trampoline->data);
vm->csp[-1].next = c->second;
obj = c->first;
continue;
}
case GROWL_TYPE_CURRY: {
GrowlCurry *c = (GrowlCurry *)(GROWL_UNBOX(obj) + 1);
growl_push(vm, c->value);
obj = c->callable;
continue;
}
default:
vm_error(vm, "attempt to call non-callable");
}
}
}
int vm_doquot(GrowlVM *vm, GrowlQuotation *quot) {
size_t gc_mark = growl_gc_mark(vm);
int result = setjmp(vm->error);
@ -117,46 +153,129 @@ int vm_doquot(GrowlVM *vm, GrowlQuotation *quot) {
vm->ip = quot->data;
vm->quotation = quot;
for (;;) {
uint8_t opcode;
switch (opcode = *vm->ip++) {
case GOP_NOP:
break;
case GOP_PUSH_NIL:
growl_push(vm, GROWL_NIL);
break;
case GOP_PUSH_CONSTANT: {
intptr_t idx = growl_sleb128_decode(&vm->ip);
if (constants != NULL) {
growl_push(vm, constants->data[idx]);
} else {
vm_error(vm, "constant index %" PRIdPTR " out of bounds", idx);
}
break;
case GOP_CALL: { // TODO: compose and curry
Growl obj = growl_pop(vm);
push_call(vm, vm->quotation, vm->ip);
GrowlQuotation *obj_quot = growl_unwrap_quotation(obj);
if (obj_quot == NULL)
vm_error(vm, "attempt to call non-callable");
vm->quotation = obj_quot;
vm->ip = obj_quot->data;
break;
}
case GOP_RETURN:
if (vm->csp != vm->cst) {
GrowlFrame frame = pop_call(vm);
vm->quotation = frame.quot;
vm->ip = frame.ip;
} else {
goto done;
}
break;
}
default:
vm_error(vm, "unknown opcode %d", opcode);
}
// clang-format off
#define VM_START() for (;;) { uint8_t opcode; switch(opcode = *vm->ip++) {
#define VM_END() }}
#define VM_DEFAULT() default:
#define VM_OP(op) case GOP_## op:
#define VM_NEXT() break
// clang-format on
VM_START()
VM_OP(NOP) VM_NEXT();
VM_OP(PUSH_NIL) {
growl_push(vm, GROWL_NIL);
VM_NEXT();
}
VM_OP(PUSH_CONSTANT) {
intptr_t idx = growl_sleb128_decode(&vm->ip);
if (constants != NULL) {
growl_push(vm, constants->data[idx]);
} else {
vm_error(vm, "constant index %" PRIdPTR " out of bounds", idx);
}
VM_NEXT();
}
VM_OP(DROP) {
(void)growl_pop(vm);
VM_NEXT();
}
VM_OP(DUP) {
growl_push(vm, growl_peek(vm, 0));
VM_NEXT();
}
VM_OP(SWAP) {
Growl b = growl_pop(vm);
Growl a = growl_pop(vm);
growl_push(vm, b);
growl_push(vm, a);
VM_NEXT();
}
VM_OP(2DROP) {
(void)growl_pop(vm);
(void)growl_pop(vm);
VM_NEXT();
}
VM_OP(2DUP) {
growl_push(vm, growl_peek(vm, 1));
growl_push(vm, growl_peek(vm, 1));
VM_NEXT();
}
VM_OP(2SWAP) {
Growl d = growl_pop(vm);
Growl c = growl_pop(vm);
Growl b = growl_pop(vm);
Growl a = growl_pop(vm);
growl_push(vm, c);
growl_push(vm, d);
growl_push(vm, a);
growl_push(vm, b);
VM_NEXT();
}
VM_OP(NIP) {
Growl b = growl_pop(vm);
(void)growl_pop(vm);
growl_push(vm, b);
VM_NEXT();
}
VM_OP(OVER) {
growl_push(vm, growl_peek(vm, 1));
VM_NEXT();
}
VM_OP(BURY) {
Growl c = growl_pop(vm);
Growl b = growl_pop(vm);
Growl a = growl_pop(vm);
growl_push(vm, c);
growl_push(vm, a);
growl_push(vm, b);
VM_NEXT();
}
VM_OP(TO_RETAIN) {
growl_rpush(vm, growl_pop(vm));
VM_NEXT();
}
VM_OP(FROM_RETAIN) {
growl_push(vm, growl_rpop(vm));
VM_NEXT();
}
VM_OP(DIG) {
Growl c = growl_pop(vm);
Growl b = growl_pop(vm);
Growl a = growl_pop(vm);
growl_push(vm, b);
growl_push(vm, c);
growl_push(vm, a);
VM_NEXT();
}
VM_OP(CALL) { // TODO: compose and curry
Growl obj = growl_pop(vm);
callstack_push(vm, vm->quotation, vm->ip);
dispatch(vm, obj);
VM_NEXT();
}
VM_OP(CALL_NEXT) {
growl_push(vm, vm->next);
vm->next = GROWL_NIL;
__attribute__((__fallthrough__));
}
VM_OP(TAIL_CALL) {
Growl obj = growl_pop(vm);
dispatch(vm, obj);
VM_NEXT();
}
VM_OP(RETURN) {
if (vm->csp != vm->cst) {
GrowlFrame frame = callstack_pop(vm);
vm->quotation = frame.quot;
vm->ip = frame.ip;
} else {
goto done;
}
VM_NEXT();
}
VM_DEFAULT() { vm_error(vm, "unknown opcode %d", opcode); }
VM_END()
done:
growl_gc_reset(vm, gc_mark);

View file

@ -21,19 +21,26 @@ typedef struct GrowlTuple GrowlTuple;
typedef struct GrowlQuotation GrowlQuotation;
typedef struct GrowlCompose GrowlCompose;
typedef struct GrowlCurry GrowlCurry;
typedef struct GrowlAlienType GrowlAlienType;
typedef struct GrowlAlien GrowlAlien;
typedef struct GrowlGCArena GrowlGCArena;
typedef struct GrowlFrame GrowlFrame;
typedef struct GrowlVM GrowlVM;
enum {
GROWL_STRING,
GROWL_LIST,
GROWL_TUPLE,
GROWL_QUOTATION,
GROWL_COMPOSE,
GROWL_CURRY,
GROWL_TYPE_NIL,
GROWL_TYPE_NUMBER,
GROWL_TYPE_STRING,
GROWL_TYPE_LIST,
GROWL_TYPE_TUPLE,
GROWL_TYPE_QUOTATION,
GROWL_TYPE_COMPOSE,
GROWL_TYPE_CURRY,
GROWL_TYPE_ALIEN,
};
uint32_t growl_type(Growl obj);
struct GrowlObjectHeader {
size_t size;
uint32_t type;
@ -82,6 +89,19 @@ GrowlCompose *growl_unwrap_compose(Growl obj);
Growl growl_curry(GrowlVM *vm, Growl value, Growl callable);
GrowlCurry *growl_unwrap_curry(Growl obj);
struct GrowlAlienType {
const char *name;
void (*finalizer)(void *);
};
struct GrowlAlien {
GrowlAlienType *type;
void *data;
};
Growl growl_make_alien(GrowlVM *vm, GrowlAlienType *type, void *data);
GrowlAlien *growl_unwrap_alien(Growl obj, GrowlAlienType *type);
struct GrowlGCArena {
uint8_t *start, *end;
uint8_t *free;
@ -103,6 +123,7 @@ void *growl_arena_alloc(GrowlGCArena *arena, size_t size, size_t align,
struct GrowlFrame {
GrowlQuotation *quot;
uint8_t *ip;
Growl next;
};
struct GrowlVM {
@ -116,6 +137,9 @@ struct GrowlVM {
Growl rst[GROWL_STACK_SIZE], *rsp;
GrowlFrame cst[GROWL_CALL_STACK_SIZE], *csp;
GrowlQuotation *compose_trampoline;
Growl next;
Growl **roots;
size_t root_count, root_capacity;