toys

toys.git
git clone git://git.lenczewski.org/toys.git
Log | Files | Refs | README | LICENSE

commit 80a86b247739696fac607fb85a09f3a0ac50c4b4
parent 9769f561b8ca0bb35106780d0e2430a0edec7a3a
Author: MikoĊ‚aj Lenczewski <mblenczewski@gmail.com>
Date:   Sat, 12 Apr 2025 12:36:40 +0000

Add relative pointer implementation.

Diffstat:
Arelptr.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Arelptr.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 158 insertions(+), 0 deletions(-)

diff --git a/relptr.c b/relptr.c @@ -0,0 +1,108 @@ +#define HEADER_IMPL +#include "relptr.h" + +#include <string.h> +#include <stdio.h> + +/* defining a struct is guaranteed to introduce a new type, and allows us to + * have a semblance of strong-ish typing for relptrs. this avoids nasty bugs + * where you confuse two different relptr types with the same backing type. + */ +typedef struct mylist_relptr { + int64_t v; +} mylist_relptr_t; + +/* our example type, a singly linked list using relative pointers. + */ +struct mylist { + int a; + mylist_relptr_t next; +}; + +static void +mylist_append(struct mylist *restrict list, struct mylist *restrict node); + +static void +mylist_print(struct mylist const *list); + +/* i like defining helper functions that give me a more compact way of using + * the RELPTR_XXX() macros. this also makes debugging slightly easier as you + * can step into the helper functions and inspect the relative pointer value + * and the absolute pointer value. + */ +static inline mylist_relptr_t +mylist_abs2rel(void const *restrict base, struct mylist const *restrict absptr) +{ + mylist_relptr_t res = { + .v = RELPTR_ABS2REL(__typeof__ (res.v), base, absptr), + }; + + return res; +} + +static inline struct mylist * +mylist_rel2abs(void const *base, struct mylist_relptr relptr) +{ + void *res = RELPTR_REL2ABS(struct mylist *, __typeof__ (relptr.v), base, relptr.v); + return res; +} + +/* a basic implementation to demonstrate usage of the relative pointer helpers. + */ +static void +mylist_append(struct mylist *restrict list, struct mylist *restrict node) +{ + if (list->next.v != RELPTR_NULL) { /* have next element in list */ + mylist_append(mylist_rel2abs(list, list->next), node); + } else { /* at end of list */ + list->next = mylist_abs2rel(list, node); + } +} + +static void +mylist_print(struct mylist const *list) +{ + printf("mylist:\n"); + + size_t i = 0; + while (list) { + printf("\t%zu = { %p, { a: %d, next: %p, } }\n", + i++, list, list->a, mylist_rel2abs(list, list->next)); + list = mylist_rel2abs(list, list->next); + } +} + +int +main(void) +{ + struct mylist buf[4], buf2[4]; + struct mylist *foo = buf, *bar = buf + 1, *baz = buf + 2; + + foo->a = 1; + bar->a = 2; + baz->a = 3; + + /* with custom types for the relptr, this step is a little opaque... + */ + foo->next.v = bar->next.v = baz->next.v = RELPTR_NULL; + + printf("before appending to list...\n"); + mylist_print(foo); + + mylist_append(foo, bar); + mylist_append(foo, baz); + + printf("after appending to list...\n"); + mylist_print(foo); + + /* with absolute pointers, copying the list to a different memory + * location would cause the list to break (it would still point to the + * old memory location). + */ + memcpy(buf2, buf, sizeof buf); + + printf("after copying to different memory location...\n"); + mylist_print(buf2); + + return 0; +} diff --git a/relptr.h b/relptr.h @@ -0,0 +1,50 @@ +#ifndef RELPTR_H +#define RELPTR_H + +#include <stddef.h> +#include <stdint.h> + +/* relative pointers allow pointers that can be copied around in memory whilst + * still remaning valid. they allow this by forcing the user to specify a "base" + * against which the pointer is calculated, as opposed to using the default + * base of 0 (when using traditional, absolute pointers). + */ + +/* we would like our relative pointers to keep using 0 as a "null" value, for + * ergonomics reasons. this makes initialisation of a data structure using + * relative pointers identical to the same structure using absolute pointers. + */ +#define RELPTR_NULL (0) + +/* in a naive implementation of relative pointers: `(uintptr_t) base + offset`, + * using a RELPTR_NULL of 0 would result in a "null" pointer that points to + * itself. this might be undesirable, as we might want to represent recursive + * data structures that do indeed point to themselves. so, we "mask" our + * relative pointer before and after conversion from an absolute pointer, to + * set the "null value" to some unlikely to be used value. + */ +#define _RELPTR_MASK(ty_relptr) ((ty_relptr) 1 << ((sizeof(ty_relptr) * 8) - 1)) + +/* we need to convert between a pointer offset and a relative pointer. + */ +#define _RELPTR_ENC(ty_relptr, ptroff) \ + ((ty_relptr) ((ptroff) ^ _RELPTR_MASK(ty_relptr))) + +#define _RELPTR_DEC(ty_relptr, relptr) \ + ((ty_relptr) ((relptr) ^ _RELPTR_MASK(ty_relptr))) + +/* these are our main helper macros, that convert between a base and an absolute + * pointer to a relative pointer, and between a base and a relative pointer back + * to an absolute pointer. + */ +#define RELPTR_ABS2REL(ty_relptr, base, absptr) \ + ((absptr) \ + ? _RELPTR_ENC(ty_relptr, (char *) absptr - (char *) base) \ + : RELPTR_NULL) + +#define RELPTR_REL2ABS(ty_absptr, ty_relptr, base, relptr) \ + (((relptr)) \ + ? ((ty_absptr) ((char *) base + _RELPTR_DEC(ty_relptr, relptr))) \ + : NULL) + +#endif /* RELPTR_H */