Initial (bad) gc

This commit is contained in:
2026-01-21 20:52:18 -08:00
parent 4c04e71078
commit 656846ddc0
16 changed files with 650 additions and 79 deletions

15
.clangd Normal file
View File

@ -0,0 +1,15 @@
CompileFlags:
Add: [-std=c11, -Wall, -Wpedantic, -xc, -D_POSIX_C_SOURCE=199309L]
Compiler: gcc
---
If:
PathMatch: .*\.h
CompileFlags:
Remove: -xc
Add: [-std=c11, -Wall, -Wpedantic, -xc-header]
Compiler: gcc
---
If:
PathMatch: bin/.*\.c
CompileFlags:
Add: -Isrc

View File

@ -1,7 +1,15 @@
DEBUG=1
ifeq ($(DEBUG),1)
DEBUG_CFLAGS=-g
else
DEBUG_CFLAGS=-D_NDEBUG
endif
CC=gcc CC=gcc
CFLAGS=-g -std=c11 -Wall -Wpedantic CFLAGS=$(DEBUG_CFLAGS) -std=c11 -Wall -Wpedantic -D_POSIX_C_SOURCE=199309L
LD=gcc LD=gcc
LDFLAGS=-g LDFLAGS=
SRCS:=$(wildcard src/*.c) SRCS:=$(wildcard src/*.c)
OBJS:=$(SRCS:src/%.c=bin/%.o) OBJS:=$(SRCS:src/%.c=bin/%.o)

View File

@ -1,7 +1,9 @@
#include "base.h" #include "base.h"
#include "gc.h"
#include "hashtable.h" #include "hashtable.h"
#include "lisp.h" #include "lisp.h"
#include "stack.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@ -17,17 +19,32 @@ const char *LISP_TYPE_NAMES[N_LISP_TYPES] = {
[TYPE_FUNCTION] = "function", [TYPE_FUNCTION] = "function",
}; };
void *lisp_alloc_object(size_t size, LispValType type) { void *lisp_alloc_object_no_gc(size_t size, LispValType type) {
assert(size >= sizeof(LispObject)); assert(size >= sizeof(LispObject));
LispObject *obj = lisp_aligned_alloc(LISP_OBJECT_ALIGNMENT, size); LispObject *obj = lisp_aligned_alloc(LISP_OBJECT_ALIGNMENT, size);
memset(obj, 0, size);
obj->type = type; obj->type = type;
obj->gc.mark = false; obj->gc.mark = false;
obj->gc.has_local_ref = false; tss_create(&obj->gc.has_local_ref, NULL);
// TODO set the below
obj->gc.entry = NULL;
return obj; return obj;
} }
void *lisp_alloc_object(size_t size, LispValType type) {
LispObject *obj = lisp_alloc_object_no_gc(size, type);
if (the_stack.depth > 0) {
add_local_reference_no_recurse(obj);
}
lisp_gc_register_object(obj);
return obj;
}
void lisp_release_object(LispVal *val) {
assert(OBJECTP(val));
LispObject *obj = val;
tss_delete(obj->gc.has_local_ref);
lisp_free(val);
}
void internal_CHECK_TYPE_signal_type_error(LispVal *obj, size_t count, void internal_CHECK_TYPE_signal_type_error(LispVal *obj, size_t count,
const LispValType types[count]) { const LispValType types[count]) {
// TODO actually throw an error // TODO actually throw an error

View File

@ -89,14 +89,16 @@ typedef enum {
} LispValType; } LispValType;
extern const char *LISP_TYPE_NAMES[N_LISP_TYPES]; extern const char *LISP_TYPE_NAMES[N_LISP_TYPES];
#define LISP_OBJECT_ALIGNMENT (1 << LISP_TAG_BITS)
void *lisp_alloc_object(size_t size, LispValType type);
typedef struct { typedef struct {
LispValType type; LispValType type;
ObjectGCInfo gc; ObjectGCInfo gc;
} LispObject; } LispObject;
#define LISP_OBJECT_ALIGNMENT (1 << LISP_TAG_BITS)
LispVal *lisp_alloc_object_no_gc(size_t size, LispValType type);
LispVal *lisp_alloc_object(size_t size, LispValType type);
void lisp_release_object(LispVal *val);
static ALWAYS_INLINE bool OBJECTP(LispVal *val) { static ALWAYS_INLINE bool OBJECTP(LispVal *val) {
return EXTRACT_TAG(val) == LISP_OBJECT_TAG; return EXTRACT_TAG(val) == LISP_OBJECT_TAG;
} }
@ -111,15 +113,19 @@ static ALWAYS_INLINE bool OBJECT_MARKED_P(LispVal *val) {
return ((LispObject *) val)->gc.mark; return ((LispObject *) val)->gc.mark;
} }
// ONLY APPLIES TO THE CALLING THREAD
static ALWAYS_INLINE bool OBJECT_HAS_LOCAL_REFERENCE_P(LispVal *val) {
assert(OBJECTP(val));
LispObject *obj = val;
return tss_get(obj->gc.has_local_ref);
}
static ALWAYS_INLINE void SET_OBJECT_HAS_LOCAL_REFERENCE(LispVal *val, static ALWAYS_INLINE void SET_OBJECT_HAS_LOCAL_REFERENCE(LispVal *val,
bool has_local_ref) { bool has_local_ref) {
assert(OBJECTP(val)); assert(OBJECTP(val));
((LispObject *) val)->gc.has_local_ref = has_local_ref; LispObject *obj = val;
} tss_set(obj->gc.has_local_ref,
(void *) (uintptr_t) (has_local_ref ? 1 : 0));
static ALWAYS_INLINE bool OBJECT_HAS_LOCAL_REFERENCE_P(LispVal *val) {
assert(OBJECTP(val));
return ((LispObject *) val)->gc.has_local_ref;
} }
static ALWAYS_INLINE LispValType TYPE_OF(LispVal *val) { static ALWAYS_INLINE LispValType TYPE_OF(LispVal *val) {
@ -240,6 +246,7 @@ DEFOBJTYPE(Vector, VECTOR, VECTORP, {
Q##cname = Fintern(make_lisp_string(internal_Q##cname##_name, \ Q##cname = Fintern(make_lisp_string(internal_Q##cname##_name, \
internal_Q##cname##_name_len, \ internal_Q##cname##_name_len, \
false, false)); \ false, false)); \
lisp_gc_register_static_object(Q##cname); \
} }
#define REGISTER_GLOBAL_FUNCTION(cname) \ #define REGISTER_GLOBAL_FUNCTION(cname) \
{ \ { \

View File

@ -1,8 +1,10 @@
#include "function.h" #include "function.h"
#include "hashtable.h"
#include "lisp.h" #include "lisp.h"
#include "list.h" #include "list.h"
#include "read.h" #include "read.h"
#include "stack.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>

View File

@ -77,14 +77,14 @@ void parse_lambda_list(LambdaListParseResult *out, LispVal *list);
// This will cause the program to exit if an error occurs while parsing // This will cause the program to exit if an error occurs while parsing
// LISP_ARGS! // LISP_ARGS!
LispVal *make_builtin_function(LispVal *name, LispVal *(*func)(), LispVal *make_builtin_function(LispVal *name, LispVal *(*func)(void),
const char *lisp_args, size_t args_len, const char *lisp_args, size_t args_len,
LispVal *docstr); LispVal *docstr);
#define BUILTIN_FUNCTION_OBJ(cname) \ #define BUILTIN_FUNCTION_OBJ(cname) \
make_builtin_function( \ make_builtin_function( \
Q##cname, (LispVal * (*) ()) F##cname, internal_F##cname##_argstr, \ Q##cname, (LispVal * (*) (void) ) F##cname, \
internal_F##cname##_argstr_len, \ internal_F##cname##_argstr, internal_F##cname##_argstr_len, \
make_lisp_string(internal_F##cname##_docstr, \ make_lisp_string(internal_F##cname##_docstr, \
internal_F##cname##_docstr_len, false, false)) internal_F##cname##_docstr_len, false, false))
DECLARE_FUNCTION(funcall, (LispVal * func, LispVal *args)); DECLARE_FUNCTION(funcall, (LispVal * func, LispVal *args));

362
src/gc.c
View File

@ -1,5 +1,365 @@
#include "gc.h" #include "gc.h"
#include "function.h"
#include "hashtable.h"
#include "lisp.h" #include "lisp.h"
#include "list.h"
#include "stack.h"
void lisp_gc_register_object(void *obj) {} #include <stdlib.h>
bool lisp_doing_gc;
struct timespec total_gc_time;
size_t total_gc_count;
#define GC_OBJECTS_BLOCK_SIZE 128
struct GCObjectsBlock {
LispVal *objs[GC_OBJECTS_BLOCK_SIZE];
};
struct GCObjects {
size_t num_blocks;
size_t num_objs;
struct GCObjectsBlock **blocks;
};
static struct GCObjects gc_objects;
static struct GCObjects static_objects;
static void init_gc_objects(struct GCObjects *restrict objects) {
objects->num_blocks = 0;
objects->num_objs = 0;
objects->blocks = NULL;
}
static void gc_objects_register(struct GCObjects *restrict objects,
LispVal *obj) {
assert(OBJECTP(obj));
if (objects->num_blocks * GC_OBJECTS_BLOCK_SIZE == objects->num_objs) {
objects->blocks =
lisp_realloc(objects->blocks, sizeof(struct GCObjectsBlock *)
* ++objects->num_blocks);
objects->blocks[objects->num_blocks - 1] =
lisp_malloc(sizeof(struct GCObjectsBlock));
objects->blocks[objects->num_blocks - 1]->objs[0] = obj;
} else {
size_t block_idx = objects->num_objs / GC_OBJECTS_BLOCK_SIZE;
size_t small_idx = objects->num_objs % GC_OBJECTS_BLOCK_SIZE;
objects->blocks[block_idx]->objs[small_idx] = obj;
}
++objects->num_objs;
}
void lisp_init_gc(void) {
init_gc_objects(&gc_objects);
init_gc_objects(&static_objects);
}
void lisp_gc_register_object(void *obj) {
gc_objects_register(&gc_objects, obj);
}
// we don't have to put static objects into all_objects because they will never
// be GCed
void lisp_gc_register_static_object(void *obj) {
gc_objects_register(&static_objects, obj);
}
void free_object_process_stack(ObjectProcessStack *restrict stack) {
for (size_t i = 0; i < stack->num_blocks; ++i) {
lisp_free(stack->blocks[i]);
}
lisp_free(stack->blocks);
}
// Ensure STACK can hold at least NEW_ENTS more entries
static void ensure_object_process_stack_size(ObjectProcessStack *restrict stack,
size_t new_ents) {
size_t total_spaces = stack->num_blocks * OBJECT_PROCESS_STACK_BLOCK_SIZE;
size_t ents_left = total_spaces - stack->num_objs;
if (ents_left < new_ents) {
// grow the stack
size_t need_to_alloc =
(new_ents - ents_left) / OBJECT_PROCESS_STACK_BLOCK_SIZE;
if ((new_ents - ents_left) % OBJECT_PROCESS_STACK_BLOCK_SIZE != 0) {
++need_to_alloc;
}
stack->blocks = lisp_realloc(stack->blocks,
sizeof(struct ObjectProcessStackBlock *)
* (stack->num_blocks + need_to_alloc));
for (size_t i = 0; i < need_to_alloc; ++i) {
stack->blocks[i + stack->num_blocks] =
lisp_malloc(sizeof(struct ObjectProcessStackBlock));
}
stack->num_blocks += need_to_alloc;
}
}
// this does NOT check if there is actually space to push the object, so make
// sure to expand the stack with the above function before pushing
static ALWAYS_INLINE void
add_to_object_process_stack(ObjectProcessStack *restrict stack, void *obj) {
if (OBJECTP(obj)) {
size_t block_idx = stack->num_objs / OBJECT_PROCESS_STACK_BLOCK_SIZE;
size_t small_idx = stack->num_objs % OBJECT_PROCESS_STACK_BLOCK_SIZE;
stack->blocks[block_idx]->objs[small_idx] = obj;
++stack->num_objs;
}
}
void object_process_stack_push_held_objects(ObjectProcessStack *restrict stack,
void *obj) {
if (!OBJECTP(obj)) {
return;
}
switch (((LispObject *) obj)->type) {
case TYPE_CONS:
ensure_object_process_stack_size(stack, 2);
add_to_object_process_stack(stack, XCAR(obj));
add_to_object_process_stack(stack, XCDR(obj));
break;
case TYPE_SYMBOL: {
LispSymbol *sym = obj;
ensure_object_process_stack_size(stack, 4);
add_to_object_process_stack(stack, sym->name);
add_to_object_process_stack(stack, sym->value);
add_to_object_process_stack(stack, sym->function);
add_to_object_process_stack(stack, sym->plist);
break;
}
case TYPE_VECTOR: {
LispVector *vec = obj;
for (size_t i = 0; i < vec->length; ++i) {
if (OBJECTP(vec->data[i])) {
ensure_object_process_stack_size(stack, 1);
}
add_to_object_process_stack(stack, vec->data[i]);
}
break;
}
case TYPE_FUNCTION: {
LispFunction *fobj = obj;
ensure_object_process_stack_size(stack, 6);
add_to_object_process_stack(stack, fobj->name);
add_to_object_process_stack(stack, fobj->docstr);
add_to_object_process_stack(stack, fobj->args.req);
add_to_object_process_stack(stack, fobj->args.opt);
add_to_object_process_stack(stack, fobj->args.kw);
add_to_object_process_stack(stack, fobj->args.rest);
// TODO when other function types are added, record their held
// references
break;
}
case TYPE_HASH_TABLE: {
HT_FOREACH_INDEX(obj, i) {
if (OBJECTP(HASH_KEY(obj, i)) || OBJECTP(HASH_VALUE(obj, i))) {
ensure_object_process_stack_size(stack, 2);
}
add_to_object_process_stack(stack, HASH_KEY(obj, i));
add_to_object_process_stack(stack, HASH_VALUE(obj, i));
}
break;
}
case TYPE_STRING:
// holds no references break;
break;
case TYPE_FIXNUM:
case TYPE_FLOAT:
default:
abort();
}
}
void *object_process_stack_pop(ObjectProcessStack *restrict stack) {
assert(stack->num_objs > 0);
--stack->num_objs;
size_t block_idx = stack->num_objs / OBJECT_PROCESS_STACK_BLOCK_SIZE;
size_t small_idx = stack->num_objs % OBJECT_PROCESS_STACK_BLOCK_SIZE;
return stack->blocks[block_idx]->objs[small_idx];
}
void gc_recursively_mark_object(void *obj) {
if (!OBJECTP(obj) || OBJECT_MARKED_P(obj)) {
return;
}
SET_OBJECT_MARKED(obj, true);
ObjectProcessStack stack;
init_object_process_stack(&stack);
object_process_stack_push_held_objects(&stack, obj);
while (!OBJECT_PROCESS_STACK_EMPTY_P(&stack)) {
LispVal *top = object_process_stack_pop(&stack);
if (!OBJECT_MARKED_P(top)) {
SET_OBJECT_MARKED(top, true);
object_process_stack_push_held_objects(&stack, top);
}
}
free_object_process_stack(&stack);
}
// as we never delete static objects, all but the last block of the static
// objects array is filled
static void gc_mark_static_roots(void) {
for (size_t i = 0; i < static_objects.num_blocks - 1; ++i) {
for (size_t j = 0; j < GC_OBJECTS_BLOCK_SIZE; ++j) {
gc_recursively_mark_object(static_objects.blocks[i]->objs[j]);
}
}
for (size_t i = 0; i < static_objects.num_objs % GC_OBJECTS_BLOCK_SIZE;
++i) {
gc_recursively_mark_object(
static_objects.blocks[static_objects.num_blocks - 1]->objs[i]);
}
}
static void gc_mark_stack_local_refs(struct LocalReferences *restrict refs) {
size_t full_blocks = refs->num_refs / LOCAL_REFERENCES_BLOCK_LENGTH;
size_t last_block_len = refs->num_refs % LOCAL_REFERENCES_BLOCK_LENGTH;
for (size_t i = 0; i < full_blocks; ++i) {
for (size_t j = 0; j < LOCAL_REFERENCES_BLOCK_LENGTH; ++j) {
gc_recursively_mark_object(refs->blocks[i]->refs[j]);
}
}
for (size_t i = 0; i < last_block_len; ++i) {
gc_recursively_mark_object(refs->blocks[full_blocks]->refs[i]);
}
}
static void gc_mark_stack_frame(struct StackFrame *restrict frame) {
gc_recursively_mark_object(frame->name);
gc_recursively_mark_object(frame->args);
gc_recursively_mark_object(frame->lexenv);
gc_recursively_mark_object(frame->fobj);
gc_mark_stack_local_refs(&frame->local_refs);
}
static void gc_mark_and_compact_the_stack(void) {
size_t i;
for (i = 0; i < the_stack.depth; ++i) {
gc_mark_stack_frame(&the_stack.frames[i]);
}
for (; i < the_stack.first_clear_local_refs; ++i) {
compact_stack_frame(&the_stack.frames[i]);
}
the_stack.first_clear_local_refs = the_stack.depth;
}
static void free_object(LispVal *val) {
switch (((LispObject *) val)->type) {
case TYPE_HASH_TABLE: {
LispHashTable *ht = val;
lisp_free(ht->data);
break;
}
case TYPE_STRING: {
LispString *str = val;
if (str->owned) {
lisp_free(str->data);
}
break;
}
case TYPE_VECTOR: {
LispVector *vec = val;
lisp_free(vec->data);
break;
}
case TYPE_CONS:
case TYPE_SYMBOL:
case TYPE_FUNCTION:
// nothing to do
break;
case TYPE_FIXNUM:
case TYPE_FLOAT:
default:
abort();
}
lisp_release_object(val);
}
static ALWAYS_INLINE void do_sweep_object(LispGCStats *restrict stats,
LispVal *cur_obj,
size_t *restrict free_block,
size_t *restrict free_idx) {
#ifndef _NDEBUG
++stats->total_objects_searched;
#endif
if (!OBJECT_MARKED_P(cur_obj)) {
++stats->total_objects_cleaned;
free_object(cur_obj);
--gc_objects.num_objs;
} else {
SET_OBJECT_MARKED(cur_obj, false);
gc_objects.blocks[*free_block]->objs[*free_idx] = cur_obj;
if (++*free_idx == GC_OBJECTS_BLOCK_SIZE) {
++*free_block;
free_idx = 0;
}
}
}
static void gc_sweep_objects(LispGCStats *restrict stats) {
#ifdef _NDEBUG
stats->total_objects_searched = gc_objects.num_objs;
#else
stats->total_objects_searched = 0;
#endif
size_t free_block = 0;
size_t free_idx = 0;
size_t n_full_blocks = gc_objects.num_blocks - 1;
size_t small_idx = gc_objects.num_objs % GC_OBJECTS_BLOCK_SIZE;
if (small_idx == 0) {
++n_full_blocks;
}
for (size_t search_block = 0; search_block < n_full_blocks;
++search_block) {
for (size_t search_idx = 0; search_idx < GC_OBJECTS_BLOCK_SIZE;
++search_idx) {
LispVal *cur_obj =
gc_objects.blocks[search_block]->objs[search_idx];
do_sweep_object(stats, cur_obj, &free_block, &free_idx);
}
}
for (size_t i = 0; i < small_idx; ++i) {
do_sweep_object(stats,
gc_objects.blocks[gc_objects.num_blocks - 1]->objs[i],
&free_block, &free_idx);
}
// clean up the now empty blocks
size_t non_empty_blocks = gc_objects.num_objs / GC_OBJECTS_BLOCK_SIZE;
if (gc_objects.num_objs % GC_OBJECTS_BLOCK_SIZE != 0) {
++non_empty_blocks;
}
for (size_t i = non_empty_blocks; i < gc_objects.num_blocks; ++i) {
lisp_free(gc_objects.blocks[i]);
}
gc_objects.num_blocks = non_empty_blocks;
gc_objects.blocks = lisp_realloc(
gc_objects.blocks, sizeof(struct GCObjectsBlock *) * non_empty_blocks);
}
void lisp_gc_now(LispGCStats *restrict stats) {
lisp_doing_gc = true;
LispGCStats backup_stats;
if (!stats) {
stats = &backup_stats;
}
stats->total_objects_cleaned = 0;
struct timespec start_time;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_time);
gc_mark_static_roots();
gc_recursively_mark_object(obarray);
gc_mark_and_compact_the_stack();
gc_sweep_objects(stats);
struct timespec end_time;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_time);
sub_timespecs(&end_time, &start_time, &stats->ellapsed_time);
add_timespecs(&stats->ellapsed_time, &total_gc_time, &total_gc_time);
++total_gc_count;
lisp_doing_gc = false;
}
void debug_print_gc_stats(FILE *stream, const LispGCStats *stats) {
fprintf(stream, "Objects Searched: %zu\n", stats->total_objects_searched);
fprintf(stream, "Objects Cleaned: %zu\n", stats->total_objects_cleaned);
double time = stats->ellapsed_time.tv_sec * 1000
+ (stats->ellapsed_time.tv_nsec / 1000000.0);
fprintf(stream, "Time Ellapsed (ms): %f\n", time);
}

View File

@ -1,23 +1,67 @@
#ifndef INCLUDED_GC_H #ifndef INCLUDED_GC_H
#define INCLUDED_GC_H #define INCLUDED_GC_H
#include <stddef.h> #include "memory.h"
typedef struct GCEntry { #include <stddef.h>
void *obj; #include <stdio.h>
struct GCEntry *prev; #include <threads.h>
struct GCEntry *next;
} GCEntry; extern bool lisp_doing_gc;
extern struct timespec total_gc_time;
extern size_t total_gc_count;
typedef struct {
size_t total_objects_searched;
size_t total_objects_cleaned;
struct timespec ellapsed_time;
} LispGCStats;
typedef struct { typedef struct {
unsigned int mark : 1; unsigned int mark : 1;
unsigned int has_local_ref : 1; tss_t has_local_ref;
GCEntry *entry;
} ObjectGCInfo; } ObjectGCInfo;
void lisp_init_gc(void);
// the argument is a LispVal * // the argument is a LispVal *
void lisp_gc_register_object(void *obj); void lisp_gc_register_object(void *obj);
void lisp_gc_register_static_object(void *obj);
size_t lisp_gc_now(void); // note that the argument is restrict!
void lisp_gc_now(LispGCStats *restrict status);
#define OBJECT_PROCESS_STACK_BLOCK_SIZE 64
struct ObjectProcessStackBlock {
void *objs[OBJECT_PROCESS_STACK_BLOCK_SIZE];
};
typedef struct {
size_t num_blocks;
size_t num_objs;
struct ObjectProcessStackBlock **blocks;
} ObjectProcessStack;
static ALWAYS_INLINE bool
OBJECT_PROCESS_STACK_EMPTY_P(ObjectProcessStack *restrict stack) {
return !stack->num_objs;
}
static ALWAYS_INLINE void
init_object_process_stack(ObjectProcessStack *restrict stack) {
stack->num_blocks = 0;
stack->num_objs = 0;
stack->blocks = NULL;
}
void free_object_process_stack(ObjectProcessStack *restrict stack);
void object_process_stack_push_held_objects(ObjectProcessStack *restrict stack,
void *obj);
void *object_process_stack_pop(ObjectProcessStack *restrict stack);
void gc_recursively_mark_object(void *obj);
// Debug
void debug_print_gc_stats(FILE *stream, const LispGCStats *stats);
#endif #endif

View File

@ -6,14 +6,28 @@
#define GROWTH_THRESHOLD 0.5 #define GROWTH_THRESHOLD 0.5
#define GROWTH_FACTOR 2 #define GROWTH_FACTOR 2
static ALWAYS_INLINE bool BUCKET_EMPTY_P(struct HashTableBucket *b) {
return !b->key;
}
static ALWAYS_INLINE float TABLE_LOAD(LispHashTable *ht) { static ALWAYS_INLINE float TABLE_LOAD(LispHashTable *ht) {
return ((float) ht->count) / ht->size; return ((float) ht->count) / ht->size;
} }
LispVal *make_hash_table_no_gc(LispVal *hash_fn, LispVal *eq_fn) {
LispHashTable *obj =
lisp_alloc_object_no_gc(sizeof(LispHashTable), TYPE_HASH_TABLE);
obj->eq_fn = eq_fn;
obj->hash_fn = hash_fn;
obj->count = 0;
obj->size = INITIAL_SIZE;
obj->data = lisp_malloc0(sizeof(struct HashTableBucket) * obj->size);
return obj;
}
void release_hash_table_no_gc(LispVal *val) {
assert(HASH_TABLE_P(val));
LispHashTable *ht = val;
lisp_free(ht->data);
lisp_free(ht);
}
DEFUN(make_hash_table, "make-hash-table", (LispVal * hash_fn, LispVal *eq_fn), DEFUN(make_hash_table, "make-hash-table", (LispVal * hash_fn, LispVal *eq_fn),
"(hash-fn eq-fn)", "") { "(hash-fn eq-fn)", "") {
LispHashTable *obj = LispHashTable *obj =
@ -51,7 +65,7 @@ find_bucket_for_key(LispHashTable *ht, LispVal *key, uintptr_t hash) {
assert(TABLE_LOAD(ht) < 0.95f); assert(TABLE_LOAD(ht) < 0.95f);
for (uintptr_t i = hash % ht->size; true; i = (i + 1) % ht->size) { for (uintptr_t i = hash % ht->size; true; i = (i + 1) % ht->size) {
struct HashTableBucket *cb = &ht->data[i]; struct HashTableBucket *cb = &ht->data[i];
if (BUCKET_EMPTY_P(cb) if (HT_BUCKET_EMPTY_P(cb)
|| (cb->hash == hash && compare_keys(ht, key, cb->key))) { || (cb->hash == hash && compare_keys(ht, key, cb->key))) {
return cb; return cb;
} }
@ -59,13 +73,14 @@ find_bucket_for_key(LispHashTable *ht, LispVal *key, uintptr_t hash) {
} }
static void rehash(LispHashTable *ht, size_t new_size) { static void rehash(LispHashTable *ht, size_t new_size) {
ht->cache_bucket = NULL;
struct HashTableBucket *old_data = ht->data; struct HashTableBucket *old_data = ht->data;
size_t old_size = ht->size; size_t old_size = ht->size;
ht->size = new_size; ht->size = new_size;
ht->data = lisp_malloc0(sizeof(struct HashTableBucket) * new_size); ht->data = lisp_malloc0(sizeof(struct HashTableBucket) * new_size);
for (size_t i = 0; i < old_size; ++i) { for (size_t i = 0; i < old_size; ++i) {
struct HashTableBucket *cob = &old_data[i]; struct HashTableBucket *cob = &old_data[i];
if (!BUCKET_EMPTY_P(cob)) { if (!HT_BUCKET_EMPTY_P(cob)) {
struct HashTableBucket *nb = struct HashTableBucket *nb =
find_bucket_for_key(ht, cob->key, cob->hash); find_bucket_for_key(ht, cob->key, cob->hash);
nb->hash = cob->hash; nb->hash = cob->hash;
@ -84,37 +99,53 @@ static void maybe_rehash(LispHashTable *ht) {
// TODO type checking // TODO type checking
DEFUN(gethash, "gethash", (LispVal * ht, LispVal *key, LispVal *def), DEFUN(gethash, "gethash", (LispVal * ht, LispVal *key, LispVal *def),
"(ht key &optional def)", "") { "(ht key &optional def)", "") {
uintptr_t hash = hash_key_for_table(ht, key); LispHashTable *obj = ht;
struct HashTableBucket *b = find_bucket_for_key(ht, key, hash); if (obj->cache_bucket && key == obj->cache_bucket->key) {
return BUCKET_EMPTY_P(b) ? def : b->value; return obj->cache_bucket->value;
}
uintptr_t hash = hash_key_for_table(obj, key);
struct HashTableBucket *b = find_bucket_for_key(obj, key, hash);
obj->cache_bucket = b;
return HT_BUCKET_EMPTY_P(b) ? def : b->value;
} }
DEFUN(puthash, "puthash", (LispVal * ht, LispVal *key, LispVal *val), DEFUN(puthash, "puthash", (LispVal * ht, LispVal *key, LispVal *val),
"(ht key val)", "") { "(ht key val)", "") {
LispHashTable *obj = ht;
if (obj->cache_bucket && key == obj->cache_bucket->key) {
obj->cache_bucket->value = val;
return Qnil;
}
maybe_rehash(ht); maybe_rehash(ht);
uintptr_t hash = hash_key_for_table(ht, key); uintptr_t hash = hash_key_for_table(ht, key);
struct HashTableBucket *b = find_bucket_for_key(ht, key, hash); struct HashTableBucket *b = find_bucket_for_key(ht, key, hash);
if (BUCKET_EMPTY_P(b)) { if (HT_BUCKET_EMPTY_P(b)) {
b->hash = hash; b->hash = hash;
b->key = key; b->key = key;
} }
b->value = val; b->value = val;
++((LispHashTable *) ht)->count; ++((LispHashTable *) ht)->count;
obj->cache_bucket = b;
return Qnil; return Qnil;
} }
DEFUN(remhash, "remhash", (LispVal * ht, LispVal *key), "(ht key)", "") { DEFUN(remhash, "remhash", (LispVal * ht, LispVal *key), "(ht key)", "") {
LispHashTable *obj = ht;
uintptr_t hash = hash_key_for_table(ht, key); uintptr_t hash = hash_key_for_table(ht, key);
struct HashTableBucket *b = find_bucket_for_key(ht, key, hash); struct HashTableBucket *b;
if (BUCKET_EMPTY_P(b)) { if (obj->cache_bucket && obj->cache_bucket->key == key) {
b = obj->cache_bucket;
} else {
b = find_bucket_for_key(ht, key, hash);
}
if (HT_BUCKET_EMPTY_P(b)) {
return Qnil; return Qnil;
} }
b->key = NULL; b->key = NULL;
b->value = NULL; // just because
LispHashTable *tobj = ht; LispHashTable *tobj = ht;
--tobj->count; --tobj->count;
size_t k = hash % tobj->size; size_t k = hash % tobj->size;
for (size_t i = (k + 1) % tobj->size; !BUCKET_EMPTY_P(&tobj->data[i]); for (size_t i = (k + 1) % tobj->size; !HT_BUCKET_EMPTY_P(&tobj->data[i]);
i = (i + 1) % tobj->size) { i = (i + 1) % tobj->size) {
size_t target = tobj->data[i].hash % tobj->size; size_t target = tobj->data[i].hash % tobj->size;
if ((i > k && target >= k && target < i) if ((i > k && target >= k && target < i)
@ -123,10 +154,10 @@ DEFUN(remhash, "remhash", (LispVal * ht, LispVal *key), "(ht key)", "") {
tobj->data[k].key = tobj->data[i].key; tobj->data[k].key = tobj->data[i].key;
tobj->data[k].value = tobj->data[i].value; tobj->data[k].value = tobj->data[i].value;
tobj->data[i].key = NULL; tobj->data[i].key = NULL;
tobj->data[i].value = NULL;
k = i; k = i;
} }
} }
obj->cache_bucket = NULL;
return Qt; return Qt;
} }

View File

@ -15,8 +15,14 @@ DEFOBJTYPE(HashTable, HASH_TABLE, HASH_TABLE_P, {
struct HashTableBucket *data; struct HashTableBucket *data;
size_t size; size_t size;
size_t count; size_t count;
struct HashTableBucket *cache_bucket;
}); });
// makes no effort to ensure its values are not GCed!!!
LispVal *make_hash_table_no_gc(LispVal *hash_fn, LispVal *eq_fn);
void release_hash_table_no_gc(LispVal *val);
DECLARE_FUNCTION(make_hash_table, (LispVal * hash_fn, LispVal *eq_fn)); DECLARE_FUNCTION(make_hash_table, (LispVal * hash_fn, LispVal *eq_fn));
DECLARE_FUNCTION(gethash, (LispVal * ht, LispVal *key, LispVal *def)); DECLARE_FUNCTION(gethash, (LispVal * ht, LispVal *key, LispVal *def));
DECLARE_FUNCTION(puthash, (LispVal * ht, LispVal *key, LispVal *val)); DECLARE_FUNCTION(puthash, (LispVal * ht, LispVal *key, LispVal *val));
@ -28,4 +34,22 @@ static ALWAYS_INLINE size_t HASH_TABLE_COUNT(LispVal *ht) {
return ((LispHashTable *) ht)->count; return ((LispHashTable *) ht)->count;
} }
static ALWAYS_INLINE bool HT_BUCKET_EMPTY_P(struct HashTableBucket *b) {
return !b->key;
}
static ALWAYS_INLINE LispVal *HASH_KEY(LispVal *ht, size_t i) {
assert(HASH_TABLE_P(ht));
return ((LispHashTable *) ht)->data[i].key;
}
static ALWAYS_INLINE LispVal *HASH_VALUE(LispVal *ht, size_t i) {
assert(HASH_TABLE_P(ht));
return ((LispHashTable *) ht)->data[i].value;
}
#define HT_FOREACH_INDEX(ht, i) \
for (size_t i = 0; i < ((LispHashTable *) ht)->size; ++i) \
if (!HT_BUCKET_EMPTY_P(&((LispHashTable *) ht)->data[i]))
#endif #endif

View File

@ -6,23 +6,28 @@
LispVal *obarray; LispVal *obarray;
static void construct_manual_symbols() { static void construct_manual_symbols(void) {
// IMPORTANT: the symbols listed here need to also be set as special in // IMPORTANT: the symbols listed here need to also be set as special in
// gen-init-globals.awk // gen-init-globals.awk
Qnil = Fmake_symbol(LISP_LITSTR("nil")); Qnil = Fmake_symbol(LISP_LITSTR("nil"));
((LispSymbol *) Qnil)->function = Qnil; ((LispSymbol *) Qnil)->function = Qnil;
((LispSymbol *) Qnil)->plist = Qnil; ((LispSymbol *) Qnil)->plist = Qnil;
lisp_gc_register_static_object(Qnil);
Qt = Fmake_symbol(LISP_LITSTR("t")); Qt = Fmake_symbol(LISP_LITSTR("t"));
((LispSymbol *) Qt)->value = Qt; ((LispSymbol *) Qt)->value = Qt;
lisp_gc_register_static_object(Qt);
Qunbound = Fmake_symbol(LISP_LITSTR("unbound")); Qunbound = Fmake_symbol(LISP_LITSTR("unbound"));
((LispSymbol *) Qunbound)->value = Qunbound; ((LispSymbol *) Qunbound)->value = Qunbound;
((LispSymbol *) Qnil)->value = Qunbound; ((LispSymbol *) Qnil)->value = Qunbound;
lisp_gc_register_static_object(Qunbound);
Qhash_string = Fmake_symbol(LISP_LITSTR("hash-string")); Qhash_string = Fmake_symbol(LISP_LITSTR("hash-string"));
lisp_gc_register_static_object(Qhash_string);
Qstrings_equal = Fmake_symbol(LISP_LITSTR("strings-equal")); Qstrings_equal = Fmake_symbol(LISP_LITSTR("strings-equal"));
lisp_gc_register_static_object(Qstrings_equal);
} }
static void register_manual_symbols() { static void register_manual_symbols(void) {
#define INTERN(cname) \ #define INTERN(cname) \
Fputhash(obarray, ((LispSymbol *) Q##cname)->name, Q##cname); Fputhash(obarray, ((LispSymbol *) Q##cname)->name, Q##cname);
INTERN(nil); INTERN(nil);
@ -33,7 +38,8 @@ static void register_manual_symbols() {
#undef INTERN #undef INTERN
} }
void lisp_init() { void lisp_init(void) {
lisp_init_gc();
construct_manual_symbols(); construct_manual_symbols();
obarray = Fmake_hash_table(Qhash_string, Qstrings_equal); obarray = Fmake_hash_table(Qhash_string, Qstrings_equal);
// these call Fintern, so they need to have obarray constructed // these call Fintern, so they need to have obarray constructed
@ -47,7 +53,7 @@ void lisp_init() {
lisp_init_stack(); lisp_init_stack();
} }
void lisp_shutdown() {} void lisp_shutdown(void) {}
DEFUN(eval, "eval", (LispVal * form), "(form)", "") { DEFUN(eval, "eval", (LispVal * form), "(form)", "") {
if (!OBJECTP(form)) { if (!OBJECTP(form)) {

View File

@ -2,11 +2,11 @@
#define INCLUDED_LISP_H #define INCLUDED_LISP_H
#include "base.h" #include "base.h"
#include "function.h" #include "function.h" // IWYU pragma: export
#include "hashtable.h" #include "hashtable.h" // IWYU pragma: export
#include "lisp_string.h" #include "lisp_string.h" // IWYU pragma: export
#include "list.h" #include "list.h" // IWYU pragma: export
#include "stack.h" #include "stack.h" // IWYU pragma: export
#include <stdio.h> #include <stdio.h>

View File

@ -13,14 +13,22 @@ DEFUN(cool_func, "cool-func", (LispVal * a, LispVal *b), "(a &optional b)",
} }
int main(int argc, const char **argv) { int main(int argc, const char **argv) {
LispGCStats gc_stats;
lisp_init(); lisp_init();
REGISTER_GLOBAL_FUNCTION(cool_func); REGISTER_GLOBAL_FUNCTION(cool_func);
push_stack_frame(Qnil, Qnil, Qnil); push_stack_frame(Qnil, Qnil, Qnil);
ReadStream s; ReadStream s;
const char BUF[] = "(1 'a)"; const char BUF[] = "(cool-func 1 (cons 1 2))";
read_stream_init(&s, BUF, sizeof(BUF) - 1); read_stream_init(&s, BUF, sizeof(BUF) - 1);
LispVal *l = read(&s); LispVal *l = read(&s);
Ffuncall(Qcool_func, l); Feval(l);
push_stack_frame(Qnil, Qnil, Qnil);
for (size_t i = 0; i < 100; ++i) {
Fcons(MAKE_FIXNUM(0x1234), LISP_LITSTR("a"));
}
pop_stack_frame();
lisp_gc_now(&gc_stats);
debug_print_gc_stats(stdout, &gc_stats);
pop_stack_frame(); pop_stack_frame();
lisp_shutdown(); lisp_shutdown();
return 0; return 0;

View File

@ -5,13 +5,14 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <time.h>
// Geneal macros // Geneal macros
#ifndef __has_attribute #ifndef __has_attribute
# define __has_attribute(attr) 0 # define __has_attribute(attr) 0
#endif #endif
#if __has_attribute(always_inline) #if __has_attribute(always_inline) && defined(_NDEBUG)
# define ALWAYS_INLINE inline __attribute__((always_inline)) # define ALWAYS_INLINE inline __attribute__((always_inline))
#else #else
# define ALWAYS_INLINE inline # define ALWAYS_INLINE inline
@ -95,5 +96,31 @@ void *lisp_realloc(void *oldptr, size_t size);
void *lisp_malloc(size_t size); void *lisp_malloc(size_t size);
void *lisp_malloc0(size_t size); void *lisp_malloc0(size_t size);
void *lisp_aligned_alloc(size_t alignment, size_t size); void *lisp_aligned_alloc(size_t alignment, size_t size);
#define lisp_free free
// other useful things
static ALWAYS_INLINE void sub_timespecs(const struct timespec *t1,
const struct timespec *t2,
struct timespec *out) {
out->tv_sec = t1->tv_sec - t2->tv_sec;
int32_t nsec = t1->tv_nsec - t2->tv_nsec;
if (nsec < 0) {
--out->tv_sec;
nsec += 1000000000;
}
out->tv_nsec = nsec;
}
static ALWAYS_INLINE void add_timespecs(const struct timespec *t1,
const struct timespec *t2,
struct timespec *out) {
out->tv_sec = t1->tv_sec + t2->tv_sec;
int32_t nsec = t1->tv_nsec + t2->tv_nsec;
if (nsec > 1000000000) {
++out->tv_sec;
nsec -= 1000000000;
}
out->tv_nsec = nsec;
}
#endif #endif

View File

@ -1,12 +1,13 @@
#include "stack.h" #include "stack.h"
#include "hashtable.h"
#include "memory.h" #include "memory.h"
#include <assert.h> #include <assert.h>
struct LispStack the_stack; struct LispStack the_stack;
void lisp_init_stack() { void lisp_init_stack(void) {
the_stack.max_depth = DEFAULT_MAX_LISP_EVAL_DEPTH; the_stack.max_depth = DEFAULT_MAX_LISP_EVAL_DEPTH;
the_stack.depth = 0; the_stack.depth = 0;
the_stack.first_clear_local_refs = 0; the_stack.first_clear_local_refs = 0;
@ -23,13 +24,6 @@ void lisp_init_stack() {
the_stack.nogc_retval = Qnil; the_stack.nogc_retval = Qnil;
} }
static ALWAYS_INLINE void init_stack_frame(struct StackFrame *frame,
LispVal *name, LispVal *args) {
frame->name = name;
frame->args = args;
frame->lexenv = Qnil;
}
void push_stack_frame(LispVal *name, LispVal *fobj, LispVal *args) { void push_stack_frame(LispVal *name, LispVal *fobj, LispVal *args) {
assert(the_stack.depth < the_stack.max_depth); assert(the_stack.depth < the_stack.max_depth);
struct StackFrame *frame = &the_stack.frames[the_stack.depth++]; struct StackFrame *frame = &the_stack.frames[the_stack.depth++];
@ -37,6 +31,7 @@ void push_stack_frame(LispVal *name, LispVal *fobj, LispVal *args) {
frame->fobj = fobj; frame->fobj = fobj;
frame->args = args; frame->args = args;
frame->lexenv = Qnil; frame->lexenv = Qnil;
frame->local_refs.num_refs = 0;
} }
static void reset_local_refs(struct LocalReferences *refs) { static void reset_local_refs(struct LocalReferences *refs) {
@ -45,13 +40,11 @@ static void reset_local_refs(struct LocalReferences *refs) {
for (size_t i = 0; i < num_full_blocks; ++i) { for (size_t i = 0; i < num_full_blocks; ++i) {
for (size_t j = 0; j < LOCAL_REFERENCES_BLOCK_LENGTH; ++j) { for (size_t j = 0; j < LOCAL_REFERENCES_BLOCK_LENGTH; ++j) {
assert(OBJECTP(refs->blocks[i]->refs[j])); assert(OBJECTP(refs->blocks[i]->refs[j]));
// TODO recurse into object
SET_OBJECT_HAS_LOCAL_REFERENCE(refs->blocks[i]->refs[j], false); SET_OBJECT_HAS_LOCAL_REFERENCE(refs->blocks[i]->refs[j], false);
} }
} }
for (size_t i = 0; i < last_block_size; ++i) { for (size_t i = 0; i < last_block_size; ++i) {
assert(OBJECTP(refs->blocks[num_full_blocks]->refs[i])); assert(OBJECTP(refs->blocks[num_full_blocks]->refs[i]));
// TODO recurse into object
SET_OBJECT_HAS_LOCAL_REFERENCE(refs->blocks[num_full_blocks]->refs[i], SET_OBJECT_HAS_LOCAL_REFERENCE(refs->blocks[num_full_blocks]->refs[i],
false); false);
} }
@ -63,8 +56,7 @@ void pop_stack_frame(void) {
reset_local_refs(&frame->local_refs); reset_local_refs(&frame->local_refs);
} }
// return true if we allocated a block static void store_local_reference_in_frame(struct StackFrame *frame,
static bool store_local_reference_in_frame(struct StackFrame *frame,
LispVal *obj) { LispVal *obj) {
struct LocalReferences *refs = &frame->local_refs; struct LocalReferences *refs = &frame->local_refs;
size_t num_full_blocks = refs->num_refs / LOCAL_REFERENCES_BLOCK_LENGTH; size_t num_full_blocks = refs->num_refs / LOCAL_REFERENCES_BLOCK_LENGTH;
@ -76,21 +68,47 @@ static bool store_local_reference_in_frame(struct StackFrame *frame,
lisp_malloc(sizeof(struct LocalReferencesBlock)); lisp_malloc(sizeof(struct LocalReferencesBlock));
refs->blocks[refs->num_blocks - 1]->refs[0] = obj; refs->blocks[refs->num_blocks - 1]->refs[0] = obj;
refs->num_refs += 1; refs->num_refs += 1;
return true; the_stack.first_clear_local_refs = the_stack.depth;
} else { } else {
refs->blocks[num_full_blocks] refs->blocks[num_full_blocks]
->refs[refs->num_refs++ % LOCAL_REFERENCES_BLOCK_LENGTH] = obj; ->refs[refs->num_refs++ % LOCAL_REFERENCES_BLOCK_LENGTH] = obj;
return false; }
}
void add_local_reference_no_recurse(LispVal *obj) {
assert(the_stack.depth > 0);
if (OBJECTP(obj) && !OBJECT_HAS_LOCAL_REFERENCE_P(obj)) {
store_local_reference_in_frame(LISP_STACK_TOP(), obj);
} }
} }
void add_local_reference(LispVal *obj) { void add_local_reference(LispVal *obj) {
assert(the_stack.depth > 0); add_local_reference_no_recurse(obj);
if (OBJECTP(obj) && OBJECT_HAS_LOCAL_REFERENCE_P(obj)) { LispVal *seen_objs = make_hash_table_no_gc(Qnil, Qnil);
if (store_local_reference_in_frame(LISP_STACK_TOP(), obj)) { ObjectProcessStack stack;
the_stack.first_clear_local_refs = the_stack.depth; init_object_process_stack(&stack);
object_process_stack_push_held_objects(&stack, obj);
while (!OBJECT_PROCESS_STACK_EMPTY_P(&stack)) {
LispVal *top = object_process_stack_pop(&stack);
assert(OBJECTP(top));
if (!OBJECT_HAS_LOCAL_REFERENCE_P(obj)) {
store_local_reference_in_frame(LISP_STACK_TOP(), top);
SET_OBJECT_HAS_LOCAL_REFERENCE(obj, true);
}
if (NILP(Fgethash(seen_objs, obj, Qnil))) {
object_process_stack_push_held_objects(&stack, top);
Fputhash(seen_objs, obj, Qt);
} }
// TODO recurse into object
SET_OBJECT_HAS_LOCAL_REFERENCE(obj, true);
} }
free_object_process_stack(&stack);
release_hash_table_no_gc(seen_objs);
}
void compact_stack_frame(struct StackFrame *restrict frame) {
struct LocalReferences *restrict refs = &frame->local_refs;
for (size_t i = 1; i < refs->num_blocks; ++i) {
lisp_free(refs->blocks[i]);
}
lisp_realloc(refs->blocks, sizeof(struct LocalReferencesBlock *));
refs->num_blocks = 1;
} }

View File

@ -36,13 +36,17 @@ struct LispStack {
extern struct LispStack the_stack; extern struct LispStack the_stack;
static ALWAYS_INLINE struct StackFrame *LISP_STACK_TOP() { static ALWAYS_INLINE struct StackFrame *LISP_STACK_TOP(void) {
return the_stack.depth ? &the_stack.frames[the_stack.depth - 1] : NULL; return the_stack.depth ? &the_stack.frames[the_stack.depth - 1] : NULL;
} }
void lisp_init_stack(void); void lisp_init_stack(void);
void push_stack_frame(LispVal *name, LispVal *fobj, LispVal *args); void push_stack_frame(LispVal *name, LispVal *fobj, LispVal *args);
void pop_stack_frame(void); void pop_stack_frame(void);
void add_local_reference_no_recurse(LispVal *obj);
void add_local_reference(LispVal *obj); void add_local_reference(LispVal *obj);
// used by the GC
void compact_stack_frame(struct StackFrame *restrict frame);
#endif #endif