From a38fef78571d689483d7e4e05e528cd0639e92dd Mon Sep 17 00:00:00 2001 From: Alexander Rosenberg Date: Fri, 11 Jul 2025 02:53:57 +0900 Subject: [PATCH] Fix (I hope) reference counting and garbage collecting --- src/lisp.c | 438 ++++++++++++++++++++++++++++++++++++++++------------- src/lisp.h | 59 ++++---- src/main.c | 98 ++++++++---- src/read.c | 2 +- 4 files changed, 434 insertions(+), 163 deletions(-) diff --git a/src/lisp.c b/src/lisp.c index 2ffa520..bc88262 100644 --- a/src/lisp.c +++ b/src/lisp.c @@ -64,82 +64,16 @@ LispSymbol _Qt = { DEF_STATIC_SYMBOL(backquote, "`"); DEF_STATIC_SYMBOL(comma, ","); -void _internal_lisp_delete_object(LispVal *val) { - switch (TYPEOF(val)) { - case TYPE_INTEGER: - case TYPE_FLOAT: - lisp_free(val); - break; - case TYPE_STRING: { - LispString *str = (LispString *) val; - if (!str->is_static) { - lisp_free(str->data); - } - lisp_free(val); - } break; - case TYPE_SYMBOL: { - LispSymbol *sym = (LispSymbol *) val; - lisp_unref(sym->name); - lisp_unref(sym->plist); - lisp_unref(sym->function); - lisp_unref(sym->value); - lisp_free(val); - } break; - case TYPE_PAIR: - lisp_unref(((LispPair *) val)->head); - lisp_unref(((LispPair *) val)->tail); - lisp_free(val); - break; - case TYPE_VECTOR: { - LispVector *vec = (LispVector *) val; - for (size_t i = 0; i < vec->length; ++i) { - lisp_unref(vec->data[i]); - } - lisp_free(vec->data); - lisp_free(val); - } break; - case TYPE_FUNCTION: { - LispFunction *fn = (LispFunction *) val; - lisp_unref(fn->doc); - lisp_unref(fn->args); - lisp_unref(fn->rargs); - lisp_unref(fn->oargs); - lisp_unref(fn->rest_arg); - lisp_unref(fn->kwargs); - if (!fn->is_builtin) { - lisp_unref(fn->body); - } - lisp_unref(fn->lexenv); - lisp_free(val); - } break; - case TYPE_HASHTABLE: { - LispHashtable *tbl = (LispHashtable *) val; - for (size_t i = 0; i < tbl->table_size; ++i) { - struct HashtableBucket *cur = tbl->data[i]; - while (cur) { - lisp_unref(cur->key); - lisp_unref(cur->value); - struct HashtableBucket *next = cur->next; - lisp_free(cur); - cur = next; - } - } - lisp_free(tbl->data); - lisp_unref(tbl->eq_fn); - lisp_unref(tbl->hash_fn); - lisp_free(val); - } break; - case TYPE_USER_POINTER: { - LispUserPointer *ptr = (LispUserPointer *) val; - if (ptr->free_func) { - ptr->free_func(ptr->data); - } - lisp_free(val); - } break; - default: - abort(); - }; -} +struct GCRoot { + struct GCRoot *next; + struct GCRoot *prev; + LispVal *object; +}; + +static struct GCRoot *gc_roots = NULL; +static size_t bytes_allocated = 0; +static size_t last_gc = 0; +static bool is_doing_gc; void *lisp_malloc(size_t size) { return lisp_realloc(NULL, size); @@ -149,6 +83,9 @@ void *lisp_realloc(void *old_ptr, size_t size) { if (!size) { return NULL; } + if (!is_doing_gc) { + bytes_allocated += size; + } void *new_ptr = realloc(old_ptr, size); if (!new_ptr) { abort(); @@ -156,11 +93,300 @@ void *lisp_realloc(void *old_ptr, size_t size) { return new_ptr; } +void *lisp_ref(void *val) { + if (!STATICP(val)) { + ++((LispVal *) val)->ref_count; + } + return val; +} + +static void track_as_gc_root(LispVal *obj) { + if (!obj->gc_root) { + struct GCRoot *nr = lisp_malloc(sizeof(struct GCRoot)); + nr->object = obj; + nr->next = gc_roots; + nr->prev = NULL; + if (gc_roots) { + gc_roots->prev = nr; + } + gc_roots = nr; + obj->gc_root = nr; + } +} + +void *lisp_float_ref(void *val) { + if (LISPVAL(val)->ref_count > 0) { + --LISPVAL(val)->ref_count; + } + return val; +} + +struct ToCheck { + struct ToCheck *next; + LispVal *obj; +}; + +#define CHECK(no) \ + if (!STATICP(no)) { \ + struct ToCheck *new = lisp_malloc(sizeof(struct ToCheck)); \ + new->obj = no; \ + new->next = queue; \ + queue = new; \ + } + +static struct ToCheck *check_object(LispVal *obj, struct ToCheck *queue) { + switch (TYPEOF(obj)) { + case TYPE_INTEGER: + case TYPE_FLOAT: + case TYPE_STRING: + case TYPE_USER_POINTER: + // can't hold references, do nothing + break; + case TYPE_SYMBOL: { + LispSymbol *sym = (LispSymbol *) obj; + CHECK(LISPVAL(sym->name)); + CHECK(LISPVAL(sym->function)); + CHECK(LISPVAL(sym->value)); + CHECK(LISPVAL(sym->plist)); + } break; + case TYPE_PAIR: + CHECK(((LispPair *) obj)->head); + CHECK(((LispPair *) obj)->tail); + break; + case TYPE_VECTOR: { + LispVector *vec = (LispVector *) obj; + for (size_t i = 0; i > vec->length; ++i) { + CHECK(vec->data[i]); + } + } break; + case TYPE_FUNCTION: { + LispFunction *func = (LispFunction *) obj; + CHECK(func->body); + CHECK(func->args); + CHECK(func->doc); + CHECK(func->kwargs); + CHECK(func->oargs); + CHECK(func->rargs); + CHECK(func->lexenv); + CHECK(func->rest_arg); + } break; + case TYPE_HASHTABLE: { + HASHTABLE_FOREACH(key, val, obj, { + CHECK(key); + CHECK(val); + }); + } break; + default: + abort(); + } + return queue; +} + +static void lisp_free_object(LispVal *val, bool is_static) { + struct GCRoot *root = val->gc_root; + if (!STATICP(val) && (root = val->gc_root)) { + if (root->next) { + root->next->prev = root->prev; + } + if (root->prev) { + root->prev->next = root->next; + } else { + gc_roots = gc_roots->next; + if (gc_roots) { + gc_roots->prev = NULL; + } + } + lisp_free(root); + } + switch (TYPEOF(val)) { + case TYPE_INTEGER: + case TYPE_FLOAT: + case TYPE_SYMBOL: + case TYPE_PAIR: + case TYPE_FUNCTION: + break; + case TYPE_STRING: { + LispString *str = (LispString *) val; + if (!str->is_static) { + lisp_free(str->data); + } + } break; + case TYPE_VECTOR: { + LispVector *vec = (LispVector *) val; + lisp_free(vec->data); + } break; + case TYPE_HASHTABLE: { + LispHashtable *tbl = (LispHashtable *) val; + for (size_t i = 0; i < tbl->table_size; ++i) { + struct HashtableBucket *cur = tbl->data[i]; + while (cur) { + struct HashtableBucket *next = cur->next; + lisp_free(cur); + cur = next; + } + } + lisp_free(tbl->data); + } break; + case TYPE_USER_POINTER: { + LispUserPointer *ptr = (LispUserPointer *) val; + if (ptr->free_func) { + ptr->free_func(ptr->data); + } + } break; + default: + abort(); + }; + if (!is_static) { + lisp_free(val); + } +} + +static bool check_gc_root(struct GCRoot *root) { + LispVal *found = make_lisp_hashtable(Qnil, Qnil); + struct ToCheck *queue = NULL; + CHECK(root->object); + size_t num_at_zero = 0; + bool first_pass = true; + while (queue) { + LispVal *obj = queue->obj; + struct ToCheck *next = queue->next; + lisp_free(queue); + queue = next; + LispVal *index = Fgethash(found, obj, Qnil); + if (!NILP(index)) { + if (((LispInteger *) index)->value > 0 + && !--((LispInteger *) index)->value) { + ++num_at_zero; + } + // we already searched this object, don't dereference everything + // again! + continue; + } else { + Fputhash(found, obj, + make_lisp_integer(obj->ref_count + && obj->ref_count - !first_pass)); + } + first_pass = false; + queue = check_object(obj, queue); + } + if (num_at_zero == ((LispHashtable *) found)->count) { + LispHashtable *tbl = (LispHashtable *) found; + for (size_t i = 0; i < tbl->count; ++i) { + for (struct HashtableBucket *bucket = tbl->data[i]; bucket; + bucket = bucket->next) { + lisp_free_object(bucket->key, false); + bucket->key = Qnil; + } + } + } + lisp_unref(found); + return false; +} + +void garbage_collect() { + if (is_doing_gc) { + return; + } + is_doing_gc = true; + struct GCRoot *root = gc_roots; + while (root) { + if (check_gc_root(root)) { + root = gc_roots; + } else { + root = root->next; + } + } + is_doing_gc = false; +} + +struct ToFree { + struct ToFree *next; + LispVal *obj; +}; + +#define FREE(l, no) \ + { \ + struct ToFree *new = lisp_malloc(sizeof(struct ToFree)); \ + new->obj = no; \ + new->next = l; \ + l = new; \ + } + +static struct ToFree *lisp_unref_recursive(LispVal *val) { + struct ToFree *to_free = NULL; + struct ToCheck *queue = NULL; + CHECK(val); + val->ref_count = 0; // prevent double free + FREE(to_free, val); + while (queue) { + LispVal *obj = queue->obj; + struct ToCheck *next = queue->next; + lisp_free(queue); + queue = next; + if (STATICP(obj) || obj->finalizing) { + continue; + } else if (obj->ref_count >= 2) { + --obj->ref_count; + } else { + if (obj->ref_count) { + --obj->ref_count; + // don't check multiple times + FREE(to_free, obj); + } + obj->finalizing = true; + queue = check_object(obj, queue); + } + } + return to_free; +} + +#undef CHECK +#undef FREE + +static void *lisp_unref_extended(void *val, bool even_if_static) { + if (!even_if_static && STATICP(val)) { + return val; + } else if (LISPVAL(val)->ref_count > 1) { + --LISPVAL(val)->ref_count; + if (!is_doing_gc) { + track_as_gc_root(val); + } + if (bytes_allocated - last_gc > GC_EVERY_N_BYTES) { + garbage_collect(); + last_gc = bytes_allocated; + } + return val; + } else { + struct ToFree *to_free = lisp_unref_recursive(val); + while (to_free) { + LispVal *obj = to_free->obj; + struct ToFree *next = to_free->next; + lisp_free(to_free); + to_free = next; + lisp_free_object(obj, false); + } + return Qnil; + } +} + +void *lisp_unref(void *val) { + return lisp_unref_extended(val, false); +} + +void lisp_unref_double_ptr(void **val) { + lisp_unref(*val); +} + +#define CONSTRUCT_OBJECT(var, Type, TYPE) \ + Type *var = lisp_malloc(sizeof(Type)); \ + var->type = TYPE; \ + var->ref_count = 0; \ + var->gc_root = NULL; \ + var->finalizing = false; + LispVal *make_lisp_string(const char *data, size_t length, bool take, bool is_static) { - LispString *self = lisp_malloc(sizeof(LispString)); - self->type = TYPE_STRING; - self->ref_count = 0; + CONSTRUCT_OBJECT(self, LispString, TYPE_STRING); if (take) { self->data = (char *) data; } else { @@ -188,9 +414,7 @@ LispVal *sprintf_lisp(const char *format, ...) { } LispVal *make_lisp_symbol(LispVal *name) { - LispSymbol *self = lisp_malloc(sizeof(LispSymbol)); - self->type = TYPE_SYMBOL; - self->ref_count = 0; + CONSTRUCT_OBJECT(self, LispSymbol, TYPE_SYMBOL); self->name = (LispString *) lisp_ref(name); self->plist = Qnil; self->function = Qunbound; @@ -200,16 +424,14 @@ LispVal *make_lisp_symbol(LispVal *name) { } LispVal *make_lisp_pair(LispVal *head, LispVal *tail) { - LispPair *self = lisp_malloc(sizeof(LispPair)); - self->type = TYPE_PAIR; - self->ref_count = 0; + CONSTRUCT_OBJECT(self, LispPair, TYPE_PAIR); self->head = lisp_ref(head); self->tail = lisp_ref(tail); return LISPVAL(self); } LispVal *make_lisp_integer(intmax_t value) { - LispInteger *self = lisp_malloc(sizeof(LispInteger)); + CONSTRUCT_OBJECT(self, LispInteger, TYPE_INTEGER); self->type = TYPE_INTEGER; self->ref_count = 0; self->value = value; @@ -217,17 +439,13 @@ LispVal *make_lisp_integer(intmax_t value) { } LispVal *make_lisp_float(long double value) { - LispFloat *self = lisp_malloc(sizeof(LispFloat)); - self->type = TYPE_FLOAT; - self->ref_count = 0; + CONSTRUCT_OBJECT(self, LispFloat, TYPE_FLOAT); self->value = value; return LISPVAL(self); } LispVal *make_lisp_vector(LispVal **data, size_t length) { - LispVector *self = lisp_malloc(sizeof(LispVector)); - self->type = TYPE_VECTOR; - self->ref_count = 0; + CONSTRUCT_OBJECT(self, LispVector, TYPE_VECTOR); self->data = data; self->length = length; return LISPVAL(self); @@ -435,9 +653,7 @@ malformed: LispVal *make_lisp_function(LispVal *args, LispVal *doc, LispVal *lexenv, LispVal *body, bool is_macro) { - LispFunction *self = lisp_malloc(sizeof(LispFunction)); - self->type = TYPE_FUNCTION; - self->ref_count = 0; + CONSTRUCT_OBJECT(self, LispFunction, TYPE_FUNCTION); self->is_builtin = false; self->is_macro = is_macro; self->args = Qnil; @@ -457,9 +673,7 @@ LispVal *make_lisp_function(LispVal *args, LispVal *doc, LispVal *lexenv, } LispVal *make_lisp_hashtable(LispVal *eq_fn, LispVal *hash_fn) { - LispHashtable *self = lisp_malloc(sizeof(LispHashtable)); - self->type = TYPE_HASHTABLE; - self->ref_count = 0; + CONSTRUCT_OBJECT(self, LispHashtable, TYPE_HASHTABLE); self->table_size = LISP_HASHTABLE_INITIAL_SIZE; self->data = lisp_malloc(sizeof(struct HashtableBucket *) * self->table_size); @@ -471,9 +685,7 @@ LispVal *make_lisp_hashtable(LispVal *eq_fn, LispVal *hash_fn) { } LispVal *make_user_pointer(void *data, void (*free_func)(void *)) { - LispUserPointer *self = lisp_malloc(sizeof(LispUserPointer)); - self->type = TYPE_USER_POINTER; - self->ref_count = 0; + CONSTRUCT_OBJECT(self, LispUserPointer, TYPE_USER_POINTER); self->data = data; self->free_func = free_func; return LISPVAL(self); @@ -906,6 +1118,15 @@ DEF_STATIC_SYMBOL(circular_error, "circular-error"); DEF_STATIC_SYMBOL(malformed_lambda_list_error, "malformed-lambda-list-error"); DEF_STATIC_SYMBOL(argument_error, "argument-error"); +struct StaticReference *static_references = NULL; + +void add_static_reference(LispVal *obj) { + struct StaticReference *sr = lisp_malloc(sizeof(struct StaticReference)); + sr->next = static_references; + sr->obj = obj; + static_references = sr; +} + LispVal *Vobarray = Qnil; void lisp_init() { @@ -941,7 +1162,7 @@ void lisp_init() { if, "(cond then &rest else)", "Evaluate THEN if COND is non-nil, otherwise evaluate ELSE."); REGISTER_FUNCTION( - setq, "(&rest args)", + setq, "(var val)", "Set each of a number of variables to their respective values."); REGISTER_FUNCTION(progn, "(&rest forms)", "Evaluate each of FORMS."); REGISTER_FUNCTION(symbol_function, "(sym &opt resolve)", ""); @@ -951,10 +1172,21 @@ void lisp_init() { REGISTER_FUNCTION(eval, "(expr)", "Evaluate the lisp expression EXPR"); REGISTER_FUNCTION(read, "(source)", "Read and return the next s-expr from SOURCE."); + REGISTER_FUNCTION(eq, "(obj1 obj2)", + "Return non-nil if OBJ1 and OBJ2 are equal"); } void lisp_shutdown() { + while (static_references) { + struct StaticReference *next = static_references->next; + lisp_unref_extended(static_references->obj, false); + lisp_free(static_references); + static_references = next; + } + UNREF_INPLACE(Vobarray); + + garbage_collect(); } void register_static_function(LispVal *func) {} @@ -1523,7 +1755,7 @@ static void debug_dump_real(FILE *stream, void *obj, bool first) { fputc(']', stream); } break; case TYPE_FUNCTION: - if (((LispFunction *) obj)->builtin) { + if (((LispFunction *) obj)->is_builtin) { fprintf(stream, "", (uintmax_t) obj); } else { fprintf(stream, "", (uintmax_t) obj); diff --git a/src/lisp.h b/src/lisp.h index 95bd771..2d9d734 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -41,7 +41,9 @@ extern struct _TypeNameEntry LISP_TYPE_NAMES[N_LISP_TYPES]; #define LISP_OBJECT_HEADER \ LispType type; \ - ptrdiff_t ref_count; + void *gc_root; \ + ptrdiff_t ref_count; \ + bool finalizing; typedef struct { LISP_OBJECT_HEADER; @@ -270,7 +272,7 @@ inline static bool NUMBERP(LispVal *v) { { \ LispHashtable *__hashtable_foreach_table = (LispHashtable *) table; \ for (size_t __hashtable_foreach_i = 0; \ - __hashtable_foreach_i < __hashtable_foreach_table->count; \ + __hashtable_foreach_i < __hashtable_foreach_table->table_size; \ ++__hashtable_foreach_i) { \ struct HashtableBucket *__hashtable_foreach_cur = \ __hashtable_foreach_table->data[__hashtable_foreach_i]; \ @@ -292,43 +294,21 @@ inline static bool NUMBERP(LispVal *v) { // ############################# // # Allocation and references # // ############################# + +#define GC_EVERY_N_BYTES 1024 * 80 void *lisp_malloc(size_t size); void *lisp_realloc(void *old_ptr, size_t size); #define lisp_free free -inline static void *lisp_ref(void *val) { - if (!STATICP(val)) { - ++((LispVal *) val)->ref_count; - } - return val; -} - -inline static void *lisp_float_ref(void *val) { - if (LISPVAL(val)->ref_count > 0) { - --LISPVAL(val)->ref_count; - } - return val; -} - -void _internal_lisp_delete_object(LispVal *val); -inline static void *lisp_unref(void *val) { - if (STATICP(val)) { - return val; - } else if (LISPVAL(val)->ref_count > 1) { - --LISPVAL(val)->ref_count; - return val; - } else { - _internal_lisp_delete_object(val); - return Qnil; - } -} +void *lisp_ref(void *val); +void *lisp_float_ref(void *val); +void garbage_collect(); +void *lisp_unref(void *val); #define UNREF_INPLACE(variable) \ { \ variable = lisp_unref(variable); \ } -inline static void lisp_unref_double_ptr(void **val) { - lisp_unref(*val); -} +void lisp_unref_double_ptr(void **val); #define IGNORE_REF(val) (lisp_unref(lisp_ref(val))) // ################ @@ -481,10 +461,22 @@ extern LispVal *Qargument_error; Fthrow(Qtype_error, Qnil); \ } +struct StaticReference { + struct StaticReference *next; + LispVal *obj; +}; + +extern struct StaticReference *static_references; + +void add_static_reference(LispVal *obj); + extern LispVal *Vobarray; -#define REGISTER_SYMBOL(sym) \ - Fputhash(Vobarray, LISPVAL(((LispSymbol *) Q##sym)->name), Q##sym) +#define REGISTER_SYMBOL(sym) \ + { \ + Fputhash(Vobarray, LISPVAL(((LispSymbol *) Q##sym)->name), Q##sym); \ + add_static_reference(Q##sym); \ + } #define REGISTER_STATIC_FUNCTION(obj, args, docstr) \ ((LispFunction *) (obj))->doc = STATIC_STRING(docstr); \ { \ @@ -492,6 +484,7 @@ extern LispVal *Vobarray; lisp_ref(src); \ set_function_args((LispFunction *) (obj), Fread(src)); \ lisp_unref(src); \ + add_static_reference(obj); \ } #define REGISTER_FUNCTION(fn, args, docstr) \ REGISTER_SYMBOL(fn); \ diff --git a/src/main.c b/src/main.c index 4068a9a..eb4391b 100644 --- a/src/main.c +++ b/src/main.c @@ -88,30 +88,76 @@ int main(int argc, const char **argv) { REGISTER_STATIC_FUNCTION(Ftoplevel_error_handler_function, "(e)", ""); REGISTER_STATIC_FUNCTION(Ftoplevel_exit_handler_function, "(e)", ""); size_t pos = 0; - WITH_PUSH_FRAME(Qtoplevel, Qnil, false, { - the_stack->hidden = true; - LispVal *err_var = INTERN_STATIC("err-var"); - Fputhash( - the_stack->handlers, Qt, - // simply call the above function - const_list(3, err_var, Ftoplevel_error_handler_function, err_var)); - Fputhash( - the_stack->handlers, Qshutdown_signal, - // simply call the above function - const_list(3, err_var, Ftoplevel_exit_handler_function, err_var)); - Fputhash(the_stack->handlers, Qeof_error, - // ignore - Fpair(Qnil, Qnil)); - while (pos < file_len) { - LispVal *tv; - WITH_PUSH_FRAME(Qtoplevel_read, Qnil, false, { - pos += read_from_buffer(buffer + pos, file_len - pos, &tv); - }); - WITH_CLEANUP(tv, { - IGNORE_REF(Feval(tv)); // - }) - } - }); - lisp_shutdown(); - return exit_status; + // WITH_PUSH_FRAME(Qtoplevel, Qnil, false, { + // the_stack->hidden = true; + // LispVal *err_var = INTERN_STATIC("err-var"); + // Fputhash( + // the_stack->handlers, Qt, + // // simply call the above function + // const_list(3, err_var, Ftoplevel_error_handler_function, + // err_var)); + // Fputhash( + // the_stack->handlers, Qshutdown_signal, + // // simply call the above function + // const_list(3, err_var, Ftoplevel_exit_handler_function, + // err_var)); + // Fputhash(the_stack->handlers, Qeof_error, + // // ignore + // Fpair(Qnil, Qnil)); + // while (pos < file_len) { + // LispVal *tv; + // WITH_PUSH_FRAME(Qtoplevel_read, Qnil, false, { + // pos += read_from_buffer(buffer + pos, file_len - pos, &tv); + // }); + // WITH_CLEANUP(tv, { + // IGNORE_REF(Feval(tv)); // + // }); + // } + // }); + stack_enter(Qtoplevel, (((LispVal *) (&_Qnil))), 0); + if (_setjmp(the_stack->start) == 0) { + { + the_stack->hidden = 1; + LispVal *err_var = + (_internal_INTERN_STATIC(("err-var"), sizeof("err-var") - 1)); + Fputhash( + the_stack->handlers, (((LispVal *) (&_Qt))), + const_list(3, err_var, + ((LispVal *) (&_Ftoplevel_error_handler_function)), + err_var)); + Fputhash( + the_stack->handlers, Qshutdown_signal, + const_list(3, err_var, + ((LispVal *) (&_Ftoplevel_exit_handler_function)), + err_var)); + Fputhash(the_stack->handlers, Qeof_error, + Fpair((((LispVal *) (&_Qnil))), (((LispVal *) (&_Qnil))))); + while (pos < file_len) { + LispVal *tv; + stack_enter(Qtoplevel_read, (((LispVal *) (&_Qnil))), 0); + if (_setjmp(the_stack->start) == 0) { + { + pos += + read_from_buffer(buffer + pos, file_len - pos, &tv); + } + } + stack_leave(); + ; + lisp_ref(tv); + { + void *__with_cleanup_cleanup = register_cleanup( + (lisp_cleanup_func_t) &lisp_unref_double_ptr, &(tv)); + {{(lisp_unref(lisp_ref(Feval(tv)))); + } + }; + cancel_cleanup(__with_cleanup_cleanup); + lisp_unref(tv); + }; + } +} +} +stack_leave(); +; +lisp_shutdown(); +return exit_status; } diff --git a/src/read.c b/src/read.c index 49aa695..7e85701 100644 --- a/src/read.c +++ b/src/read.c @@ -79,8 +79,8 @@ static LispVal *read_list(struct ReadState *state) { int c; while ((c = peekc(state)) != ')') { if (c == EOS) { - EOF_ERROR(state); UNREF_INPLACE(list); + EOF_ERROR(state); return Qnil; } LispVal *elt = read_internal(state);