Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
array_list.h
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <lxsameer@lxsameer.com>
4 *
5 * This library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19#pragma once
20
21/** @file
22 * A bit-partitioned trie with a tail buffer, used as a growable indexed
23 * sequence in the runtime. It has the same shape as the persistent `seq`
24 * (see seq.h and the `Ideal hash trees` paper), but this one is mutable.
25 *
26 * TL;DR
27 * - A wide trie (branching factor AL_BR) holds the bulk of the elements,
28 * and a small tail buffer holds the most recently pushed ones.
29 * - Elements are stored by reference as opaque `void *`. The caller owns
30 * their lifetime.
31 * - Every node lives in the owning context's memory, so nothing is freed
32 * until that context is released.
33 * - It is mutable in place. `array_list_push` updates the struct and the
34 * trie nodes directly. Copying the struct yields an alias that shares
35 * storage, not an independent snapshot.
36 * - Appends are fast. The tail absorbs pushes until it fills, then it
37 * becomes a leaf in the trie and a fresh tail starts.
38 * - Random access is shallow, at most AL_MAX_DEPTH + 1 pointer hops.
39 * - The maximum number of elements is AL_MAX_ELEMENTS.
40 * - THIS IS NOT THREAD SAFE. Operating on the array or the values has
41 * to be synchronized with a lock or something.
42 *
43 */
44
45// clang-format off
46/* AI Generated (🀦)
47 ------------------------------------------------------------------------------
48 Example at a readable scale, with AL_BR = 4 and AL_SHIFT = 2
49 ------------------------------------------------------------------------------
50 array_list state:
51 len = 11
52 tail_len = 3 // last 3 elements live in the tail
53 depth = 1 // one inner level above the leaves
54 trie = elements [0..7] // first 8 elements live in the trie
55 tail = elements [8, 9, 10]
56
57 High-level view
58 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ Trie (elements 0..7) ─────────────┐
59 β”‚ array_list_t β”‚ β”‚ (depth = 1, AL_BR = 4, AL_SHIFT = 2) β”‚
60 β”‚ len = 11 β”‚ β”‚ β”‚
61 β”‚ tail_len = 3 β”‚ β”‚ Root (inner node, level 1) β”‚
62 β”‚ depth = 1 β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
63 β”‚ root ────────┼───────►│ β”‚ c[0] c[1] c[2] c[3] β”‚ β”‚
64 β”‚ tail ──┐ β”‚ β”‚ └──┬──────┬───── (NULL) (NULL)β”€β”€β”˜ β”‚
65 β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β–Ό β”‚
66 β”‚ β”‚ Leaf(0..3) Leaf(4..7) β”‚
67 β–Ό β”‚ [0 1 2 3] [4 5 6 7] β”‚
68 Tail (8..10) β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
69 [ 8 | 9 | 10 ] Leaves hold up to AL_BR elements
70
71 Bit-partitioned indexing of the trie
72 For an index n in [0..7], with AL_SHIFT = 2:
73 hi = (n >> 2) & 0b11 // selects Root.c[hi]
74 lo = n & 0b11 // selects the leaf slot within that child
75
76 Example, n = 6 (binary 0b0110)
77 hi = 0b01 = 1 -> Root.c[1] -> Leaf(4..7)
78 lo = 0b10 = 2 -> Leaf(4..7)[2] = 6
79
80 Tail indexing
81 For n in [8..10], use tail[n - (len - tail_len)]
82 n = 9 -> tail[9 - (11 - 3)] = tail[1] = 9
83
84 Notes
85 * Only the right fringe and the tail change on a push.
86 * When tail_len reaches AL_BR the tail is promoted to a leaf and spliced
87 into the trie, updating the nodes on that path in place.
88 ------------------------------------------------------------------------------
89*/
90// clang-format on
91
92#include "serene/rt/errors.h"
93#include "serene/utils.h"
94
95typedef struct srn_context_t srn_context_t;
96typedef size_t array_list_index_t;
97
98// -----------------------------------------------------------------------------
99// Tunables
100// -----------------------------------------------------------------------------
101
102/// branching factor (power of two)
103#define AL_BR 32U
104
105/// log2(AL_BR)
106#define AL_SHIFT 5U
107#define AL_MASK (AL_BR - 1u)
108
109/**
110 * Maximum trie depth, meaning the level of the root. At this depth the trie holds
111 * AL_BR^(AL_MAX_DEPTH + 1) = 32^12 = 1,152,921,504,606,846,976 elements, which is the
112 * hard capacity ceiling of an array list. The bound also keeps the level shift
113 * (depth + 1) * AL_SHIFT below the width of size_t, so the index math never shifts by
114 * 64 or more.
115 */
116#define AL_MAX_DEPTH 11
117
118/**
119 * Maximum number of elements an array list can hold: a full trie at AL_MAX_DEPTH plus a full tail.
120 */
121#define AL_MAX_ELEMENTS (((size_t)1 << (AL_SHIFT * (AL_MAX_DEPTH + 1))) + AL_BR)
122
123// TODO(lxsameer): The current values of tunables are tuned for a 64bit systems. Since size_t is
124// smaller in a 32bit or 16bit systme, we need to retune them based on the platfrom. We are not
125// there but we have to eventually do it
126static_assert(
127 (size_t)AL_SHIFT * (AL_MAX_DEPTH + 1) < sizeof(size_t) * CHAR_BIT,
128 "AL_MAX_DEPTH exceeds what size_t can address on this platform"
129);
130
131/**
132 * We use generic pointers to refer to internal nodes, leaf nodes and even elements. It makes the
133 * calculation cruical to determining what type of data we are looking at, in each node.
134 * `array_list_elem_t` will be pointing to actual user data when the node is a leaf node (depth ==
135 * 0) and it will be pointing to the next node in the trie if the node is an inner node (depth !=
136 * 0).
137 */
138typedef void *array_list_elem_t;
139
144
145// -----------------------------------------------------------------------------
146// Node
147// -----------------------------------------------------------------------------
148
149/**
150 * We have two type of node that both are implemented using the same data structure. Inner nodes
151 * that point to other inner nodes or leaf nodes, and leaf nodes which points to actual elements of
152 * the sequence.
153 *
154 * The main factor in determining the nature of the node is the depth of the trie. Depth zero, means
155 * a leaf node and an inner node otherwise.
156 */
157typedef struct array_list_node_t {
158 /// We allocate `children` to be a buffer of AL_BR number of pointers
161
162typedef struct array_list_t {
164 /// logical length. While techically this implementation will support up to
165 /// 2^85 elements in each array_list, but we will limit it down to
166 /// (2^64-1)(UINT64_MAX)(on 64bit machines) in order to keep the `array_list_t` as
167 /// small as possible
168 size_t len;
169 /// 0..AL_BR
170 uint16_t tail_len;
171 /// tree depth in levels (0 == leaf level)
172 uint8_t depth;
173 /// NULL means β€œall data is in tail”
175 /// small tail array for fast push/pop. We allocate this in heap with SEQ_BR
176 /// size and move it later to the inner Nodes.
178
179 /// The context that owns every allocation the array list retains. `array_list_empty`
180 /// sets it. Pushes allocate new tail pages, leaves, and inner nodes in this
181 /// context's block chain. Elements are stored as is; the caller must
182 /// allocate them with a lifetime at least as long as this context.
185
186// -----------------------------------------------------------------------------
187// Public API
188// -----------------------------------------------------------------------------
189
190/**
191 * Create an empty array list in the given context `ctx`.
192 */
193[[nodiscard]] [[gnu::nonnull(1)]]
195
196/**
197 * Push the given element `x` to the end of array_list `al`.
198 */
199[[nodiscard]] [[gnu::nonnull(1)]]
201
202/**
203 * Update the given index `n` of the array list `al` with the new element `x`. Returns null on
204 * success, or an error when `n` is out of bounds.
205 */
206[[nodiscard]] [[gnu::nonnull(1)]]
208
209/**
210 * Look up the element at the given index `n` in the given array list `al`.
211 */
212[[nodiscard]] [[gnu::nonnull(1)]]
214
215/**
216 * Remove the element at the given index `n` from the array list `al`, shifting every later element
217 * down one to keep the sequence contiguous. Returns null on success, or an error when `n` is out of
218 * bounds.
219 */
220[[nodiscard]] [[gnu::nonnull(1)]]
int n
Definition acutest.h:525
array_list_maybe_element_t array_list_get(const array_list_t *al, array_list_index_t n)
Look up the element at the given index n in the given array list al.
Definition array_list.c:235
srn_error_t * array_list_update(array_list_t *al, array_list_index_t n, array_list_elem_t x)
Update the given index n of the array list al with the new element x.
Definition array_list.c:246
#define AL_SHIFT
log2(AL_BR)
Definition array_list.h:106
void * array_list_elem_t
We use generic pointers to refer to internal nodes, leaf nodes and even elements.
Definition array_list.h:138
srn_error_t * array_list_remove(array_list_t *al, array_list_index_t n)
Remove the element at the given index n from the array list al, shifting every later element down one...
Definition array_list.c:301
#define AL_MAX_DEPTH
Maximum trie depth, meaning the level of the root.
Definition array_list.h:116
array_list_t array_list_empty(const srn_context_t *ctx)
Create an empty array list in the given context ctx.
Definition array_list.c:101
srn_error_t * array_list_push(array_list_t *al, array_list_elem_t x)
Push the given element x to the end of array_list al.
Definition array_list.c:114
size_t array_list_index_t
Definition array_list.h:96
Error handling for the runtime.
array_list_elem_t data
Definition array_list.h:142
We have two type of node that both are implemented using the same data structure.
Definition array_list.h:157
array_list_elem_t * children
We allocate children to be a buffer of AL_BR number of pointers.
Definition array_list.h:159
array_list_node_t * root
NULL means β€œall data is in tail”
Definition array_list.h:174
uint8_t depth
tree depth in levels (0 == leaf level)
Definition array_list.h:172
array_list_elem_t * tail
small tail array for fast push/pop.
Definition array_list.h:177
size_t len
logical length.
Definition array_list.h:168
uint16_t tail_len
0..AL_BR
Definition array_list.h:170
const srn_context_t * ctx
The context that owns every allocation the array list retains.
Definition array_list.h:183
A runtime error, a tag classifying the failure and a human-readable message.
Definition errors.h:147