Major API reorganization, test infrastructure

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.
This commit is contained in:
Peter McGoron 2024-07-08 19:58:22 -04:00
parent 70df63c28e
commit 4f7847b2c0
22 changed files with 1084 additions and 18531 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
*.o
*.so
*.test
tests.makefile

View File

@ -1,21 +1,48 @@
.PHONY: test clean
COMMON_OBJECTS=uns.o
CHENEY_C89_OBJECTS=cheney_c89.o c89_relo.o
.POSIX:
.PHONY: tests clean string_tests htable_tests
.SUFFIXES:
.SUFFIXES: .c .o .test
CC=cc
CFLAGS=-Wall -std=c89 -Werror -pedantic -fPIC -g -Iinclude
libuniversalservice.so: $(COMMON_OBJECTS) $(CHENEY_C89_OBJECTS)
$(CC) -shared $(COMMON_OBJECTS) $(CHENEY_C89_OBJECTS) -o libuniversalservice.so
tests: string_tests htable_tests
COMMON_OBJECTS=uns.o
uns.o: uns.c include/uns.h
libuniversalservice.so: uns.o ${ALL_COLLECTORS}
${CC} -shared uns.o ${ALL_COLLECTORS} -o libuniversalservice.so
.c.o:
$(CC) $(CFLAGS) $< -c -o $@
${CC} ${CFLAGS} $< -c -o $@
test:
## Collectors
CHENEY_C89_OBJS = cheney_c89.o
cheney_c89.o: cheney_c89.c include/uns.h include/cheney_c89.h
COLLECTOR_OBJS=${CHENEY_C89_OBJS}
## Example libraries
examples/string/uns_string.o: examples/string/uns_string.c \
examples/string/uns_string.h \
include/uns.h
UNS_STRING_OBJS=examples/string/uns_string.o
examples/hashtable/uns_hashtable.o: examples/hashtable/uns_hashtable.c \
examples/hashtable/uns_hashtable.h \
include/uns.h
UNS_HASHTABLE_OBJS=examples/hashtable/uns_hashtable.o
EXAMPLE_OBJS=${UNS_STRING_OBJS} ${UNS_HASHTABLE_OBJS}
###################
## Clean
##################
clean:
rm -f $(COMMON_OBJECTS) $(CHENEY_C89_OBJECTS) libuniversalservice.so
${RM} -f ${COMMON_OBJECTS} libuniversalservice.so \
${TEST_OBJS} ${COLLECTOR_OBJS} ${EXAMPLE_OBJS}
tests.makefile: gen_tests.sh
./gen_tests.sh > tests.makefile
include tests.makefile

View File

