tli/mem.c
Jonas Maier e69e9bd0f9 sync
2022-12-29 10:03:25 +01:00

488 lines
8.7 KiB
C

#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include "lisp.h"
#include "assert.h"
static node_t mem[MEM_LEN] = {0};
static ptr empty = 0;
static sym_t symbols[SYM_LEN] = {0};
/*
nodes that are reserved for builtin use
should never be GC'ed
*/
static ptr builtin_use;
#define make(name) \
static ptr sym_##name = 0; \
int is_##name(ptr i) \
{ \
return i == sym_##name; \
}
make(quote)
make(unquote)
make(quasiquote)
make(lambda)
make(macro)
make(definition)
make(partial_app)
make(pragma)
#undef make
#define UNBOUND 2
int is_functionlike(ptr i)
{
return i == sym_lambda || i == sym_macro;
}
char *get_symbol_str(ptr s)
{
return symbols[s].name;
}
ptr get_symbol_binding(ptr s)
{
return symbols[s].binding;
}
static void init_builtin_symbols(void)
{
#define make_sym(var_name, sym) \
var_name = new_symbol(sym); \
new_binding(var_name, var_name)
make_sym(sym_lambda, ".\\");
make_sym(sym_definition, "def");
make_sym(sym_macro, "m\\");
make_sym(sym_quote, "quote");
make_sym(sym_unquote, "unquote");
make_sym(sym_quasiquote, "quasiquote");
make_sym(sym_partial_app, "..");
make_sym(sym_pragma, "pragma");
#undef make_sym
}
/*
reads the input file
*/
void read_input(void)
{
FILE *f = fopen("input.txt", "rb");
assert(f);
fseek(f, 0, SEEK_END);
size_t fsize = (size_t)ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = malloc(fsize + 1);
assert(buf);
fread(buf, fsize, 1, f);
fclose(f);
buf[fsize] = 0;
ptr input = new_nil();
for (char *cursor = buf + fsize - 1; cursor >= buf; cursor--)
{
input = new_cons(new_int(*cursor), input);
}
new_binding(new_symbol("input"), input);
free(buf);
}
#define MEM_UNINIT 0
#define MEM_INITIALIZING 1
#define MEM_INITIALIZED 2
static int initialized = MEM_UNINIT;
/*
initializes the interpreter
do NOT call twice
*/
void init(void)
{
if (initialized)
{
failwith("already initialized or in the process of doing so");
}
initialized = MEM_INITIALIZING;
mem[0].kind = T_NIL;
mem[1].kind = T_INT;
mem[1].value = 1;
assert(UNBOUND == 2);
mem[UNBOUND].kind = T_POO;
empty = 3;
for (int i = 3; i < MEM_LEN; ++i)
{
mem[i].kind = T_EMT;
if (i < MEM_LEN - 1)
{
mem[i].next_free = i + 1;
}
else
{
mem[i].next_free = i;
}
}
init_builtin_symbols();
register_builtins();
builtin_use = empty;
read_input();
initialized = MEM_INITIALIZED;
}
/* GC run count */
static int gen = 1;
/* marks values with a global binding as 'in use' */
static void mark_globals(void)
{
for (ptr s = 0; s < SYM_LEN; s++)
{
if (symbols[s].name[0] != 0)
{
int binding = symbols[s].binding;
mem[binding].gc = gen;
}
}
}
ptr *walker;
/*
searches the entire C stack for references to LISP values
if it finds any, it marks those values as 'in use'
*/
void stack_search_impl(void)
{
ptr stack_bottom = 0;
walker = &stack_bottom;
while (++walker != stack_top)
{
if (*walker<MEM_LEN && * walker> 0)
{
mem[*walker].gc = gen;
}
}
}
static void mark_reachable(ptr i);
/*
marks a value as 'in use' if it isn't already marked as such
if it is a freshly marked value, it will also mark the child values
*/
static void maybe_mark(ptr i)
{
if (mem[i].gc != gen)
{
mem[i].gc = gen;
mark_reachable(i);
}
}
/* marks descendants of the node as reachable */
static void mark_reachable(ptr i)
{
if (mem[i].gc != gen)
{
return;
}
if (kind(i) == T_CON)
{
maybe_mark(get_head(i));
maybe_mark(get_tail(i));
}
}
/* marks all indirectly reachable nodes as used */
static void mark_all_reachable(void)
{
for (ptr i = 0; i < MEM_LEN; i++)
{
mark_reachable(i);
}
}
/*
marks all unreachable nodes as 'empty' and make them available to be reused
*/
static void reconstruct_empty_list(void)
{
ptr prev_empty = 0;
int free_memory = 0;
for (ptr i = builtin_use; i < MEM_LEN; i++)
{
if (mem[i].gc == gen || kind(i) == T_SYM)
{
continue;
}
mem[i].kind = T_EMT;
mem[i].gc = ~0;
free_memory++;
if (prev_empty)
{
mem[i].next_free = prev_empty;
}
else
{
mem[i].next_free = i;
}
prev_empty = i;
}
empty = prev_empty;
int usage = 100 - 100 * free_memory / MEM_LEN;
// printf("GC go brrrr... (%d%%)\n", usage);
if (usage > MAX_MEMORY_USAGE || usage > 99)
{
printf("Out of memory.\n");
exit(-1);
}
}
/*
garbage collector to free up nodes
does not return if out of memory, instead will stop program
*/
void gc(void)
{
gen = (gen + 1) & ((~0) >> 1);
mark_globals();
stack_search();
mark_all_reachable();
reconstruct_empty_list();
}
static ptr alloc(void)
{
if (kind(empty) != T_EMT)
{
gc();
}
ptr next = mem[empty].next_free;
ptr new = empty;
empty = next;
node_t zero = {0};
mem[new] = zero;
mem[new].gc = gen;
return new;
}
static void check(ptr i)
{
if (i >= 0 && i < MEM_LEN)
{
if (mem[i].gc == ~0 && mem[i].kind == T_EMT)
{
printf("%ld ", i);
println(i);
failwith("we freed a node that is still in use :(");
}
}
}
// -- constructors for new lisp values -- //
ptr new_int(i64 value)
{
ptr i = alloc();
mem[i].kind = T_INT;
mem[i].value = value;
return i;
}
ptr new_cons(ptr head, ptr tail)
{
ptr i = alloc();
mem[i].kind = T_CON;
check(head);
check(tail);
mem[i].head = head;
mem[i].tail = tail;
return i;
}
ptr new_nil(void)
{
return 0;
}
ptr new_list(int len, ...)
{
va_list vargs;
va_start(vargs, len);
ptr args[len];
for (int i = 0; i < len; i++)
{
args[i] = va_arg(vargs, ptr);
}
ptr list = new_nil();
for (int i = len - 1; i >= 0; i--)
{
list = new_cons(args[i], list);
}
return list;
}
ptr new_true(void)
{
return 1;
}
ptr quoted(ptr i)
{
check(i);
switch (kind(i))
{
case T_CON:
case T_SYM:
return new_list(2, new_symbol("quote"), i);
default:
return i;
}
}
ptr new_builtin(ptr (*fun)(ptr), char *sym, int kind)
{
ptr i = alloc();
assert(kind == T_FUN || kind == T_MAC);
mem[i].kind = kind;
mem[i].builtin = fun;
ptr s = new_symbol(sym);
new_binding(s, i);
return i;
}
ptr new_symbol(char *symbol)
{
if (!strcmp(symbol, "nil") || !strcmp(symbol, "NIL"))
{
return 0;
}
for (ptr k = 0; k < SYM_LEN; k++)
{
if (strlen(symbols[k].name) == 0)
{
// we arrived at the section of unused symbols
// we have not found the symbol in the existing table
// we add a new symbol
ptr i = alloc();
mem[i].kind = T_SYM;
mem[i].symbol = k;
int len = (int)strlen(symbol);
assert(len > 0 && len < 16);
strcpy(symbols[k].name, symbol);
symbols[k].binding = UNBOUND; // point to garbage
symbols[k].node = i;
return i;
}
else if (!strcmp(symbols[k].name, symbol))
{
return symbols[k].node;
}
}
failwith("Out of Symbols.");
}
i64 kind(ptr i)
{
assert(i >= 0);
assert(i < MEM_LEN);
return mem[i].kind;
}
i64 get_int(ptr i)
{
check(i);
assert(mem[i].kind == T_INT);
return mem[i].value;
}
ptr (*get_fn_ptr(ptr i))(ptr)
{
assert(kind(i) == T_FUN || kind(i) == T_MAC);
return mem[i].builtin;
}
ptr get_head(ptr i)
{
check(i);
assert(mem[i].kind == T_CON);
return mem[i].head;
}
ptr get_tail(ptr i)
{
check(i);
assert(mem[i].kind == T_CON);
return mem[i].tail;
}
ptr elem(int idx, ptr node)
{
check(node);
if (idx)
{
return elem(idx - 1, get_tail(node));
}
else
{
return get_head(node);
}
}
ptr get_symbol(ptr i)
{
check(i);
assert(mem[i].kind == T_SYM);
return mem[i].symbol;
}
ptr get_nil(ptr i)
{
check(i);
assert(mem[i].kind == T_NIL);
return 0;
}
void new_binding(ptr symbol, ptr expression)
{
sym_t *sym = &symbols[get_symbol(symbol)];
if (sym->binding != UNBOUND)
{
printf("Definitions cannot be shadowed.\nOffending symbol: %s.\n", &sym->name[0]);
assert(initialized == MEM_INITIALIZING);
}
sym->binding = expression;
}
int mem_usage(void)
{
return sizeof(mem) + sizeof(symbols);
}