A memory arena implementation targeting standard C - this is draft code for Slider 3 (https://mrsename.blogspot.com/), a new programming language implementation being developed. Other memory arena implementations (as far as I know) are large, complex, slow, proprietary or non-portable.
In the following code: An arena's memory block starts with metadata consisting of an array of "free lists" - linked lists of released spaces. Each index in the array is one less than a number of units - the unit size (specified, along with the arena's total number of units, when creating the arena) is the minimum size of a space. A space is allocated by calling "a_malloc", expanded-if-possible by calling "a_realloc" which expands it into a large-enough free space immediately after it (if no such space exists, the space's content is relocated instead), and released by calling "a_free" which coalesces it with any free space immediately before/after it - those 3 functions are analogous to the C standard functions "malloc", "realloc", and "free". Each space has a pointer-sized header (storing its size) and footer (pointing to the header if the space is deallocated) used when coalescing. The arena provides bump-pointer alllocation within the current free space; once it runs out, the largest free space becomes the new current one. The whole arena can be released at once by calling "a_del". Arenas can be nested (by repeated use of "#include") and can't be shared between threads.
//arena.h
//If this code is written correctly, it makes no assumptions of the target platform,
//other than C standard compatibility and that pointers and integers are interchangeable.
//The a_init function below initializes an arena. Its parameters are: a pointer to the
//arena struct, the unit size, and the total number of units.
//The program that includes this file specifies what action to take if an error happens.
#ifndef err
#define err \
{ \
printf("\nerror at line %d in ", __LINE__); \
printf("%s", __FILE__); \
exit(1); \
}
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//By default, the C standard allocator is the underlying allocator
#ifndef u_alloc
#define u_alloc malloc
#define u_free free
#endif
#ifndef ArenaSupport
//Needing to be overflow-safe, this code uses overflow blocking code
//at https://mega.nz/file/zgJRmRIC#HBHnY9uQGOEKkfl468U6lhg2ce9Rj9J8JZjOsokYyrE
#include "overflow.h"
//The header included above provides functions uadd, usub, umul, and udivL for safe
//unsigned addition, subtraction, multiplication, and division.
//On systems with virtual memory, memset as used below is perhaps implemented
//efficiently, e.g. by bringing in page(s) of zeroes.
#define bzero(b, len) memset((b), '\0', (len))
typedef void *ptr;
#define null NULL
#define itmAt(T, a, n) (T *)uadd((adr)a, umul(n, sizeof(T)))
#define adrAt(a, n) *(itmAt(adr, a, n))
#define dec1(f) usub(f, 1)
#define ptrsize sizeof(ptr)
#define Ptrs(n) umul(n, ptrsize)
#define uprint(l) printf("%" PRIu64 "\n", (unum)(l));
typedef struct
{
ptr data; // the arena's memory block
adr unitSize,
start, //start of the arena's content area (which is after the metadata)
fpos, //current free space's position
fsize, //current free space's size
units, //total number of units
size; //the arena's total content size
} ArenaSt;
typedef ArenaSt *Arena;
//size of header plus footer:
#define hfsize Ptrs(2)
adr b_space(ptr p)
{ //obtain header pointer from pointer to space's content:
return usub((adr)p, ptrsize);
}
ptr a_space(adr p)
{ //from header pointer, obtain pointer to space's content:
return (ptr)uadd(p, ptrsize);
}
adr roundUp(adr l, adr f)
{
adr rem = l % f;
if (rem > 0)
l = uadd(l, usub(f, rem));
return l;
}
adr r_space(Arena a, adr l)
{
l = uadd(l, hfsize);
//round up to multiple of unit size:
adr us = a->unitSize;
return roundUp(l, us);
}
//The free lists are doubly-linked. In a deallocated space, first comes the header, then
//the Next pointer, then the Previous pointer. For an allocated space, the footer is zero.
#define Size(p) adrAt(p, 0)
#define Next(o) adrAt(o, 1)
#define Prev(o) adrAt(o, 2)
#define Footer(p, osize) adrAt(uadd(p, usub(osize, ptrsize)), 0)
#define Index(bsize) dec1(udivL(bsize, a->unitSize))
#define MetaAt(pos) adrAt(a->data, pos)
#define Meta(bsize) MetaAt(Index(bsize))
#define incb(p, v) p = uadd((p), (adr)(v))
#define decb(p, v) p = usub((p), (adr)(v))
//end of arena's memory block:
#define End uadd(a->size, a->start)
//footer of space right before space S:
#define Before(S) adrAt(usub(S, ptrsize), 0)
void a_prepare(adr o, adr nsize)
{ //Prepares an allocated space
//store size in header:
Size(o) = nsize;
//set footer to zero:
Footer(o, nsize) = 0;
}
ptr use_space(Arena a, adr osize)
{
adr p = a->fpos;
a_prepare(p, osize);
decb(a->fsize, osize);
incb(a->fpos, osize);
return a_space(p);
}
void fl_remove(Arena a, adr o, adr bsize)
{
//remove a space from its free list:
adr prev = Prev(o);
adr sp = Next(o);
if (prev == 0)
{
Meta(bsize) = sp;
if (sp != 0)
Prev(sp) = 0;
}
else
{
//In a linked list, if A.next is B and B.next is C, then after removing B, A.next
//will be C and C.prev will be A.
Next(prev) = sp;
if (sp != 0)
Prev(sp) = prev;
}
}
void fl_add(Arena a, adr o, adr osize)
{
adr prev = Meta(osize);
Meta(osize) = o;
Next(o) = prev;
Prev(o) = 0;
if (prev != 0)
Prev(prev) = o;
Size(o) = osize;
Footer(o, osize) = o;
}
void join_after(Arena a, adr o, adr osize)
{
adr end = uadd(o, osize); //end of this space
if (end < End)
{
//is the current space right after this one?
if ((a->fsize != 0) && (end == a->fpos))
{
a->fpos = o;
incb(a->fsize, osize);
return;
}
else
{
adr asize = Size(end);
adr footer = Footer(end, asize);
//is the space right after this one free?
if (footer != 0)
{
incb(osize, asize);
//remove that space from its free list:
fl_remove(a, end, asize);
}
}
}
if ((a->fsize == 0) || (a->fpos == o))
{
a->fpos = o;
a->fsize = osize;
}
else
{
//add this space to its free list
fl_add(a, o, osize);
}
}
void a_free(Arena a, ptr op)
{
if (op == null)
return;
adr o = b_space(op);
adr osize = Size(o);
if (o > a->start)
{
//is the current space right before this one?
if ((a->fsize != 0) && (uadd(a->fpos, a->fsize) == o))
{
o = a->fpos;
incb(osize, a->fsize);
a->fsize = osize;
}
else
{
adr before = Before(o);
//is the space right before this one free?
if (before != 0)
{
adr bsize = Size(before);
o = before;
incb(osize, bsize);
//remove that space from its free list:
fl_remove(a, o, bsize);
}
}
}
join_after(a, o, osize);
}
void a_replace(Arena a)
{
if (a->fsize != 0)
{ //save current space into the free list matching its size
fl_add(a, a->fpos, a->fsize);
}
//replace current free space with largest one
for (adr f = dec1(a->units); f > 0; f = dec1(f))
{
adr p = MetaAt(dec1(f));
if (p != 0)
{
a->fpos = p;
a->fsize = umul(a->unitSize, f);
adr fs = Next(p);
MetaAt(dec1(f)) = fs;
if (fs != 0)
Prev(fs) = 0;
return;
}
}
a->fpos = 0;
a->fsize = 0;
}
ptr a_malloc(Arena a, adr osize)
{
if (osize == 0)
return null;
//check if arena is full
if (a->fsize == 0)
return null;
adr otsize = r_space(a, osize);
if (otsize > a->fsize)
{
a_replace(a);
if (otsize > a->fsize)
{
return null;
}
}
if (otsize == a->fsize)
{
ptr p = use_space(a, otsize);
a_replace(a);
return p;
}
return use_space(a, otsize);
}
ptr a_realloc(Arena a, ptr op, adr rnsize)
{
if (op == null)
return op;
if (rnsize == 0)
{
a_free(a, op);
return null;
}
adr nsize = r_space(a, rnsize);
adr o = b_space(op);
adr osize = Size(o);
if (nsize <= osize)
return op; //shrinking a space is not supported
adr end = uadd(o, osize); //end of this space
adr diff = usub(nsize, osize); //difference between new size and old size
if (end < End)
{
//is the current space right after this one, and if so, does it have sufficient size?
if ((end == a->fpos) && (a->fsize >= diff))
{
a_prepare(o, nsize);
decb(a->fsize, diff);
incb(a->fpos, diff);
if (a->fsize == 0)
{
a_replace(a);
}
return op;
}
else if ((end != a->fpos) || (a->fsize == 0))
{
adr asize = Size(end);
adr footer = Footer(end, asize);
//is the space right after this one free, and if so, does it have sufficient size?
if ((footer != 0) && (asize >= diff))
{
//remove that space from its free list:
fl_remove(a, end, asize);
if (asize > diff)
{
fl_add(a, uadd(end, diff), usub(asize, diff));
}
a_prepare(o, nsize);
return op;
}
}
}
ptr n = a_malloc(a, rnsize);
if (n == null)
{
a_free(a, op);
return null;
}
memcpy(n, op, usub(osize, hfsize));
a_free(a, op);
return n;
}
#define ArenaSupport
#endif
void a_del(Arena a)
{
u_free(a->data);
}
void a_init(Arena a, adr us, adr units)
{
//minimum unit size is 4 times pointer size
adr min = Ptrs(4);
if (us < min)
us = min;
//unit size must be multiple of pointer size
us = roundUp(us, ptrsize);
if (units < 2)
units = 2;
a->unitSize = us;
adr csize = umul(us, units);
a->size = csize;
a->fsize = csize;
adr msize = Ptrs(dec1(units)); //size of metadata
ptr d = u_alloc(uadd(msize, csize));
if (d == null)
err;
a->data = d;
bzero(d, msize);
a->fpos = uadd(msize, (adr)d);
a->start = a->fpos;
a->units = units;
}
#ifdef Test
int main(int argc, char **argvp)
{
ArenaSt z;
Arena a = &z;
a_init(a, 0, 12);
uprint(Ptrs(8));
uprint(a->fsize);
ptr q = a_malloc(a, Ptrs(8));
printf("q:");
uprint(q);
uprint(a->fsize);
ptr w = a_malloc(a, Ptrs(8));
printf("w:");
uprint(w);
uprint(a->fsize);
ptr e = a_malloc(a, Ptrs(8));
uprint(e);
uprint(a->fsize);
ptr r = a_malloc(a, Ptrs(8));
uprint(r);
uprint(a->fsize);
ptr n = a_malloc(a, 1);
printf("n:");
uprint(n); //null
a_free(a, w);
a_free(a, e);
uprint(a->fsize);
uprint(a->fpos);
r = a_realloc(a, r, Ptrs(16));
printf("r:");
uprint(r); //r is now where w was
uprint(a->fsize);
uprint(a->fpos);
a_free(a, r);
q = a_realloc(a, q, Ptrs(16));
printf("q:");
uprint(q); //q stays in same place
uprint(a->fsize);
uprint(a->fpos);
a_free(a, q);
ptr x[12];
for (adr i = 0; i < 12; incb(i, 2))
{
x[i] = a_malloc(a, 1);
ptr p = a_malloc(a, 1);
printf("p:");
uprint(p);
x[uadd(i, 1)] = p;
}
uprint(a->fsize);
uprint(a->fpos);
for (adr i = 0; i < 12; incb(i, 2))
{
a_free(a, x[uadd(i, 1)]);
}
uprint(a->fsize);
uprint(a->fpos);
for (adr i = 0; i < 12; incb(i, 2))
{
printf("x i:");
uprint(x[i]);
ptr p = a_malloc(a, 1);
printf("+ 1:");
uprint(p);
x[uadd(i, 1)] = p;
}
a_free(a, x[0]);
uprint(a->fsize);
uprint(a->fpos);
printf("x 8:");
uprint(x[8]);
a_free(a, x[8]);
uprint(a->fsize);
uprint(a->fpos);
printf("x 7:");
uprint(x[7]);
ptr p = a_realloc(a, x[7], Ptrs(6));
x[7] = p;
printf("x 7:");
uprint(p); //x[7] stays in same place
printf("x 6:");
uprint(x[6]);
a_free(a, x[7]);
uprint(a->fsize);
uprint(a->fpos);
p = a_realloc(a, x[6], Ptrs(10));
x[6] = p;
printf("x 6:");
uprint(p); //x[6] stays in same place
a_del(a);
return 0;
}
#endif
/*The code above is available for distribution and use, under the following terms:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR(S) OR
COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
Note: the "overflow.h" header which the above code uses and contains the location of (https://mega.nz/file/zgJRmRIC#HBHnY9uQGOEKkfl468U6lhg2ce9Rj9J8JZjOsokYyrE) defines the "adr" type. In the above code's next version, the size (in pointers) of the metadata array may be reduced to half the number of units. To test the above code, compile and run the following program:
#define Test
#include "arena.h"
Also, can anyone provide insight regarding the implications of licensing code under the terms above?
adr
but the code defines several variables as type adr. It would be very helpful for a review if you provided test code for this as well, such as a C source file. The license is not relevant since all stack exchange communities have their own license. You don't own the code after it is posted on stack exchange. \$\endgroup\$