toys

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

commit 9769f561b8ca0bb35106780d0e2430a0edec7a3a
Author: Mikołaj Lenczewski <mblenczewski@gmail.com>
Date:   Sat, 12 Apr 2025 11:59:24 +0000

Initial commit

Diffstat:
A.editorconfig | 17+++++++++++++++++
A.gitignore | 3+++
ALICENSE | 18++++++++++++++++++
AREADME | 9+++++++++
Aarena.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aarena.h | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abuild.sh | 11+++++++++++
Aclean.sh | 5+++++
Alist.c | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alist.h | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 435 insertions(+), 0 deletions(-)

diff --git a/.editorconfig b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +guidelines = 80, 120, 160 + +[*.{c,h}] +indent_style = tab +indent_size = 8 + +[*.{md,txt}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +bin/ + +**/.*.swp diff --git a/LICENSE b/LICENSE @@ -0,0 +1,18 @@ +The MIT-Zero License + +Copyright (c) 2025 Mikołaj Lenczewski <mikolaj@lenczewski.org> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so. + +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 +AUTHORS OR COPYRIGHT HOLDERS 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. diff --git a/README b/README @@ -0,0 +1,9 @@ +toys +============================================================================== +A small collection of C data structures, algorithms, and macros that I have +used in the past. Written in (reasonably) portable C99. Includes basic test +programs. Includes some commentary for each toy. + +toys: Building +------------------------------------------------------------------------------ +To build, simply run `build.sh`. Similarly, to clean, simply run `clean.sh`. diff --git a/arena.c b/arena.c @@ -0,0 +1,88 @@ +#define HEADER_IMPL +#include "arena.h" + +#include <stdio.h> + +struct mystruct { + int a, b, c; +}; + +struct alignas(64) mystruct2 { + char buf[17]; +}; + +int +main(void) +{ + printf("IS_POW2(1): %d, IS_POW2(2): %d, IS_POW2(3): %d\n", + IS_POW2(1), IS_POW2(2), IS_POW2(3)); + + printf("IS_ALIGNED(2, 2): %d, IS_ALIGNED(1, 2): %d\n", + IS_ALIGNED(2, 2), IS_ALIGNED(1, 2)); + + printf("ALIGN_PREV(2, 2): %d, ALIGN_PREV(1, 2): %d\n", + ALIGN_PREV(2, 2), ALIGN_PREV(1, 2)); + + printf("ALIGN_NEXT(2, 2): %d, ALIGN_NEXT(1, 2): %d\n", + ALIGN_NEXT(2, 2), ALIGN_NEXT(1, 2)); + + char buf[1024]; + struct arena arena = { .ptr = buf, .cap = sizeof buf, .len = 0, }; + + printf("arena: buf: %p, cap: %zu, len: %zu\n", + arena.ptr, arena.cap, arena.len); + + struct mystruct *foo = arena_alloc(&arena, sizeof *foo, alignof(struct mystruct)); + assert(foo); + + printf("foo: %p, sizeof foo: %zu, alignof foo: %zu\n", + foo, sizeof *foo, alignof(struct mystruct)); + + foo->a = foo->b = foo->c = 42; + + struct mystruct *bar = ARENA_ALLOC_SIZED(&arena, struct mystruct); + assert(bar); + + printf("bar: %p, sizeof bar: %zu, alignof bar: %zu\n", + bar, sizeof *bar, alignof(struct mystruct)); + + bar->a = bar->b = bar->c = 69; + + assert(foo != bar); + + printf("arena_reset() ...\n"); + + printf("\tbefore reset: foo->a = %d\n", foo->a); + arena_reset(&arena); + printf("\tafter reset: foo->a = %d\n", foo->a); + + struct mystruct *baz = ARENA_ALLOC_SIZED(&arena, struct mystruct); + assert(baz); + assert(foo == baz); + + printf("\tafter reset: baz: %p, baz->a = %d, baz == foo: %d\n", + baz, baz->a, baz == foo); + + // struct mystruct2 *fee = ARENA_ALLOC_ARRAY(&arena, struct mystruct2, 2); + struct mystruct2 *fee = arena_alloc(&arena, sizeof *fee * 2, alignof(struct mystruct2)); + assert(fee); + + printf("fee: %p, sizeof fee: %zu, alignof fee: %zu\n", + fee, sizeof *fee, alignof(struct mystruct2)); + + printf("fee[0]: %p, fee[0] is aligned: %d, fee[1]: %p, fee[1] is aligned: %d\n", + &fee[0], IS_ALIGNED((uintptr_t) &fee[0], alignof(struct mystruct2)), + &fee[1], IS_ALIGNED((uintptr_t) &fee[1], alignof(struct mystruct2))); + + assert(IS_ALIGNED((uintptr_t) &fee[0], 64)); + assert(IS_ALIGNED((uintptr_t) &fee[1], 64)); + + char *mybuf = arena_alloc(&arena, 128, 256); + assert(mybuf); + + printf("mybuf: %p, sizeof mybuf: 128, alignof mybuf: 256\n", mybuf); + + assert(IS_ALIGNED((uintptr_t) mybuf, 256)); + + return 0; +} diff --git a/arena.h b/arena.h @@ -0,0 +1,68 @@ +#ifndef ARENA_H +#define ARENA_H + +#include <assert.h> +#include <stdalign.h> +#include <stddef.h> +#include <stdint.h> + +/* helper macros to allow us to use bitwise tricks to quickly and efficiently + * calculate aligned addresses and sizes. + */ +#define IS_POW2(v) (((v) & ((v) - 1)) == 0) +#define IS_ALIGNED(v, align) (((v) & ((align) - 1)) == 0) +#define ALIGN_PREV(v, align) ((v) & ~((align) - 1)) +#define ALIGN_NEXT(v, align) ALIGN_PREV(((v) + ((align) - 1)), (align)) + +struct arena { + void *ptr; + uint64_t cap, len; +}; + +/* arenas dont support freeing individual elements, instead allowing only to + * free all elements at once. + */ +inline void +arena_reset(struct arena *arena) +{ + arena->len = 0; +} + +/* we might want to allocate arbitrary buffers out of an arena, with any given + * alignment (as long as it is a power of two). + */ +inline void * +arena_alloc(struct arena *arena, size_t size, size_t align) +{ + assert(size); + assert(align); + assert(IS_POW2(align)); + + uintptr_t aligned_ptr = ALIGN_NEXT((uintptr_t) arena->ptr + arena->len, align); + if (aligned_ptr + size > (uintptr_t) arena->ptr + arena->cap) + return NULL; + + arena->len = (aligned_ptr - (uintptr_t) arena->ptr) + size; + + return (void *) aligned_ptr; +} + +/* helper macros to make allocation more concise. + */ +#define ARENA_ALLOC_ARRAY(arena, T, n) \ + ((T *) arena_alloc((arena), sizeof(T) * (n), alignof(T))) + +#define ARENA_ALLOC_SIZED(arena, T) \ + ARENA_ALLOC_ARRAY((arena), T, 1) + +#endif /* ARENA_H */ + +#ifdef HEADER_IMPL + +extern inline void +arena_reset(struct arena *arena); + +extern inline void * +arena_alloc(struct arena *arena, size_t size, size_t align); + +#endif /* HEADER_IMPL */ diff --git a/build.sh b/build.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +FLAGS="-Wall -Wextra -Wpedantic -Wno-format-pedantic -std=c99 -Og -g" + +set -ex + +mkdir -p bin + +for src in *.c; do + cc -o bin/$(basename -s .c $src) $src $FLAGS +done diff --git a/clean.sh b/clean.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -ex + +rm -rf bin diff --git a/list.c b/list.c @@ -0,0 +1,66 @@ +#define HEADER_IMPL +#include "list.h" + +struct mystruct { + int a; + struct list_node list_node; +}; + +int +main(void) +{ + struct list_node list = LIST_INIT(list); + list_print(&list, 0); + + struct mystruct a = {1,{0,0}}, b = {2,{0,0}}, c = {3,{0,0}}; + fprintf(stderr, "a: %p\n", (void *) &a.list_node); + fprintf(stderr, "b: %p\n", (void *) &b.list_node); + fprintf(stderr, "c: %p\n", (void *) &c.list_node); + + /* list_iter() */ + fprintf(stderr, "list_iter()...\n"); + list_push_head(&list, &a.list_node); + list_push_head(&list, &b.list_node); + list_push_head(&list, &c.list_node); + list_print(&list, 0); + + /* list_entry_iter() */ + { + fprintf(stderr, "list_entry_iter()...\n"); + fprintf(stderr, "mystruct list: %p, head: %p, tail: %p\n", + (void *) &list, (void *) LIST_HEAD(&list), (void *) LIST_TAIL(&list)); + + size_t i = 0; + struct mystruct *it; + LIST_ENTRY_ITER(&list, it, list_node) { + fprintf(stderr, "\t%zu = { %d, { %p, prev: %p, next: %p } }\n", + i++, it->a, + (void *) &it->list_node, + (void *) it->list_node.prev, + (void *) it->list_node.next); + } + } + + /* list_riter() */ + fprintf(stderr, "list_riter()...\n"); + list_print(&list, 1); + + /* list_entry_riter() */ + { + fprintf(stderr, "list_entry_riter()...\n"); + fprintf(stderr, "list: %p, head: %p, tail: %p\n", + (void *) &list, (void *) LIST_HEAD(&list), (void *) LIST_TAIL(&list)); + + size_t i = 0; + struct mystruct *it; + LIST_ENTRY_RITER(&list, it, list_node) { + fprintf(stderr, "\t%zu = { %d, { %p, prev: %p, next: %p } }\n", + i++, it->a, + (void *) &it->list_node, + (void *) it->list_node.prev, + (void *) it->list_node.next); + } + } + + return 0; +} diff --git a/list.h b/list.h @@ -0,0 +1,150 @@ +#ifndef LIST_H +#define LIST_H + +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +/* we define an "intrusive" list, meaning that out data struct contains a + * list_node member. this allows a type-generic list implementation, but + * necessitates need a way to get from a pointer to this list_node, to a + * pointer to the parent struct + */ +#define TO_PARENT(ptr, T, member) \ + ((T *) ((uintptr_t) (ptr) - offsetof(T, member))) + +/* we choose to implement a doubly-linked list for simplicity and flexible + * use in situations that require either singly-linked or doubly-linked lists + */ +struct list_node { + struct list_node *prev, *next; +}; + +/* shorthands for getting the head and tail members of a list + */ +#define LIST_HEAD(list) ((list)->next) +#define LIST_TAIL(list) ((list)->prev) + +/* instead of having NULL signify an empty list, we have the list itself be the + * "sentinel value". this approach simplifys some of the code (as we always + * point to a valid instance in our next and prev fields). this makes checking + * whether a list is empty slightly convoluted unfortunately. + */ +#define LIST_INIT(list) { .prev = &(list), .next = &(list), } +#define LIST_EMTPY(list) (LIST_HEAD(list) == (list) && LIST_TAIL(list) == (list)) + + +inline void +list_node_link(struct list_node *node, struct list_node *prev, struct list_node *next) +{ + node->prev = prev; + prev->next = node; + node->next = next; + next->prev = node; +} + +inline struct list_node * +list_node_unlink(struct list_node *node) +{ + node->prev->next = node->next; + node->next->prev = node->prev; + return node; +} + +/* some helpers to push and pop a node to and from an existing list + */ +inline void +list_push_head(struct list_node *restrict list, struct list_node *restrict node) +{ + list_node_link(node, list, LIST_HEAD(list)); +} + +inline void +list_push_tail(struct list_node *restrict list, struct list_node *restrict node) +{ + list_node_link(node, LIST_TAIL(list), list); +} + +inline struct list_node * +list_pop_head(struct list_node *list) +{ + struct list_node *node = list_node_unlink(LIST_HEAD(list)); + return node; +} + +inline struct list_node * +list_pop_tail(struct list_node *list) +{ + struct list_node *node = list_node_unlink(LIST_TAIL(list)); + return node; +} + +/* helper macros to allow simple iteration over the list_node instances, or + * over the parent types that comprise the list. + */ +#define LIST_ITER(list, it) \ + for ((it) = LIST_HEAD(list); (it) != (list); (it) = (it)->next) + +#define LIST_RITER(list, it) \ + for ((it) = LIST_TAIL(list); (it) != (list); (it) = (it)->prev) + +#define LIST_NODE_ENTRY(ptr, T, member) TO_PARENT(ptr, T, member) + +#define LIST_ENTRY_ITER(list, it, member) \ + for ((it) = LIST_NODE_ENTRY(LIST_HEAD(list), __typeof__ (*(it)), member); \ + &(it)->member != (list); \ + (it) = LIST_NODE_ENTRY((it)->member.next, __typeof__ (*(it)), member)) + +#define LIST_ENTRY_RITER(list, it, member) \ + for ((it) = LIST_NODE_ENTRY(LIST_TAIL(list), __typeof__ (*(it)), member); \ + &(it)->member != (list); \ + (it) = LIST_NODE_ENTRY((it)->member.prev, __typeof__ (*(it)), member)) + +inline void +list_print(struct list_node const *list, int reversed) +{ + fprintf(stderr, "list: %p, head: %p, tail: %p\n", + (void *) list, (void *) LIST_HEAD(list), (void *) LIST_TAIL(list)); + + struct list_node *it; + if (reversed) { + LIST_ITER(list, it) { + fprintf(stderr, "\t{ %p, prev: %p, next: %p }\n", + (void *) it, (void *) it->prev, (void *) it->next); + } + } else { + LIST_RITER(list, it) { + fprintf(stderr, "\t{ %p, prev: %p, next: %p }\n", + (void *) it, (void *) it->prev, (void *) it->next); + } + } +} + +#endif /* LIST_H */ + +#ifdef HEADER_IMPL + +extern inline void +list_node_link(struct list_node *restrict node, + struct list_node *restrict prev, struct list_node *restrict next); + +extern inline struct list_node * +list_node_unlink(struct list_node *node); + +extern inline void +list_push_head(struct list_node *restrict list, struct list_node *restrict node); + +extern inline void +list_push_tail(struct list_node *restrict list, struct list_node *restrict node); + +extern inline struct list_node * +list_pop_head(struct list_node *list); + +extern inline struct list_node * +list_pop_tail(struct list_node *list); + +extern inline void +list_print(struct list_node const *list, int reversed); + +#endif /* HEADER_IMPL */