apfl/src/resizable.c
Laria Carolin Chabowski ebf3fc89ff Introduce allocator abstraction
We now no longer call malloc/free/... directly, but use an allocator object
that is passed around.

This was mainly done as a preparation for a garbage collector: The
collector will need to know, how much memory we're using, introducing the
collector abstraction will allow the GC to hook into the memory allocation
and observe the memory usage.

This has other potential applications:

- We could now be embedded into applications that can't use the libc
  allocator.
- There could be an allocator that limits the total amount of used memory,
  e.g. for sandboxing purposes.
- In our tests we could use this to simulate out of memory conditions
  (implement an allocator that fails at the n-th allocation, increase n by
  one and restart the test until there are no more faked OOM conditions).

The function signature of the allocator is basically exactly the same as
the one Lua uses.
2022-02-08 22:53:13 +01:00

144 lines
3.1 KiB
C

#include <assert.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include "alloc.h"
#include "resizable.h"
void
apfl_resizable_init(void **mem, size_t *len, size_t *cap)
{
*mem = NULL;
*len = 0;
*cap = 0;
}
bool
apfl_resizable_resize(
struct apfl_allocator allocator,
size_t elem_size,
void **mem,
size_t *len,
size_t *cap,
size_t newlen
) {
// TODO: We're wasteful here by never actually shrinking the memory.
if (newlen <= *len || newlen < *cap) {
*len = newlen;
return true;
}
assert(newlen >= *cap);
if (!apfl_resizable_ensure_cap(allocator, elem_size, mem, cap, newlen)) {
return false;
}
*len = newlen;
return true;
}
bool
apfl_resizable_ensure_cap(
struct apfl_allocator allocator,
size_t elem_size,
void **mem,
size_t *cap,
size_t want_cap
) {
if (want_cap <= *cap) {
return true;
}
// TODO: We currently simply grow the memory to have space for exactly
// want_cap elements. It would probably be smarter to grow the memory
// a bit larger to reduce calls to realloc.
void *newmem = REALLOC_BYTES(allocator, *mem, *cap * elem_size, want_cap * elem_size);
if (newmem == NULL) {
return false;
}
*mem = newmem;
*cap = want_cap;
return true;
}
bool
apfl_resizable_ensure_cap_for_more_elements(
struct apfl_allocator allocator,
size_t elem_size,
void **mem,
size_t len,
size_t *cap,
size_t more_elements
) {
return apfl_resizable_ensure_cap(allocator, elem_size, mem, cap, len + more_elements); // TODO: What if len + more_elements overflows?
}
bool apfl_resizable_splice(
struct apfl_allocator allocator,
size_t elem_size,
void **mem,
size_t *len,
size_t *cap,
size_t cut_start,
size_t cut_len,
const void *other_mem,
size_t other_len
) {
if (cut_start > *len || cut_start + cut_len > *len) {
return false;
}
if (other_len > cut_len) {
if (!apfl_resizable_ensure_cap_for_more_elements(
allocator,
elem_size,
mem,
*len,
cap,
other_len - cut_len
)) {
return false;
}
}
size_t src_off = cut_start + cut_len;
size_t dst_off = cut_start + other_len;
memmove(
((char *)(*mem)) + (dst_off * elem_size),
((char *)(*mem)) + (src_off * elem_size),
(*len - cut_start - cut_len) * elem_size
);
if (other_len > 0 && other_mem != NULL) {
memcpy(
((char *)(*mem)) + cut_start * elem_size,
other_mem,
other_len * elem_size
);
}
*len += other_len - cut_len;
return true;
}
bool
apfl_resizable_append(struct apfl_allocator allocator, size_t elem_size, void **mem, size_t *len, size_t *cap, const void *other_mem, size_t other_len)
{
return apfl_resizable_splice(
allocator,
elem_size,
mem,
len,
cap,
*len,
0,
other_mem,
other_len
);
}