universalservice/cheney_c89.c

208 lines
5.2 KiB
C

/* Copyright (C) 2024 Peter McGoron
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <assert.h>
#include <string.h>
#include "uns.h"
#include "internal/c89_relo.h"
#include "cheney_c89.h"
struct ctx {
unsigned char *tospace;
unsigned char *tospace_end;
unsigned char *tospace_alloc;
};
const size_t uns_cheney_c89_ctx_size = sizeof(struct ctx);
static void zero_ctx(struct ctx *ctx)
{
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;
free(ctx->tospace);
}
/* 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)
{
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;
}
/* Move an entire object pointed to by p from fromspace to tospace. */
static unsigned char *relocate(struct uns_gc *gc, unsigned char *p)
{
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;
}
/* 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 l = uns_c89_relo_get_record_len(p);
size_t i;
void *newp;
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);
}
}
int uns_cheney_c89_collect(struct uns_gc *gc)
{
/* Save fromspace */
struct ctx *ctx = gc->ctx;
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;
assert(gc->next_alloc >= fromspace_lim - fromspace);
/* Bail out immediately if allocation fails. This preserves
* the objects as they were.
*/
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;
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);
}
free(fromspace);
if (gc->after_gc)
gc->after_gc(gc);
return 1;
}
static void *alloc(struct 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);
/* Make sure to check for header space when allocating */
if (ctx->tospace_end - ctx->tospace_alloc < total_bytes)
uns_cheney_c89_collect(gc);
if (ctx->tospace_end - ctx->tospace_alloc < total_bytes)
gc->oom(gc);
return raw_alloc(ctx, bytes, is_record);
}
void *uns_cheney_c89_alloc(struct uns_gc *gc, size_t bytes)
{
return alloc(gc, bytes, 0);
}
void *uns_cheney_c89_alloc_record(struct uns_gc *gc, size_t records)
{
size_t i;
unsigned char *res = alloc(gc, records*sizeof(void *), 1);
for (i = 0; i < records; i++)
uns_c89_relo_record_set((void *)res, i, NULL);
return res;
}
int uns_cheney_c89_init(struct uns_gc *gc)
{
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)
return 0;
ctx->tospace_alloc = ctx->tospace;
ctx->tospace_end = ctx->tospace + gc->next_alloc;
return 1;
}