@ -2,7 +2,56 @@
Universal Service
=================
Universal Service is a collection of garbage collectors written in C.
Universal Service is a collection of garbage collectors written in C,
designed to allow flexibility in programs that use it. All collectors
are accessed using a common API so code written for one collector can be
used with other collectors with the same feature set without modification.
-----------
Terminology
-----------
Generally speaking, ``UNS_WORD`` must be an integer that can be converted
to and from a pointer to data. ``UNS_SWORD`` is the signed version of
``UNS_WORD``. Both must be integer types.
In collectors where this conversion cannot be assumed (like C89 collectors)
or not possible, then ``UNS_WORD`` should be a type that can be used to
index any arrays (like ``size_t``) and ``UNS_SWORD`` is like ``ssize_t``.
A "region" denotes a block of memory in the heap. The "header" of a
region is a hidden area of the region that holds information about
the region. It may not be connected to the actual region itself. A
"pointer to a region" is a pointer to the first byte of user accessable
space: it is not a pointer to the header.
----------
Collectors
----------
* ``cheney_c89``: A pure C89 copying collector. Useful for applications
that want a resizable heap.
* (Planned) ``inplace_c89``: A pure C89 mark-lazy-sweep collector with
support for weak references.
-----------------
Adding Collectors
-----------------
The minimum set of functions a collector must define are
* ``init``: Initialize ``Uns_GC`` with the heap and callbacks specific to
that collector.
* ``collect``: Force a garbage collection.
* ``alloc``: Allocate memory. All allocators must support at least
``UNS_BYTES`` and ``UNS_RECORD``. All other type combinations are
optional. ``UNS_RECORD`` may be equivalent to a type combination
containing ``UNS_RECORD``.
* ``deinit``: Free heap.
* ``record_set``: Set an entry in a record to the passed value.
This function is not allowed to cause a garbage collection.
* ``record_get``: Get the value of an entry.
This function is not allowed to cause a garbage collection.
-------
License
@ -19,7 +68,8 @@ redistribute modifications to the LGPL code. From the
> Does the LGPL have different requirements for statically vs dynamically
> linked modules with a covered work?
>
> For the purpose of complying with the LGPL (any extant version: v2, v2.1 or v3):
> For the purpose of complying with the LGPL (any extant version: v2,
> v2.1 or v3):
>
> If you statically link against an LGPLed library, you must also
> provide your application in an object (not necessarily source) format,
@ -27,7 +77,7 @@ redistribute modifications to the LGPL code. From the
> the application.
You can statically link any LGPL 3.0 code to code of permissive licenses
(like MIT), and even to source-available license (like the SSPL or the
(like MIT), and even to source-available licenses (like the SSPL or the
Commons Clause) as long as the end user can recompile the program to use
their own version of the library.

View File

@ -1,73 +0,0 @@
/* 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/>.
*/
#include <string.h>
#include <assert.h>
#include "uns.h"
#include "internal/c89_relo.h"
typedef struct uns_c89_relo_hdr Hdr;
size_t uns_c89_relo_get_len(void *p)
{
Hdr *hdr = uns_c89_relo_get_hdr(p);
return (size_t)(hdr->len < 0 ? -hdr->len : hdr->len);
}
size_t uns_c89_relo_get_record_len(void *p)
{
Hdr *hdr = uns_c89_relo_get_hdr(p);
if (hdr->len > 0)
return 0;
return (size_t)(-hdr->len) / sizeof(void *);
}
void *uns_c89_relo_init(void *p, size_t len_size, int is_record)
{
Hdr *hdr = p;
assert(len_size < UNS_SWORD_MAX);
hdr->relo = NULL;
hdr->len = (uns_sword)len_size;
if (is_record)
hdr->len = -hdr->len;
return hdr + 1;
}
static void check_len(void *rec, size_t i)
{
assert(i * sizeof(void *) < uns_c89_relo_get_len(rec));
}
void uns_c89_relo_record_set(Uns_ptr rec, size_t i, void *v)
{
check_len(rec, i);
memcpy((unsigned char *)rec + i*sizeof(void*), &v, sizeof(v));
}
void *uns_c89_relo_record_get(Uns_ptr rec, size_t i)
{
void *v;
check_len(rec, i);
memcpy(&v, (unsigned char *)rec + i*sizeof(void*), sizeof(v));
return v;
}

View File

@ -21,187 +21,450 @@
#include <assert.h>
#include <string.h>
#include "uns.h"
#include "internal/c89_relo.h"
#include "cheney_c89.h"
struct ctx {
/** Pointer to the beginning of the heap. */
unsigned char *tospace;
/** Pointer to one past the end of the heap. */
unsigned char *tospace_end;
/** Pointer to the next place to alloc data. This may be one
* past the end of the heap, meaning there is no space left
* on the heap.
*/
unsigned char *tospace_alloc;
/** A value set by the user to control the next heap size after
* a collection.
*/
size_t new_heap_size;
uns_cheney_c89_collect_callback cb;
struct uns_cheney_c89_statistics stats;
};
const size_t uns_cheney_c89_ctx_size = sizeof(struct ctx);
static void zero_ctx(struct ctx *ctx)
void uns_cheney_c89_deinit(Uns_GC gc)
{
ctx->tospace = 0;
ctx->tospace_end = 0;
ctx->tospace_alloc = 0;
}
void uns_cheney_c89_deinit(struct uns_gc *gc)
{
struct ctx *ctx = gc->ctx;
struct ctx *ctx = uns_ctx(gc);
free(ctx->tospace);
free(ctx);
}
/* Allocate without doing bounds checking. This is common code for
* collecting (when all values will fit into the tospace) and
* allocation (where bounds checking is done beforehand).
*/
static void *raw_alloc(struct ctx *ctx, size_t len, int is_record)
void uns_cheney_c89_set_collect_callback(
Uns_GC gc,
uns_cheney_c89_collect_callback cb
)
{
unsigned char *p;
/*
printf("%lu (%ld)\n", (unsigned long)(len + sizeof(struct uns_c89_relo_hdr)),
(long)(ctx->tospace_end - ctx->tospace_alloc));
*/
assert(ctx->tospace_alloc + sizeof(struct uns_c89_relo_hdr) + len <= ctx->tospace_end);
p = uns_c89_relo_init(ctx->tospace_alloc, len, is_record);
ctx->tospace_alloc = p + len;
return p;
struct ctx *ctx = uns_ctx(gc);
ctx->cb = cb;
}
/* Move an entire object pointed to by p from fromspace to tospace. */
static unsigned char *relocate(struct uns_gc *gc, unsigned char *p)
void uns_cheney_c89_set_new_heap_size(Uns_GC gc, size_t l)
{
size_t l;
unsigned char *res;
struct ctx *ctx = gc->ctx;
if (!p)
return NULL;
res = uns_c89_relo_get_relo(p);
if (res)
return res;
l = uns_c89_relo_get_len(p);
res = raw_alloc(ctx, l, uns_c89_relo_is_record(p));
memcpy(res, p, l);
gc->after_collection += l + sizeof(struct uns_c89_relo_hdr);
uns_c89_relo_set_relo(p, res);
return res;
struct ctx *ctx = uns_ctx(gc);
ctx->new_heap_size = l;
}
/* Scan each part of the record pointed to by "p" and relocate it. */
static void scan_record(struct uns_gc *gc, unsigned char *p)
size_t uns_cheney_c89_get_new_heap_size(Uns_GC gc)
{
size_t l = uns_c89_relo_get_record_len(p);
size_t i;
void *newp;
struct ctx *ctx = uns_ctx(gc);
return ctx->new_heap_size;
}
for (i = 0; i < l; i++) {
newp = relocate(gc, uns_c89_relo_record_get((void *)p, i));
uns_c89_relo_record_set((void *)p, i, newp);
/** Header of a allocated region is a single uns_sword.
*
* 0: Relocated. What follows is a pointer to the relocated region.
* Positive: allocated bytes.
* Negative: allocated record of pointers with this many bytes.
*
* All lengths must be representable in positive and negative.
* Hence UNS_SWORD_MIN and UNS_SWORD_MAX are disallowed length
* values.
*
* Relocated pointers only exist during copying and are not present
* during normal execution.
*/
/** Destructured header. "len" is always the readable length of the
* region in bytes (it is always positive).
*/
struct hdr {
enum {
RELO,
REGION,
RECORD
} typ;
uns_sword len;
};
/** Number of fields in a record. */
#define REC_FIELDS(n) ((n)/sizeof(void*))
/** Size in bytes of the header. */
#define HDR_LEN sizeof(uns_sword)
/** Extract a header from a pointer to a region. */
#define HDR_PTR(p) ((unsigned char *)p - HDR_LEN)
/** Minimum size of a region. */
#define MIN_REG_LEN sizeof(void*)
/** Destructure header pointed to by p
*
* # Parameters
* - `out notnull hdr`: Destructured header.
* - `notnull p`: Pointer to a header. This is not a pointer to a region.
* For a pointer to a region, use `hdr_extract`.
*/
static void hdr_read(struct hdr *hdr, unsigned char *p)
{
assert(hdr);
assert(p);
memcpy(&hdr->len, p, HDR_LEN);
if (hdr->len < 0) {
hdr->typ = RECORD;
hdr->len = -hdr->len;
} else if (hdr->len == 0) {
hdr->typ = RELO;
hdr->len = sizeof(void*);
} else {
hdr->typ = REGION;
}
}
int uns_cheney_c89_collect(struct uns_gc *gc)
/** Destructure header from a pointer to a region.
*
* # Parameters
* - `out notnull hdr`: Destructured header.
* - `notnull p`: Pointer to a region. This is not a pointer to the
* header.
*/
#define hdr_extract(h,p) hdr_read(h, HDR_PTR(p))
/** Write a header to a location.
*
* # Parameters
* - `notnull hdr`: Header description with all fields filled out. This
* function does not do sanity checking and may overflow if given
* bad data.
* - `notnull p`: Header to where the header should be written to.
* This will overwrite data at the pointer location. The pointer
* becomes a pointer to a header, not a pointer to a region.
*/
static void hdr_write_direct(struct hdr *hdr, unsigned char *p)
{
uns_sword s;
assert(hdr);
assert(p);
switch (hdr->typ) {
case REGION: s = hdr->len; break;
case RELO: s = 0; break;
case RECORD: s = -hdr->len; break;
}
memcpy(p, &s, HDR_LEN);
}
/** Write to the header of a region.
*
* # Parameters
* - `notnull hdr`: See `hdr_write_direct`.
* - `notnull p`: Header to a region. The call will do pointer arithmetic
* to get the pointer to the header, and write to the header. It will
* not overwrite region data.
*/
#define hdr_write(h, p) hdr_write_direct(h, HDR_PTR(p))
/** Returns true if there is enough space in the heap for a region with
* length `bytes`.
*/
static int enough_space(struct ctx *ctx, uns_sword bytes)
{
return ctx->tospace_end - ctx->tospace_alloc >= bytes + HDR_LEN;
}
/** Allocate region without bounds checking.
*
* # Parameters
* - `len`: Length in bytes of the entire record. This includes the header
* region. The length should have been adjusted by the caller to include
* the minimum region length.
* # Returns
* A pointer to a region.
*/
static void *raw_alloc(struct ctx *ctx, uns_sword len, int is_record)
{
unsigned char *p;
struct hdr hdr = {0};
assert(len >= HDR_LEN + MIN_REG_LEN);
hdr.len = len - HDR_LEN;
if (is_record) {
hdr.typ = RECORD;
} else {
hdr.typ = REGION;
}
assert(enough_space(ctx, len));
p = ctx->tospace_alloc;
hdr_write_direct(&hdr, p);
ctx->tospace_alloc += len;
return p + HDR_LEN;
}
/** Move an entire object to the new heap during collection.
*
* This function does nothing if `p` is NULL. Otherwise, `p` is a pointer
* to a region.
* If `p` was relocated (typ == `RELO`), then this function returns a
* pointer to the relocated header in tospace.
* Otherwise, it allocates memory in the tospace, copies the entire region
* with its header to the tospace, and modifies the region in the fromspace
* to be a region of type `RELO`.
*/
static unsigned char *relocate(struct ctx *ctx, unsigned char *p)
{
void *res;
struct hdr hdr = {0};
if (!p)
return NULL;
hdr_extract(&hdr, p);
if (hdr.typ == RELO) {
memcpy(&res, p, sizeof(void*));
return res;
}
assert(hdr.len >= MIN_REG_LEN);
/* Write entire region to memory */
res = raw_alloc(ctx, HDR_LEN + hdr.len, hdr.typ == RECORD);
memcpy(res, p, hdr.len);
hdr_write(&hdr, res);
/* Change old pointer to relocation pointer */
hdr.typ = RELO;
hdr_write(&hdr, p);
memcpy(p, &res, sizeof(void*));
return res;
}
/** Calculate the starting byte index of an element in a record.
*
* # Parameters
* - `notnull p`: Pointer to a region.
* - `loc`: Index into the region.
*/
static size_t record_index(Uns_ptr p, size_t loc)
{
struct hdr hdr = {0};
assert(p);
hdr_extract(&hdr, p);
assert(hdr.typ == RECORD);
/* Turn hdr.len into the number of records in the region */
assert(loc < hdr.len / sizeof(void*));
assert(loc < SIZE_MAX/sizeof(void*));
return loc * sizeof(void*);
}
void *uns_cheney_c89_get(Uns_ptr p, size_t loc,
enum uns_fld_type *typ)
{
void *r;
if (typ)
*typ = UNS_POINTER;
loc = record_index(p, loc);
memcpy(&r, (unsigned char *)p + loc, sizeof(void*));
return r;
}
void uns_cheney_c89_set(Uns_ptr p, size_t loc,
enum uns_fld_type typ, void *newp)
{
assert(typ == UNS_POINTER);
loc = record_index(p, loc);
memcpy((unsigned char *)p + loc, &newp, sizeof(void*));
}
/** Relocate each pointer in a record and record its new pointer.
*
* # Parameters
* - `notnull p`: Pointer to a record.
* - `len`: Number of elements in the record.
*/
static void scan_record(struct ctx *ctx, void *p, size_t len)
{
size_t i;
void *newp;
for (i = 0; i < len; i++) {
newp = relocate(ctx,
uns_cheney_c89_get(p, i, NULL));
uns_cheney_c89_set(p, i, UNS_POINTER, newp);
}
}
/** Main section of the copying algorithm. */
static void relocate_everything(Uns_GC gc)
{
unsigned char *scanptr;
struct uns_ctr *root;
struct ctx *ctx = uns_ctx(gc);
struct hdr hdr = {0};
/* Relocate roots */
for (root = uns_roots(gc); root; root = root->next)
root->p = relocate(ctx, root->p);
/* Scan the heap until the end of allocated space. If there
* is a record at this location, read the record and relocate
* all values, and then change each field to the relocated
* record pointer.
*/
scanptr = ctx->tospace;
while (scanptr != ctx->tospace_alloc) {
/* scanptr currently points to the header data. */
hdr_read(&hdr, scanptr);
scanptr += HDR_LEN;
if (hdr.typ == RECORD)
scan_record(ctx, scanptr, (size_t)hdr.len/sizeof(void*));
scanptr += hdr.len;
}
}
int uns_cheney_c89_collect(Uns_GC gc)
{
/* Save fromspace */
struct ctx *ctx = gc->ctx;
struct ctx *ctx = uns_ctx(gc);
unsigned char *fromspace = ctx->tospace;
unsigned char *fromspace_lim = ctx->tospace_alloc;
unsigned char *scanptr;
size_t newlen = gc->next_alloc;
struct uns_ctr *root;
size_t newlen = ctx->new_heap_size;
assert(gc->next_alloc >= fromspace_lim - fromspace);
ctx->stats.usage_before = fromspace_lim - fromspace;
/* Bail out immediately if allocation fails. This preserves
* the objects as they were.
*/
assert(newlen >= fromspace_lim - fromspace);
ctx->tospace = malloc(newlen);
if (!ctx->tospace) {
ctx->tospace = fromspace;
return 1;
}
/* Setup statistics */
gc->before_collection = fromspace_lim - fromspace;
/*
printf("before: %ld\n", gc->before_collection);
*/
gc->after_collection = 0;
gc->collection_number += 1;
/* Setup context to be valid for the allocator */
ctx->tospace_end = ctx->tospace + newlen;
ctx->tospace_alloc = ctx->tospace;
/* Relocate roots */
for (root = gc->roots; root; root = root->next)
root->p = relocate(gc, root->p);
scanptr = ctx->tospace;
while (scanptr != ctx->tospace_alloc) {
/* scanptr currently points to the header data that the mutator
* does not usually see.
*/
scanptr += sizeof(struct uns_c89_relo_hdr);
if (uns_c89_relo_is_record(scanptr))
scan_record(gc, scanptr);
scanptr += uns_c89_relo_get_len(scanptr);
}
relocate_everything(gc);
free(fromspace);
if (gc->after_gc)
gc->after_gc(gc);
ctx->stats.usage_after = ctx->tospace_alloc - ctx->tospace;
ctx->stats.collection_number += 1;
if (ctx->cb)
ctx->cb(gc, &ctx->stats);
return 1;
}
static void *alloc(struct uns_gc *gc, size_t bytes, int is_record)
static void *alloc(Uns_GC gc, size_t bytes, int is_record)
{
struct ctx *ctx = gc->ctx;
size_t total_bytes = bytes + sizeof(struct uns_c89_relo_hdr);
struct ctx *ctx = uns_ctx(gc);
uns_sword bytes_as_sword;
if (bytes >= UNS_SWORD_MAX) {
uns_on_oom(gc);
return NULL;
} else if (bytes < MIN_REG_LEN) {
bytes = MIN_REG_LEN;
}
bytes_as_sword = (uns_sword)bytes + HDR_LEN;
/* Make sure to check for header space when allocating */
if (ctx->tospace_end - ctx->tospace_alloc < total_bytes)
if (!enough_space(ctx, bytes_as_sword)) {
uns_cheney_c89_collect(gc);
if (ctx->tospace_end - ctx->tospace_alloc < total_bytes)
gc->oom(gc);
if (!enough_space(ctx, bytes_as_sword)) {
uns_on_oom(gc);
return NULL;
}
}
return raw_alloc(ctx, bytes, is_record);
return raw_alloc(ctx, HDR_LEN + bytes, is_record);
}
void *uns_cheney_c89_alloc(struct uns_gc *gc, size_t bytes)
void *uns_cheney_c89_alloc(Uns_GC gc, size_t bytes, enum uns_bytes_type typ)
{
assert(typ == UNS_NOEXEC);
return alloc(gc, bytes, 0);
}
void *uns_cheney_c89_alloc_record(struct uns_gc *gc, size_t records)
void *uns_cheney_c89_alloc_rec(Uns_GC gc, size_t len, enum uns_record_type typ)
{
void *p;
size_t i;
unsigned char *res = alloc(gc, records*sizeof(void *), 1);
assert(typ == UNS_POINTERS_ONLY);
for (i = 0; i < records; i++)
uns_c89_relo_record_set((void *)res, i, NULL);
if (len >= SIZE_MAX/sizeof(void*)) {
uns_on_oom(gc);
return NULL;
} else if (len == 0) {
len = 1;
}
return res;
p = alloc(gc, len*sizeof(void*), 1);
if (p == NULL)
return NULL;
for (i = 0; i < len; i++)
uns_cheney_c89_set(p, i, UNS_POINTER, NULL);
return p;
}
int uns_cheney_c89_init(struct uns_gc *gc)
int uns_cheney_c89_init(Uns_GC gc, size_t heap_size)
{
struct ctx *ctx = gc->ctx;
gc->deinit = uns_cheney_c89_deinit;
gc->collect = uns_cheney_c89_collect;
gc->alloc = uns_cheney_c89_alloc;
gc->alloc_record = uns_cheney_c89_alloc_record;
gc->len = uns_c89_relo_get_len;
gc->record_set_ptr = uns_c89_relo_record_set;
gc->record_get_ptr = uns_c89_relo_record_get;
zero_ctx(ctx);
ctx->tospace = malloc(gc->next_alloc);
if (!ctx->tospace)
struct ctx *ctx = malloc(sizeof(struct ctx));
if (!ctx)
return 0;
ctx->tospace_alloc = ctx->tospace;
ctx->tospace_end = ctx->tospace + gc->next_alloc;
uns_deinit(gc);
ctx->tospace_alloc = ctx->tospace = malloc(heap_size);
if (!ctx->tospace) {
free(ctx);
return 0;
}
ctx->stats.usage_before = ctx->stats.usage_after
= ctx->stats.collection_number = 0;
ctx->tospace_end = ctx->tospace + heap_size;
ctx->new_heap_size = heap_size;
ctx->cb = NULL;
uns_set_ctx(gc, ctx);
uns_set_deinit(gc, uns_cheney_c89_deinit);
uns_set_collect(gc, uns_cheney_c89_collect);
uns_set_alloc(gc, uns_cheney_c89_alloc);
uns_set_alloc_rec(gc, uns_cheney_c89_alloc_rec);
uns_set_set(gc, uns_cheney_c89_set);
uns_set_get(gc, uns_cheney_c89_get);
return 1;
}

View File

@ -1,23 +0,0 @@
.PHONY: test clean
TESTS=test_insert.test test_insert_delete.test
COMMON_OBJS=../test_common.o uns_hashtable.o
.SUFFIXES: .test
CFLAGS=-Wall -std=c89 -Werror -pedantic -fPIC -g -Iinclude
libunshashtable.so: uns_hashtable.o
$(CC) -shared -I../../include $(CFLAGS) $< -o libunshashtable.so
test: $(TESTS) $(COMMON_OBJS)
for i in $(TESTS); do \
LD_LIBRARY_PATH=$$(pwd)/../../ valgrind ./$$i || exit 1; \
done
test_insert.test: $(COMMON_OBJS)
.c.test:
$(CC) -I../../include $(CFLAGS) $< $(COMMON_OBJS) -L../../ -luniversalservice -o $@
.c.o:
$(CC) -I../../include $(CFLAGS) $< -c -o $@
clean:
rm -f $(TESTS) $(COMMON_OBJS) libunshashtable.so

View File

@ -33,7 +33,7 @@
#define ARRLEN(a) (sizeof(a) /sizeof((a)[0]))
int test(struct uns_gc *gc)
int test(Uns_GC gc)
{
/* Keys and values will be stored as NUL terminated for simplicity. */
struct uns_ctr htbl = {0};
@ -55,8 +55,8 @@ int test(struct uns_gc *gc)
fflush(stdout);
slen = strlen(names[i]);
str.p = gc->alloc(gc, slen);
val.p = gc->alloc(gc, strlen(values[i]));
str.p = uns_alloc(gc, slen + 1, 0);
val.p = uns_alloc(gc, strlen(values[i]) + 1, 0);
strcpy(str.p, names[i]);
strcpy(val.p, values[i]);
@ -82,6 +82,6 @@ int test(struct uns_gc *gc)
uns_root_remove(gc, &val);
uns_root_remove(gc, &str);
uns_root_remove(gc, &htbl);
gc->collect(gc);
uns_collect(gc);
return 0;
}

View File

@ -60,8 +60,8 @@ int test(struct uns_gc *gc)
fflush(stdout);
slen = strlen(names[i]);
str.p = gc->alloc(gc, slen);
val.p = gc->alloc(gc, strlen(values[i]));
str.p = uns_alloc(gc, slen + 1, 0);
val.p = uns_alloc(gc, strlen(values[i]) + 1, 0);
strcpy(str.p, names[i]);
strcpy(val.p, values[i]);
@ -101,6 +101,6 @@ int test(struct uns_gc *gc)
uns_root_remove(gc, &val);
uns_root_remove(gc, &str);
uns_root_remove(gc, &htbl);
gc->collect(gc);
uns_collect(gc);
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -35,52 +35,52 @@ enum {
HTBL_HDR_LEN
};
static size_t get_size_t(struct uns_gc *gc, struct uns_ctr *root, size_t i)
static size_t get_size_t(Uns_GC gc, struct uns_ctr *root, size_t i)
{
size_t r;
void *p = gc->record_get_ptr(root->p, i);
void *p = uns_get(gc, root->p, i, NULL);
memcpy(&r, p, sizeof(r));
return r;
}
static void set_size_t(struct uns_gc *gc, struct uns_ctr *root, size_t i, size_t val)
static void set_size_t(Uns_GC gc, struct uns_ctr *root, size_t i, size_t val)
{
void *p = gc->record_get_ptr(root->p, i);
void *p = uns_get(gc, root->p, i, NULL);
memcpy(p, &val, sizeof(val));
}
static void *index(struct uns_gc *gc, struct uns_ctr *htbl, size_t i)
static void *index(Uns_GC gc, struct uns_ctr *htbl, size_t i)
{
return gc->record_get_ptr(htbl->p, i + HTBL_HDR_LEN);
return uns_get(gc, htbl->p, i + HTBL_HDR_LEN, NULL);
}
void uns_hashtable_alloc_into(struct uns_gc *gc, struct uns_ctr *r, size_t size)
void uns_hashtable_alloc_into(Uns_GC gc, struct uns_ctr *r, size_t size)
{
unsigned char *p;
size_t i;
r->p = gc->alloc_record(gc, size + HTBL_HDR_LEN);
r->p = uns_alloc_rec(gc, size + HTBL_HDR_LEN, 0);
p = gc->alloc(gc, sizeof(size_t));
gc->record_set_ptr(r->p, HTBL_LEN_PTR, p);
p = uns_alloc(gc, sizeof(size_t), 0);
uns_set(gc, r->p, HTBL_LEN_PTR, UNS_POINTER, p);
set_size_t(gc, r, HTBL_LEN_PTR, size);
p = gc->alloc(gc, sizeof(size_t));
gc->record_set_ptr(r->p, HTBL_USED_PTR, p);
p = uns_alloc(gc, sizeof(size_t), 0);
uns_set(gc, r->p, HTBL_USED_PTR, UNS_POINTER, p);
set_size_t(gc, r, HTBL_USED_PTR, 0);
/* Set entries to zero. */
for (i = HTBL_HDR_LEN; i <= size; i++)
gc->record_set_ptr(r->p, i, NULL);
uns_set(gc, r->p, i, UNS_POINTER, NULL);
}
size_t uns_hashtable_len(struct uns_gc *gc, struct uns_ctr *htbl)
size_t uns_hashtable_len(Uns_GC gc, struct uns_ctr *htbl)
{
return get_size_t(gc, htbl, HTBL_LEN_PTR);
}
size_t uns_hashtable_used(struct uns_gc *gc, struct uns_ctr *htbl)
size_t uns_hashtable_used(Uns_GC gc, struct uns_ctr *htbl)
{
return get_size_t(gc, htbl, HTBL_USED_PTR);
}
@ -93,19 +93,19 @@ enum {
HTBL_ENTRY_LEN
};
static void alloc_holder(struct uns_gc *gc, struct uns_ctr *r, struct uns_ctr *string,
static void alloc_holder(Uns_GC gc, struct uns_ctr *r, struct uns_ctr *string,
struct uns_ctr *value, size_t len)
{
unsigned char *alloc_ptr;
r->p = gc->alloc_record(gc, HTBL_ENTRY_LEN);
r->p = uns_alloc_rec(gc, HTBL_ENTRY_LEN, 0);
alloc_ptr = gc->alloc(gc, sizeof(size_t));
alloc_ptr = uns_alloc(gc, sizeof(size_t), 0);
memcpy(alloc_ptr, &len, sizeof(size_t));
gc->record_set_ptr(r->p, LENGTH, alloc_ptr);
gc->record_set_ptr(r->p, STRING, string->p);
gc->record_set_ptr(r->p, VALUE, value->p);
gc->record_set_ptr(r->p, NEXT, NULL);
uns_set(gc, r->p, LENGTH, UNS_POINTER, alloc_ptr);
uns_set(gc, r->p, STRING, UNS_POINTER, string->p);
uns_set(gc, r->p, VALUE, UNS_POINTER, value->p);
uns_set(gc, r->p, NEXT, UNS_POINTER, NULL);
}
static unsigned long fnv(unsigned char *p, size_t len)
@ -121,16 +121,16 @@ static unsigned long fnv(unsigned char *p, size_t len)
return hash;
}
static int search_container(struct uns_gc *gc, struct uns_ctr *cur_ctr,
static int search_container(Uns_GC gc, struct uns_ctr *cur_ctr,
unsigned char *str, size_t len)
{
unsigned char *ctr_str = gc->record_get_ptr(cur_ctr->p, STRING);
unsigned char *ctr_str = uns_get(gc, cur_ctr->p, STRING, NULL);
size_t ctr_str_len = get_size_t(gc, cur_ctr, LENGTH);
void *next;
if (ctr_str_len != len || memcmp(ctr_str, str, len) != 0) {
/* strings do not match */
next = gc->record_get_ptr(cur_ctr->p, NEXT);
next = uns_get(gc, cur_ctr->p, NEXT, NULL);
if (!next)
return 0;
cur_ctr->p = next;
@ -140,7 +140,7 @@ static int search_container(struct uns_gc *gc, struct uns_ctr *cur_ctr,
}
}
static int search_for_container(struct uns_gc *gc, struct uns_ctr *htbl,
static int search_for_container(Uns_GC gc, struct uns_ctr *htbl,
struct uns_ctr *container,
unsigned char *str, size_t len, size_t *hash_out)
{
@ -158,7 +158,7 @@ static int search_for_container(struct uns_gc *gc, struct uns_ctr *htbl,
/* This could be made better with internal pointers, but that is not
* portable.
*/
void uns_hashtable_del(struct uns_gc *gc, struct uns_ctr *htbl,
void uns_hashtable_del(Uns_GC gc, struct uns_ctr *htbl,
unsigned char *str, size_t len,
struct uns_ctr *old_value)
{
@ -174,26 +174,26 @@ void uns_hashtable_del(struct uns_gc *gc, struct uns_ctr *htbl,
/* Special case: first pointer */
if (len == get_size_t(gc, &rec, LENGTH) &&
memcmp(str, gc->record_get_ptr(rec.p, STRING), len) == 0) {
old_value->p = gc->record_get_ptr(rec.p, VALUE);
gc->record_set_ptr(htbl->p, HTBL_HDR_LEN + hash,
gc->record_get_ptr(rec.p, NEXT));
memcmp(str, uns_get(gc, rec.p, STRING, NULL), len) == 0) {
old_value->p = uns_get(gc, rec.p, VALUE, NULL);
uns_set(gc, htbl->p, HTBL_HDR_LEN + hash, UNS_POINTER,
uns_get(gc, rec.p, NEXT, NULL));
}
prevptr = rec;
for (rec.p = gc->record_get_ptr(rec.p, NEXT); rec.p;
rec.p = gc->record_get_ptr(rec.p, NEXT)) {
for (rec.p = uns_get(gc, rec.p, NEXT, NULL); rec.p;
rec.p = uns_get(gc, rec.p, NEXT, NULL)) {
if (len == get_size_t(gc, &rec, LENGTH)
&& memcmp(str, gc->record_get_ptr(rec.p, VALUE), len) == 0) {
old_value->p = gc->record_get_ptr(rec.p, VALUE);
gc->record_set_ptr(prevptr.p, NEXT,
gc->record_get_ptr(rec.p, NEXT));
&& memcmp(str, uns_get(gc, rec.p, VALUE, NULL), len) == 0) {
old_value->p = uns_get(gc, rec.p, VALUE, NULL);
uns_set(gc, prevptr.p, UNS_POINTER, NEXT,
uns_get(gc, rec.p, NEXT, NULL));
return;
}
}
}
void uns_hashtable_search(struct uns_gc *gc, struct uns_ctr *htbl,
void uns_hashtable_search(Uns_GC gc, struct uns_ctr *htbl,
unsigned char *str, size_t string_len,
struct uns_ctr *found)
{
@ -201,12 +201,12 @@ void uns_hashtable_search(struct uns_gc *gc, struct uns_ctr *htbl,
r = search_for_container(gc, htbl, found, str, string_len, NULL);
if (r)
found->p = gc->record_get_ptr(found->p, VALUE);
found->p = uns_get(gc, found->p, VALUE, NULL);
else
found->p = NULL;
}
void uns_hashtable_add(struct uns_gc *gc, struct uns_ctr *htbl,
void uns_hashtable_add(Uns_GC gc, struct uns_ctr *htbl,
struct uns_ctr *string, size_t string_len,
struct uns_ctr *value, struct uns_ctr *old_value)
{
@ -218,8 +218,8 @@ void uns_hashtable_add(struct uns_gc *gc, struct uns_ctr *htbl,
uns_hashtable_resize(gc, htbl, uns_hashtable_len(gc, htbl)*2);
if (search_for_container(gc, htbl, &container, (unsigned char *)string->p, string_len, &hash)) {
old_value->p = gc->record_get_ptr(container.p, VALUE);
gc->record_set_ptr(container.p, VALUE, value->p);
old_value->p = uns_get(gc, container.p, VALUE, NULL);
uns_set(gc, container.p, VALUE, UNS_POINTER, value->p);
return;
}
@ -232,18 +232,16 @@ void uns_hashtable_add(struct uns_gc *gc, struct uns_ctr *htbl,
alloc_holder(gc, &next_container, string, value, string_len);
if (container.p) {
gc->record_set_ptr(container.p, NEXT, next_container.p);
uns_set(gc, container.p, NEXT, UNS_POINTER, next_container.p);
} else {
gc->record_set_ptr(htbl->p, hash + HTBL_HDR_LEN, next_container.p);
uns_set(gc, htbl->p, hash + HTBL_HDR_LEN, UNS_POINTER, next_container.p);
}
uns_root_remove(gc, &next_container);
uns_root_remove(gc, &container);
}
/* TODO: add delete */
static void add_for_ent(struct uns_gc *gc, struct uns_ctr *htbl, void *ctrp)
static void add_for_ent(Uns_GC gc, struct uns_ctr *htbl, void *ctrp)
{
struct uns_ctr ctr = {0};
struct uns_ctr str = {0};
@ -260,8 +258,8 @@ static void add_for_ent(struct uns_gc *gc, struct uns_ctr *htbl, void *ctrp)
uns_root_add(gc, &value);
uns_root_add(gc, &old_value_should_never_happen);
str.p = gc->record_get_ptr(ctr.p, STRING);
value.p = gc->record_get_ptr(ctr.p, VALUE);
str.p = uns_get(gc, ctr.p, STRING, NULL);
value.p = uns_get(gc, ctr.p, VALUE, NULL);
str_len = get_size_t(gc, &ctr, LENGTH);
uns_hashtable_add(gc, htbl, &str, str_len, &value,
@ -273,11 +271,11 @@ static void add_for_ent(struct uns_gc *gc, struct uns_ctr *htbl, void *ctrp)
uns_root_remove(gc, &str);
uns_root_remove(gc, &ctr);
ctrp = gc->record_get_ptr(ctr.p, NEXT);
ctrp = uns_get(gc, ctr.p, NEXT, NULL);
add_for_ent(gc, htbl, ctrp);
}
void uns_hashtable_resize(struct uns_gc *gc, struct uns_ctr *htbl, size_t newsize)
void uns_hashtable_resize(Uns_GC gc, struct uns_ctr *htbl, size_t newsize)
{
struct uns_ctr newtbl = {0};
size_t i;

View File

@ -1,25 +0,0 @@
.PHONY: test clean
TESTS=test_small.test test_inefficient.test test_large.test
COMMON_OBJS=../test_common.o uns_string.o
.SUFFIXES: .test
CFLAGS=-Wall -Wno-overlength-strings -std=c89 -Werror -pedantic -fPIC -g -Iinclude
libunsstring.so: uns_string.o
$(CC) -shared -I../../include $(CFLAGS) $< -o libunsstring.so
test: $(TESTS) $(COMMON_OBJS)
for i in $(TESTS); do \
LD_LIBRARY_PATH=$$(pwd)/../../ valgrind ./$$i || exit 1; \
done
test_inefficient.test: $(COMMON_OBJS)
test_small.test: $(COMMON_OBJS)
test_large.test: $(COMMON_OBJS)
.c.test:
$(CC) -I../../include $(CFLAGS) $< $(COMMON_OBJS) -L../../ -luniversalservice -o $@
.c.o:
$(CC) -I../../include $(CFLAGS) $< -c -o $@
clean:
rm -f $(TESTS) $(COMMON_OBJS) libunsstring.so

View File

@ -24,49 +24,51 @@
*/
#include <stdio.h>
#include <string.h>
#include "uns.h"
#include "uns_string.h"
int test(struct uns_gc *gc)
int test(Uns_GC gc)
{
struct uns_ctr r = {0};
int i;
size_t i, j;
size_t len;
/* MistralAI 8x7B 0.1:
* Prompt: "write a sonnet about garbage collectors (the memory management kind)"
*
* This string constant is larger than the standard's minimum. If
* this test doesn't compile, get a better compiler.
*/
const char s[] =
"To the humble garbagemen of code's dark night,\n"
"Who tread through bits and bytes with steady hand.\n"
"They roam the heap, in dimly lit sight,\n"
"And rescue order from chaos unplanned.\n"
const char *s[] = {
"To the humble garbagemen of code's dark night,\n",
"Who tread through bits and bytes with steady hand.\n",
"They roam the heap, in dimly lit sight,\n",
"And rescue order from chaos unplanned.\n",
"With algorithms precise and swift they glide,\n"
"Through malloc'd arrays and pointers vast.\n"
"Their work essential to keep programs wide,\n"
"From crashing down upon us too soon passed.\n"
"With algorithms precise and swift they glide,\n",
"Through malloc'd arrays and pointers vast.\n",
"Their work essential to keep programs wide,\n",
"From crashing down upon us too soon passed.\n",
"Yet often left unsung are these brave knights,\n"
"Whose vigilance keeps our software clean.\n"
"In shadows cast by CPUs might,\n"
"Unseen forces that make sense of unseen.\n"
"Yet often left unsung are these brave knights,\n",
"Whose vigilance keeps our software clean.\n",
"In shadows cast by CPUs might,\n",
"Unseen forces that make sense of unseen.\n",
"So here's to you, dear Garbage Collectors all,\n"
"So here's to you, dear Garbage Collectors all,\n",
"Thank you for keeping coding dreams enthralled.\n"
;
};
/* Generate lots of garbage by being inefficient. */
uns_string_alloc(gc, &r, 32);
uns_root_add(gc, &r);
for (i = 0; i < sizeof(s) - 1; i++) {
uns_string_append_char(gc, &r, s[i]);
for (i = 0; i < sizeof(s)/sizeof(s[0]); i++) {
len = strlen(s[i]);
for (j = 0; j < len; j++)
uns_string_append_char(gc, &r, s[i][j]);
}
gc->collect(gc);
uns_collect(gc);
uns_root_remove(gc, &r);
gc->collect(gc);
uns_collect(gc);
return 0;
}

View File

@ -24,49 +24,48 @@
*/
#include <stdio.h>
#include <string.h>
#include "uns.h"
#include "uns_string.h"
int test(struct uns_gc *gc)
int test(Uns_GC gc)
{
struct uns_ctr r = {0};
int i;
int i, j;
/* MistralAI 8x7B 0.1:
* Prompt: "write a sonnet about garbage collectors (the memory management kind)"
*
* This string constant is larger than the standard's minimum. If
* this test doesn't compile, get a better compiler.
*/
const char s[] =
"To the humble garbagemen of code's dark night,\n"
"Who tread through bits and bytes with steady hand.\n"
"They roam the heap, in dimly lit sight,\n"
"And rescue order from chaos unplanned.\n"
const char *s[] = {
"To the humble garbagemen of code's dark night,\n",
"Who tread through bits and bytes with steady hand.\n",
"They roam the heap, in dimly lit sight,\n",
"And rescue order from chaos unplanned.\n",
"With algorithms precise and swift they glide,\n"
"Through malloc'd arrays and pointers vast.\n"
"Their work essential to keep programs wide,\n"
"From crashing down upon us too soon passed.\n"
"With algorithms precise and swift they glide,\n",
"Through malloc'd arrays and pointers vast.\n",
"Their work essential to keep programs wide,\n",
"From crashing down upon us too soon passed.\n",
"Yet often left unsung are these brave knights,\n"
"Whose vigilance keeps our software clean.\n"
"In shadows cast by CPUs might,\n"
"Unseen forces that make sense of unseen.\n"
"Yet often left unsung are these brave knights,\n",
"Whose vigilance keeps our software clean.\n",
"In shadows cast by CPUs might,\n",
"Unseen forces that make sense of unseen.\n",
"So here's to you, dear Garbage Collectors all,\n"
"So here's to you, dear Garbage Collectors all,\n",
"Thank you for keeping coding dreams enthralled.\n"
;
};
/* generate lots of garbage */
uns_string_alloc(gc, &r, 32);
uns_root_add(gc, &r);
for (i = 0; i < 100; i++) {
uns_string_append_bytes(gc, &r, s, sizeof(s) - 1);
for (j = 0; j < sizeof(s)/sizeof(s[0]); j++)
uns_string_append_bytes(gc, &r, s[j], strlen(s[j]));
}
gc->collect(gc);
uns_collect(gc);
uns_root_remove(gc, &r);
gc->collect(gc);
uns_collect(gc);
return 0;
}

View File

@ -27,7 +27,7 @@
#include "uns.h"
#include "uns_string.h"
int test(struct uns_gc *gc)
int test(Uns_GC gc)
{
struct uns_ctr r = {0};
const char s[] = "hello, world";

View File

@ -34,44 +34,45 @@ enum {
NUMBER_OF_IND = 3
};
static size_t get_size_t(struct uns_gc *gc, struct uns_ctr *root, size_t i)
static size_t get_size_t(Uns_GC gc, struct uns_ctr *root, size_t i)
{
size_t r;
void *p = gc->record_get_ptr(root->p, i);
/* TODO: record type checking */
void *p = uns_get(gc, root->p, i, NULL);
memcpy(&r, p, sizeof(r));
return r;
}
static void set_size_t(struct uns_gc *gc, struct uns_ctr *root, size_t i, size_t val)
static void set_size_t(Uns_GC gc, struct uns_ctr *root, size_t i, size_t val)
{
void *p = gc->record_get_ptr(root->p, i);
void *p = uns_get(gc, root->p, i, NULL);
memcpy(p, &val, sizeof(val));
}
#define set_allen(gc,root,val) set_size_t(gc, root, ALLEN_IND, val)
#define set_len(gc,root,val) set_size_t(gc, root, LEN_IND, val)
static size_t uns_string_allen(struct uns_gc *gc, struct uns_ctr *root)
static size_t uns_string_allen(Uns_GC gc, struct uns_ctr *root)
{
return get_size_t(gc, root, ALLEN_IND);
}
size_t uns_string_len(struct uns_gc *gc, struct uns_ctr *root)
size_t uns_string_len(Uns_GC gc, struct uns_ctr *root)
{
return get_size_t(gc, root, LEN_IND);
}
char *uns_string_ptr(struct uns_gc *gc, struct uns_ctr *root)
char *uns_string_ptr(Uns_GC gc, struct uns_ctr *root)
{
return gc->record_get_ptr(root->p, BYTES_IND);
return uns_get(gc, root->p, BYTES_IND, NULL);
}
void uns_string_alloc(struct uns_gc *gc, struct uns_ctr *root, size_t start_len)
void uns_string_alloc(Uns_GC gc, struct uns_ctr *root, size_t start_len)
{
unsigned char *p;
root->p = gc->alloc_record(gc, NUMBER_OF_IND);
root->p = uns_alloc_rec(gc, NUMBER_OF_IND, 0);
/* The following is incorrect:
* gc->record_set_ptr(root->p, ALLEN_IND, gc->alloc(gc, sizeof(size_t)));
@ -79,19 +80,19 @@ void uns_string_alloc(struct uns_gc *gc, struct uns_ctr *root, size_t start_len)
* but root->p may have already been put on the stack.
*/
p = gc->alloc(gc, sizeof(size_t));
gc->record_set_ptr(root->p, ALLEN_IND, p);
p = uns_alloc(gc, sizeof(size_t), 0);
uns_set(gc, root->p, ALLEN_IND, UNS_POINTER, p);
set_allen(gc, root, start_len);
p = gc->alloc(gc, sizeof(size_t));
gc->record_set_ptr(root->p, LEN_IND, p);
p = uns_alloc(gc, sizeof(size_t), 0);
uns_set(gc, root->p, LEN_IND, UNS_POINTER, p);
set_len(gc, root, 0);
p = gc->alloc(gc, start_len);
gc->record_set_ptr(root->p, BYTES_IND, p);
p = uns_alloc(gc, start_len, 0);
uns_set(gc, root->p, BYTES_IND, UNS_POINTER, p);
}
void uns_string_resize(struct uns_gc *gc, struct uns_ctr *root, size_t newlen)
void uns_string_resize(Uns_GC gc, struct uns_ctr *root, size_t newlen)
{
struct uns_ctr tmp_new = {0};
size_t old_len = uns_string_len(gc, root);
@ -112,7 +113,7 @@ void uns_string_resize(struct uns_gc *gc, struct uns_ctr *root, size_t newlen)
uns_root_remove(gc, &tmp_new);
}
void uns_string_ensure(struct uns_gc *gc, struct uns_ctr *root, size_t extent)
void uns_string_ensure(Uns_GC gc, struct uns_ctr *root, size_t extent)
{
size_t used = uns_string_len(gc, root);
size_t allen = uns_string_allen(gc, root);
@ -121,7 +122,7 @@ void uns_string_ensure(struct uns_gc *gc, struct uns_ctr *root, size_t extent)
uns_string_resize(gc, root, used + extent);
}
void uns_string_append_bytes(struct uns_gc *gc, struct uns_ctr *to,
void uns_string_append_bytes(Uns_GC gc, struct uns_ctr *to,
const void *from, size_t bytes)
{
size_t len = uns_string_len(gc, to);
@ -130,12 +131,12 @@ void uns_string_append_bytes(struct uns_gc *gc, struct uns_ctr *to,
set_len(gc, to, len + bytes);
}
void uns_string_append_char(struct uns_gc *gc, struct uns_ctr *root, char c)
void uns_string_append_char(Uns_GC gc, struct uns_ctr *root, char c)
{
uns_string_append_bytes(gc, root, &c, 1);
}
char *uns_string_cstring(struct uns_gc *gc, struct uns_ctr *root)
char *uns_string_cstring(Uns_GC gc, struct uns_ctr *root)
{
char *p;
uns_string_ensure(gc, root, 1);

View File

@ -39,15 +39,15 @@
* (allocated length) (used) (size of used)
*/
size_t uns_string_len(struct uns_gc *gc, struct uns_ctr *root);
char *uns_string_ptr(struct uns_gc *gc, struct uns_ctr *root);
size_t uns_string_len(Uns_GC gc, struct uns_ctr *root);
char *uns_string_ptr(Uns_GC gc, struct uns_ctr *root);
/* The following functions may cause a collection. */
void uns_string_alloc(struct uns_gc *gc, struct uns_ctr *root, size_t start_len);
void uns_string_resize(struct uns_gc *gc, struct uns_ctr *root, size_t newlen);
void uns_string_ensure(struct uns_gc *gc, struct uns_ctr *root, size_t extent);
void uns_string_append_bytes(struct uns_gc *gc, struct uns_ctr *to,
void uns_string_alloc(Uns_GC gc, struct uns_ctr *root, size_t start_len);
void uns_string_resize(Uns_GC gc, struct uns_ctr *root, size_t newlen);
void uns_string_ensure(Uns_GC gc, struct uns_ctr *root, size_t extent);
void uns_string_append_bytes(Uns_GC gc, struct uns_ctr *to,
const void *from, size_t bytes);
void uns_string_append_char(struct uns_gc *gc, struct uns_ctr *root, char c);
char *uns_string_cstring(struct uns_gc *gc, struct uns_ctr *root);
void uns_string_append_char(Uns_GC gc, struct uns_ctr *root, char c);
char *uns_string_cstring(Uns_GC gc, struct uns_ctr *root);
#endif

View File

@ -28,53 +28,49 @@
#include "uns.h"
#include "cheney_c89.h"
/* This file sets up the environment for a test.
/* This file sets up the environment for a test for portable C89
* collectors.
*
* Each test is compiled as a separate program to isolate tests portably.
* A test suite is a shell script (or batch file or whatever you want...)
*/
static void oom(struct uns_gc *gc)
{
(void)gc;
fprintf(stderr, "failed: OOM\n");
exit(1);
}
static void after_gc(struct uns_gc *gc)
static void after_gc(Uns_GC gc, struct uns_cheney_c89_statistics *stats)
{
fprintf(stderr,
"The garbage collector has run %ld times\n"
"cheney_c89 has run %ld times\n"
"\tbefore collection: %lu\n"
"\tafter collection: %lu\n",
gc->collection_number,
gc->before_collection,
gc->after_collection
stats->collection_number,
stats->usage_before,
stats->usage_after
);
if (gc->after_collection >= gc->before_collection/2)
gc->next_alloc *= 2;
if (stats->usage_after >= stats->usage_before/2)
uns_cheney_c89_set_new_heap_size(gc,
uns_cheney_c89_get_new_heap_size(gc) * 2
);
}
extern int test(struct uns_gc *gc);
extern int test(Uns_GC gc);
int main(void)
{
static struct uns_gc gc;
int i;
Uns_GC gc;
gc.next_alloc = 1024;
gc.ctx = malloc(uns_cheney_c89_ctx_size);
gc.after_gc = after_gc;
gc.oom = oom;
if (!gc.ctx)
gc = malloc(uns_gc_size);
if (!gc)
return 1;
if (!uns_cheney_c89_init(&gc))
return 2;
gc.next_alloc = 2048;
uns_gc_zero(gc);
i = test(&gc);
gc.deinit(&gc);
if (!uns_cheney_c89_init(gc, 1024))
return 1;
uns_cheney_c89_set_collect_callback(gc, after_gc);
uns_cheney_c89_set_new_heap_size(gc, 2048);
i = test(gc);
uns_deinit(gc);
return i;
}

65
gen_tests.sh Executable file
View File

@ -0,0 +1,65 @@
#!/bin/sh
# The road to hell is paved with Makefile generators.
TEST_TARGETS=""
GENERATED_OBJS=""
gen_test() {
local NAME COLLECTOR TEST_IMPL DEPS OBJDEPS
NAME=$1
COLLECTOR=$2
TEST_IMPL="$NAME.c"
DEPS="$1.o uns.o $3"
OBJDEPS="include/uns.h $1.c $4"
printf '%s.o: %s\n' "$NAME" "$OBJDEPS"
printf '%s_%s.test: %s\n' "$NAME" "$COLLECTOR" "$DEPS"
printf '\t${CC} ${LDFLAGS} %s -o %s_%s.test\n' \
"$DEPS" "$NAME" "$COLLECTOR"
printf '\tvalgrind ./%s_%s.test\n' "$NAME" "$COLLECTOR"
TEST_TARGETS=$(printf "%s %s_%s.test" "$TEST_TARGETS" "$NAME" "$COLLECTOR")
GENERATED_OBJS="$GENERATED_OBJS $NAME.o"
}
gen_string_test() {
local COLLECTOR DEPS OBJDEPS
COLLECTOR=$1
DEPS="$2 examples/test_$COLLECTOR.o examples/string/uns_string.o"
OBJDEPS="examples/string/uns_string.h"
gen_test 'examples/string/test_small' "$COLLECTOR" \
"$DEPS" "$OBJDEPS"
gen_test 'examples/string/test_large' "$COLLECTOR" \
"$DEPS" "$OBJDEPS"
gen_test 'examples/string/test_inefficient' "$COLLECTOR" \
"$DEPS" "$OBJDEPS"
}
gen_hashtable_test() {
local COLLECTOR DEPS OBJDEPS
COLLECTOR=$1
DEPS="$2 examples/test_$COLLECTOR.o examples/hashtable/uns_hashtable.o"
OBJDEPS="examples/hashtable/uns_hashtable.h examples/hashtable/testnames.h"
gen_test 'examples/hashtable/test_insert' "$COLLECTOR" \
"$DEPS" "$OBJDEPS"
gen_test 'examples/hashtable/test_insert_delete' "$COLLECTOR" \
"$DEPS" "$OBJDEPS"
}
gen_tests() {
gen_string_test "$1" '${CHENEY_C89_OBJS}'
gen_hashtable_test "$1" '${CHENEY_C89_OBJS}'
}
gen_tests cheney_c89
printf "tests: %s\n" "$TEST_TARGETS"
printf 'clean_tests: %s\n\t${RM} -f %s %s\n' "$TEST_TARGETS" "$GENERATED_OBJS"

View File

@ -20,13 +20,44 @@
#include "uns.h"
/* Cheney copying collector in pure portable C89. */
/* Cheney copying collector in pure portable C89.
*
* The copying collector does not support weak references or mixed
* records. Every collection calls malloc() to obtain a new heap.
*
* This collector is useful for hosted implementations that want a
* resizable heap.
*/
void uns_cheney_c89_deinit(struct uns_gc *gc);
int uns_cheney_c89_init(struct uns_gc *gc);
int uns_cheney_c89_collect(struct uns_gc *gc);
void *uns_cheney_c89_alloc(struct uns_gc *gc, size_t bytes);
void *uns_cheney_c89_alloc_record(struct uns_gc *gc, size_t records);
struct uns_cheney_c89_statistics {
size_t usage_before;
size_t usage_after;
size_t collection_number;
};
typedef void (*uns_cheney_c89_collect_callback)
(Uns_GC, struct uns_cheney_c89_statistics *)
;
void uns_cheney_c89_set_new_heap_size(Uns_GC gc, size_t siz);
size_t uns_cheney_c89_get_new_heap_size(Uns_GC gc);
void uns_cheney_c89_set_collect_callback(
Uns_GC gc,
uns_cheney_c89_collect_callback cb
);
void *uns_cheney_c89_get(Uns_ptr p, size_t loc,
enum uns_fld_type *typ);
void uns_cheney_c89_set(Uns_ptr p, size_t loc,
enum uns_fld_type typ, void *newp);
void uns_cheney_c89_deinit(Uns_GC gc);
int uns_cheney_c89_collect(Uns_GC gc);
void *uns_cheney_c89_alloc(Uns_GC gc, size_t elems,
enum uns_bytes_type typ);
void *uns_cheney_c89_alloc_rec(Uns_GC gc, size_t elems,
enum uns_record_type typ);
int uns_cheney_c89_init(Uns_GC gc, size_t heap_size);
const extern size_t uns_cheney_c89_ctx_size;

View File

@ -1,52 +0,0 @@
#ifndef UNS_FORMAT_C89_H
#define UNS_FORMAT_C89_H
/* 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/>.
*/
#include "uns.h"
/* This is a strictly C89+ header format with relocation pointers and
* lengths.
*
* Forwarding pointers need to be stored in the header because in strict
* C, arithmetic on pointers is undefined for pointers not pointing into
* the same object.
*
* The length of the string is limited to "len" bytes and is stored as
* a `uns_sword`. If the word is negative, then the region is a record,
* and if the number is positive then the region is just bytes.
*/
struct uns_c89_relo_hdr {
void *relo;
uns_sword len;
};
#define uns_c89_relo_get_hdr(p)((struct uns_c89_relo_hdr *)(p) - 1)
size_t uns_c89_relo_get_len(void *p);
size_t uns_c89_relo_get_record_len(void *p);
#define uns_c89_relo_get_relo(p) (uns_c89_relo_get_hdr(p)->relo)
#define uns_c89_relo_set_relo(p, v) (uns_c89_relo_get_relo(p) = (v))
#define uns_c89_relo_is_record(p) (uns_c89_relo_get_hdr(p)->len < 0)
void *uns_c89_relo_init(void *p, size_t len_size, int is_record);
void uns_c89_relo_record_set(Uns_ptr rec, size_t i, void *v);
void *uns_c89_relo_record_get(Uns_ptr rec, size_t i);
#endif

View File

@ -18,6 +18,24 @@
*/
#include <stddef.h>
/********************************
* Compatability stuff.
********************************/
#if __STDC_VERSION__ >= 199101L
# define UNS_INLINE inline
# include <stdint.h>
#else
# define UNS_INLINE
# define SIZE_MAX ((size_t)0 - 1)
#endif
#if defined(__GNUC__)
# define UNS_UNUSED __attribute__((unused))
#else
# define UNS_UNUSED
#endif
#if !defined(UNS_WORD) && __STDC_VERSION__ >= 199101L
# include <stdint.h>
# include <limits.h>
@ -38,75 +56,215 @@
typedef UNS_WORD uns_word;
typedef UNS_SIGNED_WORD uns_sword;
/* This struct doesn't exist and is used to make a unique pointer for
* type checking.
*
* This pointer must be what was returned from an allocation (i.e. it points
* to the first byte but before any hidden header data).
/** Type for a pointer to a region.
*
* This struct doesn't exist and is used to make a unique pointer for
* type checking.
*
* This pointer must be what was returned from an allocation (i.e. it
* points to the first byte but before any hidden header data).
*/
typedef struct uns_ptr *Uns_ptr;
/** Doubly linked list used to
/** Flags for bytes regions. */
enum uns_bytes_type {
UNS_NOEXEC = 0
};
/** Flags for record regions. */
enum uns_record_type {
UNS_POINTERS_ONLY = 0
};
/** Type of a value in a record. */
enum uns_fld_type {
UNS_POINTER,
UNS_INTEGER,
/** Value is a weak pointer. */
UNS_WEAK
};
/** Container that boxes pointers for the runtime.
*
* Used for
*
* 1) Store roots of the GC,
* 2) Store GC pointers used in C and possibly not pointed-to anywhere
* else in GC space,
* 3) Preserve the location of a pointer after it moves.
*
* A root that is not a part of any GC MUST have ``prev`` and ``next``
* set to NULL.
*/
struct uns_ctr {
struct uns_ctr *prev;
/** Pointer to a region in the heap, or NULL.
*
* All roots must point into the same heap, or to NULL.
* Multiple roots are allowed to point to the same region.
*/
void *p;
/** This field is managed by the library.
* This must be set to NULL when initializing the container and
* must not be touched while the container is in the root list.
*/
struct uns_ctr *prev;
/** This field is managed by the library.
* This must be set to NULL when initializing the container and
* must not be touched while the container is in the root list.
*/
struct uns_ctr *next;
};
struct uns_gc {
void *ctx;
/* The collector only changes the pointer values of "p".
*
* * The collector never adds or removes roots.
* * The collector never rearranges the order of the container structures.
*
* The roots list can have repeated values.
*/
struct uns_ctr *roots;
size_t next_alloc;
void (*oom)(struct uns_gc *);
void (*after_gc)(struct uns_gc *);
size_t before_collection;
size_t after_collection;
long collection_number;
void (*deinit)(struct uns_gc *);
/* These functions, and only these functions, may invalidate
* pointers by either moving them or freeing them.
*/
int (*collect)(struct uns_gc *);
void *(*alloc)(struct uns_gc *, size_t);
void *(*alloc_record)(struct uns_gc *, size_t);
/* These functions MUST NOT cause collections. */
/* This function is optional. It is only provided if the GC can
* calculate the length of the pointer quickly.
*/
size_t (*len)(void *);
void (*record_set_ptr)(Uns_ptr, size_t, void *);
void *(*record_get_ptr)(Uns_ptr, size_t);
};
/** Add a root to the GC. The root must point to valid memory, or NULL. */
void uns_root_add(struct uns_gc *gc, struct uns_ctr *root);
/** Remove a root from the GC. It is OK to remove a root that is not a
* part of any GC using this function (the call will have no effect).
/** Opaque struct that stores the state of a garbage collected heap.
*
* The only thing that is stable about the layout of this struct is
* that the first field is ALWAYS a pointer to the collector context.
*
* The context pointer will always be the same pointer from init to
* deinit.
*/
void uns_root_remove(struct uns_gc *gc, struct uns_ctr *root);
typedef struct uns_gc *Uns_GC;
/** Size of `struct uns_gc` for allocation. */
extern const size_t uns_gc_size;
/** Get the context pointer for the collector. */
UNS_UNUSED static UNS_INLINE void *uns_ctx(Uns_GC gc)
{
return *((void **)gc);
}
/** Set the context pointer for the collector. */
UNS_UNUSED static UNS_INLINE void uns_set_ctx(Uns_GC gc, void *ctx)
{
*((void **)gc) = ctx;
}
/** Zero the memory of the generic GC struct. This will leak
* memory if a collector was initialized and not deinitialized
* beforehand.
*
* This must be called when the structure is allocated.
*/
void uns_gc_zero(Uns_GC gc);
/** Free memory associated with the GC. It is OK to call this
* with a GC that has not been initialized.
*/
void uns_deinit(Uns_GC gc);
/** Set deinit function. */
void uns_set_deinit(Uns_GC gc, void(*deinit)(Uns_GC));
/** Set OOM handler. */
void uns_set_oom(Uns_GC gc, void(*oom)(Uns_GC));
/** Call OOM handler. */
void uns_on_oom(Uns_GC gc);
/** Set garbage collection function. */
void uns_set_collect(Uns_GC gc, int(*collect)(Uns_GC));
/** Set allocation function. */
void uns_set_alloc(Uns_GC gc,
void*(*alloc)(Uns_GC, size_t, enum uns_bytes_type));
/** Set allocation function. */
void uns_set_alloc_rec(Uns_GC gc,
void*(*alloc_rec)(Uns_GC, size_t, enum uns_record_type));
/** Set record setter function. */
void uns_set_set(Uns_GC gc, void(*set)(Uns_ptr, size_t, enum uns_fld_type,
void*));
/** Set record getter function. */
void uns_set_get(Uns_GC gc, void*(*get)(Uns_ptr, size_t,
enum uns_fld_type *));
/** Force a collection. */
int uns_collect(Uns_GC gc);
/** Allocate a region of bytes.
*
* This function will only return NULL if:
*
* 1. The type of region requested in `typ` is not supported, or
* 2. The collector ran out of memory and the out-of-memory handler
* did not exit the program.
*
* Results are undefined if the type is not supported, but all
* allocators can be compiled with a mode to abort in those cases.
*/
void *uns_alloc(Uns_GC gc, size_t bytes, enum uns_bytes_type typ);
/** Allocate a record.
*
* This function will only return NULL if:
*
* 1. The type of region requested in `typ` is not supported, or
* 2. The collector ran out of memory and the out-of-memory handler
* did not exit the program.
*
* `len` is the number of elements in the record.
*/
void *uns_alloc_rec(Uns_GC gc, size_t len, enum uns_record_type typ);
/** Free a region.
*
* This is a no-op if freeing is not supported by the collector, or
* if `p` is NULL.
*/
void uns_free(Uns_GC gc, Uns_ptr p);
/** Get a field from a record.
*
* ## Parameters
* - `notnull gc`: GC data.
* - `notnull rec`: Pointer to a record.
* - `ind`: Index of the field in the record. This is not the byte
* offset.
* - `out typ`: What type of field (integer, weak, etc.) was contained
* in the field. May be NULL.
*/
void *uns_get(Uns_GC gc, Uns_ptr rec, size_t ind, enum uns_fld_type *typ);
/** Set a field in a record.
*
* ## Parameters
* - `notnull gc`: GC Data.
* - `notnull rec`: Pointer to a record.
* - `ind`: Index of the field in the record. This is not the byte
* offset.
* - `typ`: The type of record to store. Behavior is undefined if the
* field type is not supported, either by the record or by the heap.
* - `val`: Value to store.
*/
void uns_set(Uns_GC gc, Uns_ptr rec, size_t ind, enum uns_fld_type typ,
void *val);
/** Add a root to the GC.
*
* ## Parameters
* - `notnull gc`: GC data.
* - `notnull in out root`: Pointer to the container containing the root.
* Do not modify any other field in the container, except for p, after
* this function returns.
*/
void uns_root_add(Uns_GC gc, struct uns_ctr *root);
/** Remove a root from the GC.
*
* ## Parameters
* - `notnull gc`: GC data.
* - `notnull in out root`: Pointer to the container containing the
* root to be removed. If the root is not a part of any heap (the
* prev and next pointers are NULL), then this function is a noop.
* This function does not modify p.
*/
void uns_root_remove(Uns_GC gc, struct uns_ctr *root);
/** Get the head of the roots list.
*/
struct uns_ctr *uns_roots(Uns_GC gc);
#endif

139
uns.c
View File

@ -15,10 +15,140 @@
* <https://www.gnu.org/licenses/>.
*/
/* TODO: add support for freestanding */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "uns.h"
void uns_root_add(struct uns_gc *gc, struct uns_ctr *root)
/** 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;
@ -28,7 +158,7 @@ void uns_root_add(struct uns_gc *gc, struct uns_ctr *root)
gc->roots = root;
}
void uns_root_remove(struct uns_gc *gc, struct uns_ctr *root)
void uns_root_remove(Uns_GC gc, struct uns_ctr *root)
{
if (root->prev) {
root->prev->next = root->next;
@ -44,3 +174,8 @@ void uns_root_remove(struct uns_gc *gc, struct uns_ctr *root)
root->prev = root->next = NULL;
}
struct uns_ctr *uns_roots(Uns_GC gc)
{
return gc->roots;
}