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(