Peter McGoron
4f7847b2c0
The API is now hidden behind functions. The GC struct can have an unstable layout without affecting any compiled binaries (users of Universal Service or collectors hidden behind the API). The collectors can be called directly if desired. The API now allows for different allocation flags. These will be used in future extensions (like weak pointers). For now none are used. Tests are compiled for each collector. I can't think of a good solution that will encompass everything I want to write, but for now this will work on POSIX systems.
181 lines
4.1 KiB
C
181 lines
4.1 KiB
C
/* Copyright (C) 2024 Peter McGoron
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this program. If not, see
|
|
* <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* TODO: add support for freestanding */
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
|
|
#include "uns.h"
|
|
|
|
/** Internal GC state.
|
|
*
|
|
* The exact structure is hidden from implementations. The function API
|
|
* is stable(r).
|
|
*/
|
|
struct uns_gc {
|
|
/** Context pointer used by specific collector implementations. */
|
|
void *ctx;
|
|
|
|
/** Head of the roots linked list. */
|
|
struct uns_ctr *roots;
|
|
|
|
/** Function called when OOM occurs. If not specified, the library
|
|
* will print to standard error and exit.
|
|
*/
|
|
void (*oom)(struct uns_gc *);
|
|
|
|
/** Implementations of allocator functions. */
|
|
|
|
void (*deinit)(Uns_GC);
|
|
int (*collect)(Uns_GC);
|
|
|
|
void *(*alloc)(Uns_GC, size_t, enum uns_bytes_type);
|
|
void *(*alloc_rec)(Uns_GC, size_t, enum uns_record_type);
|
|
void (*free)(Uns_GC, Uns_ptr);
|
|
|
|
/** Generic implementation of record access. Values in a record may
|
|
* have unused bits filled in so raw reads may not work correctly.
|
|
* Values may also not be aligned correctly. The `get` and `set`
|
|
* functions will handle all of this so the caller gets what is
|
|
* expected.
|
|
*/
|
|
|
|
void (*set)(Uns_ptr, size_t, enum uns_fld_type, void *);
|
|
void *(*get)(Uns_ptr, size_t, enum uns_fld_type *);
|
|
};
|
|
const size_t uns_gc_size = sizeof(struct uns_gc);
|
|
|
|
void uns_gc_zero(Uns_GC gc)
|
|
{
|
|
gc->ctx = NULL;
|
|
gc->roots = NULL;
|
|
gc->oom = NULL;
|
|
gc->deinit = NULL;
|
|
gc->collect = NULL;
|
|
gc->alloc = NULL;
|
|
gc->alloc_rec = NULL;
|
|
gc->free = NULL;
|
|
gc->set = NULL;
|
|
gc->get = NULL;
|
|
}
|
|
|
|
/** Wrapper to define a setter for the generic state struct. */
|
|
#define SETTER(name, arg) \
|
|
void uns_set_##name(Uns_GC gc, arg) {gc->name = name;}
|
|
|
|
SETTER(deinit, void(*deinit)(Uns_GC))
|
|
SETTER(oom, void(*oom)(Uns_GC))
|
|
SETTER(collect, int(*collect)(Uns_GC))
|
|
SETTER(alloc, void*(*alloc)(Uns_GC, size_t, enum uns_bytes_type))
|
|
SETTER(alloc_rec, void*(*alloc_rec)(Uns_GC, size_t, enum uns_record_type))
|
|
SETTER(free, void(*free)(Uns_GC, Uns_ptr))
|
|
SETTER(set, void(*set)(Uns_ptr, size_t, enum uns_fld_type, void*))
|
|
SETTER(get, void*(*get)(Uns_ptr, size_t, enum uns_fld_type *))
|
|
#undef SETTER
|
|
|
|
void uns_deinit(Uns_GC gc)
|
|
{
|
|
if (gc->deinit)
|
|
gc->deinit(gc);
|
|
uns_gc_zero(gc);
|
|
}
|
|
|
|
int uns_collect(Uns_GC gc)
|
|
{
|
|
if (gc->collect)
|
|
return gc->collect(gc);
|
|
abort();
|
|
}
|
|
|
|
void *uns_alloc(Uns_GC gc, size_t bytes, enum uns_bytes_type typ)
|
|
{
|
|
if (gc->alloc)
|
|
return gc->alloc(gc, bytes, typ);
|
|
abort();
|
|
}
|
|
|
|
void *uns_alloc_rec(Uns_GC gc, size_t bytes, enum uns_record_type typ)
|
|
{
|
|
if (gc->alloc_rec)
|
|
return gc->alloc_rec(gc, bytes, typ);
|
|
abort();
|
|
}
|
|
|
|
void uns_free(Uns_GC gc, Uns_ptr p)
|
|
{
|
|
if (gc->free)
|
|
gc->free(gc, p);
|
|
}
|
|
|
|
void *uns_get(Uns_GC gc, Uns_ptr rec, size_t ind, enum uns_fld_type *typ)
|
|
{
|
|
if (gc->get)
|
|
return gc->get(rec, ind, typ);
|
|
else
|
|
abort();
|
|
}
|
|
|
|
void uns_set(Uns_GC gc, Uns_ptr rec, size_t ind, enum uns_fld_type typ, void *val)
|
|
{
|
|
if (gc->set)
|
|
gc->set(rec, ind, typ, val);
|
|
else
|
|
abort();
|
|
}
|
|
|
|
void uns_on_oom(Uns_GC gc)
|
|
{
|
|
if (gc->oom) {
|
|
gc->oom(gc);
|
|
} else {
|
|
fprintf(stderr, "oom\n");
|
|
abort();
|
|
}
|
|
}
|
|
|
|
void uns_root_add(Uns_GC gc, struct uns_ctr *root)
|
|
{
|
|
root->prev = NULL;
|
|
root->next = gc->roots;
|
|
|
|
if (gc->roots)
|
|
gc->roots->prev = root;
|
|
gc->roots = root;
|
|
}
|
|
|
|
void uns_root_remove(Uns_GC gc, struct uns_ctr *root)
|
|
{
|
|
if (root->prev) {
|
|
root->prev->next = root->next;
|
|
}
|
|
if (root->next) {
|
|
root->next->prev = root->prev;
|
|
}
|
|
|
|
if (gc->roots == root) {
|
|
assert(!root->prev);
|
|
gc->roots = root->next;
|
|
}
|
|
|
|
root->prev = root->next = NULL;
|
|
}
|
|
|
|
struct uns_ctr *uns_roots(Uns_GC gc)
|
|
{
|
|
return gc->roots;
|
|
}
|