Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
array_list.c
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
20
21#include <stdint.h>
22#include <stdio.h>
23
24#include "serene/rt/context.h"
25#include "serene/utils.h"
26
27#if defined(AL_DEBUG)
28# define AL_LOG(FMT, ...) DBG("ARRAY_LIST", FMT __VA_OPT__(, ) __VA_ARGS__)
29#else
30# define AL_LOG(FMT, ...)
31#endif
32
33/**
34 * Find the array index of the given `index`, considering the given `depth`. depth can be thought
35 * of, as which 5 bits of the index are we looking at (AL_SHIFT is 5).
36 */
37static inline uint16_t array_list_depth_index(const uint8_t depth, const uint64_t index) {
38 return (uint16_t)((index >> (uint8_t)(depth * AL_SHIFT)) & AL_MASK);
39}
40
41/// Create a PAGE (just slots for the data)
45
46 for (size_t i = 0; i < AL_BR; i++) {
47 p[i] = nullptr;
48 }
49 return p;
50}
51
55 n->children = array_list_create_page(ctx);
56 return n;
57}
58
59/**
60 * Walk from the root to a possible leaf node and create all the inner and leaf nodes if necessary.
61 * We use this only when we insert new data
62 */
63static array_list_node_t *
64array_list_new_path(const srn_context_t *ctx, uint8_t depth, array_list_node_t *leaf) {
65 if (depth == 0) {
66 return leaf;
67 }
68
70 n->children[0] = array_list_new_path(ctx, depth - 1, leaf);
71 return n;
72}
73
74/**
75 * Insert `leaf` below `node` at the position of logical index `index`, mutating `node` and its
76 * descendants in place. Missing inner nodes along the path are created on the way down.
77 */
79 const srn_context_t *ctx, array_list_node_t *node, uint8_t depth, uint64_t index,
81) {
82 uint16_t idx = array_list_depth_index(depth, index);
83
84 if (depth == 1) {
85 node->children[idx] = (array_list_elem_t)leaf;
86 return;
87 }
88
89 array_list_node_t *child = (array_list_node_t *)node->children[idx];
90 if (child == nullptr) {
91 node->children[idx] = (array_list_elem_t)array_list_new_path(ctx, depth - 1, leaf);
92 } else {
93 array_list_push_leaf(ctx, child, depth - 1, index, leaf);
94 }
95}
96
97// -----------------------------------------------------------------------------
98// Public API
99// -----------------------------------------------------------------------------
100
102 PANIC_IF_NULL(ctx);
103 array_list_t al;
104 al.len = 0;
105 al.tail_len = 0;
107 al.maybe_error = nullptr;
108 al.root = nullptr;
109 al.depth = 0;
110 al.ctx = ctx;
111 return al;
112}
113
115 const srn_context_t *ctx = al->ctx;
116 PANIC_IF_NULL(ctx);
117
118 if (al->len >= AL_MAX_ELEMENTS) {
119 return ERR(ctx, AL_LIMIT_REACHED, "Can't fit more elements in this sequence");
120 }
121
122 if (al->tail_len < AL_BR) {
123 // There is room in the tail. Insert the element and we're done.
124 al->tail[al->tail_len++] = x;
125 al->len++;
126 AL_LOG("Tail has space, TL: %d, L: %zu, D: %d", al->tail_len, al->len, al->depth);
127 return nullptr;
128 }
129
130 // We need a new tail node. Move the current tail to an inner node, and create a new one.
131
132 size_t sz_without_tail = al->len - (size_t)al->tail_len;
133
134 // Build a leaf around the current tail buffer. Sharing the buffer is safe.
136 PANIC_IF_NULL(leaf);
137
138 leaf->children = al->tail;
139
140 // Tail buffer is full and we need to to move it (no copy) inside the trie
141 // and allocate a new tail
142 if (al->root == nullptr) {
143 // This happens only once, at index (AL_BR + 1) and technically we can just
144 al->maybe_error = nullptr;
145 al->tail_len = 1;
146 al->len++;
147 al->tail = array_list_create_page(ctx);
148 al->tail[al->tail_len - 1] = x;
149 al->root = leaf;
150 al->depth = 0;
151 AL_LOG("Root is null, TL: %d, L: %zu, D: %d", al->tail_len, al->len, al->depth);
152
153 return nullptr;
154 }
155
156 // Root is not Null, so we have inner children and we need to find the slot which we need to move
157 // the tail into
158
159 // If the tree is full at current depth, grow a new root
160 if (sz_without_tail >> ((al->depth + 1) * AL_SHIFT)) {
161 // ^^ == floor(sz_without_tail / 2^(5*(d+1)))
162
163 // A deeper root would push the level shift (depth+1)*AL_SHIFT up to the
164 // width of size_t, so AL_MAX_DEPTH is the hard capacity ceiling.
165 if (al->depth >= AL_MAX_DEPTH) {
166 return ERR(ctx, AL_LIMIT_REACHED, "Can't fit more elements in this sequence");
167 }
168
169 array_list_node_t *new_root = array_list_new_node(ctx); // internal node
170 new_root->children[0] = al->root;
171 new_root->children[1] = array_list_new_path(ctx, al->depth, leaf);
172 al->root = new_root;
173 al->tail = array_list_create_page(ctx);
174 al->tail_len = 1;
175 al->tail[0] = x;
176 al->len++;
177 al->depth++;
178
179 return nullptr;
180 }
181
182 // Insert the leaf at the right fringe of the trie, mutating it in place.
183 array_list_push_leaf(ctx, al->root, al->depth, (uint64_t)sz_without_tail, leaf);
184
185 al->tail = array_list_create_page(ctx);
186 al->tail_len = 1;
187 al->tail[0] = x;
188 al->len++;
189 return nullptr;
190}
191
192/**
193 * Resolve the address of the slot backing logical index `n` into `*out_slot`.
194 * Returns null on success and an error otherwise, leaving `*out_slot` null when
195 * it fails.
196 */
197static srn_error_t *
199 const srn_context_t *ctx = al->ctx;
200 PANIC_IF_NULL(ctx);
201 *out_slot = nullptr;
202
203 if (n >= al->len) {
204 int msg_len = snprintf(nullptr, 0, "%zu", n);
205 PANIC_IF(msg_len < 0, "index formatting failed");
206 char *err_msg = srn_allocate(ctx, (size_t)msg_len + 1, alignof(char));
207 (void)snprintf(err_msg, (size_t)msg_len + 1, "%zu", n);
208 return ERR(ctx, INDEX_OUT_OF_BOUND, err_msg);
209 }
210
211 size_t tail_start = al->len - (size_t)al->tail_len;
212 if (n >= tail_start) {
213 *out_slot = &al->tail[n - tail_start];
214 return nullptr;
215 }
216
217 array_list_node_t *node = al->root;
218 for (int16_t d = al->depth; d >= 0; d--) {
219 uint16_t index = array_list_depth_index(d, n);
220 if (d == 0) {
221 *out_slot = &node->children[index];
222 return nullptr;
223 }
224 node = (array_list_node_t *)node->children[index];
225 if (!node) {
226 return ERR(ctx, CORRUPTED_ARRAY_LIST, "missing child");
227 }
228 }
229
230 PANIC("It should never happen");
231 return ERR(ctx, ABSURD, "");
232}
233
234/// Negative index is not supported
236 array_list_maybe_element_t result = {.data = nullptr, .maybe_error = nullptr};
237
238 array_list_elem_t *slot = nullptr;
239 result.maybe_error = array_list_slot(al, n, &slot);
240 if (slot) {
241 result.data = *slot;
242 }
243 return result;
244}
245
247 PANIC_IF_NULL(al);
248
249 array_list_elem_t *slot = nullptr;
250 srn_error_t *err = array_list_slot(al, n, &slot);
251 if (slot) {
252 *slot = x;
253 }
254 return err;
255}
256
257/**
258 * Detach the rightmost leaf from the trie and hand its buffer back as the tail,
259 * the inverse of the promotion that `array_list_push` does. Only called when the
260 * tail has drained to empty while the trie still holds elements, so the trie
261 * holds whole leaves only and `len` is a multiple of AL_BR. The trie depth is
262 * left as is, an over-tall trie stays correct for reads, updates, and pushes.
263 */
265 // REVIEW: don't rely on 64bit stuff instead of this, use a size_t or something
266 uint64_t last = al->len - 1;
267
268 if (al->depth == 0) {
269 // The root is the only leaf. Reclaiming it empties the trie.
270 al->tail = al->root->children;
271 al->root = nullptr;
272 return;
273 }
274
275 // Walk to the inner node just above the leaves along the rightmost path.
276 array_list_node_t *node = al->root;
277 for (uint8_t d = al->depth; d > 1; d--) {
278 node = (array_list_node_t *)node->children[array_list_depth_index(d, last)];
279 }
280
281 uint16_t idx = array_list_depth_index(1, last);
282 array_list_node_t *leaf = (array_list_node_t *)node->children[idx];
283 al->tail = leaf->children;
284 node->children[idx] = nullptr;
285}
286
287/// Drop the final element, keeping the tail as the physical end of the sequence.
289 if (al->tail_len == 0) {
290 // Earlier removes drained the tail; refill it from the last trie leaf so
291 // there is a physical slot to drop.
293 al->tail_len = AL_BR;
294 }
295
296 al->tail_len--;
297 al->tail[al->tail_len] = nullptr;
298 al->len--;
299}
300
302 PANIC_IF_NULL(al);
303
304 array_list_elem_t *dst = nullptr;
305 srn_error_t *err = array_list_slot(al, n, &dst);
306 if (err != nullptr) {
307 return err;
308 }
309
310 // Slide every later element down one slot, overwriting `n`. The slot just read
311 // becomes the next write target, so each step costs a single lookup. Indices
312 // stay in [0, len), so none of these lookups can fail.
313 for (array_list_index_t i = n; i + 1 < al->len; i++) {
314 array_list_elem_t *src = nullptr;
315 auto err = array_list_slot(al, i + 1, &src);
316 // At this point an error screws the entire array list, it's better to PANIC
317 PANIC_IF(err != nullptr, "array_list_remove failed. This should not happen");
318 *dst = *src;
319 dst = src;
320 }
321
322 // The last physical element is now a duplicate of the previous one.
324 return nullptr;
325}
int n
Definition acutest.h:525
array_list_maybe_element_t array_list_get(const array_list_t *al, array_list_index_t n)
Negative index is not supported.
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
static void array_list_push_leaf(const srn_context_t *ctx, array_list_node_t *node, uint8_t depth, uint64_t index, array_list_node_t *leaf)
Insert leaf below node at the position of logical index index, mutating node and its descendants in p...
Definition array_list.c:78
static uint16_t array_list_depth_index(const uint8_t depth, const uint64_t index)
Find the array index of the given index, considering the given depth.
Definition array_list.c:37
#define AL_LOG(FMT,...)
Definition array_list.c:30
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
static array_list_node_t * array_list_new_path(const srn_context_t *ctx, uint8_t depth, array_list_node_t *leaf)
Walk from the root to a possible leaf node and create all the inner and leaf nodes if necessary.
Definition array_list.c:64
static void array_list_pull_last_leaf(array_list_t *al)
Detach the rightmost leaf from the trie and hand its buffer back as the tail, the inverse of the prom...
Definition array_list.c:264
static array_list_node_t * array_list_new_node(const srn_context_t *ctx)
Definition array_list.c:52
static srn_error_t * array_list_slot(const array_list_t *al, array_list_index_t n, array_list_elem_t **out_slot)
Resolve the address of the slot backing logical index n into *out_slot.
Definition array_list.c:198
static array_list_elem_t * array_list_create_page(const srn_context_t *ctx)
Create a PAGE (just slots for the data).
Definition array_list.c:42
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
static void array_list_drop_last(array_list_t *al)
Drop the final element, keeping the tail as the physical end of the sequence.
Definition array_list.c:288
A bit-partitioned trie with a tail buffer, used as a growable indexed sequence in the runtime.
#define AL_SHIFT
log2(AL_BR)
Definition array_list.h:106
#define AL_MAX_ELEMENTS
Maximum number of elements an array list can hold: a full trie at AL_MAX_DEPTH plus a full tail.
Definition array_list.h:121
void * array_list_elem_t
We use generic pointers to refer to internal nodes, leaf nodes and even elements.
Definition array_list.h:138
#define AL_MASK
Definition array_list.h:107
#define AL_MAX_DEPTH
Maximum trie depth, meaning the level of the root.
Definition array_list.h:116
#define AL_BR
branching factor (power of two)
Definition array_list.h:103
size_t array_list_index_t
Definition array_list.h:96
void * srn_allocate(const srn_context_t *ctx, size_t size, size_t alignment)
Definition context.c:73
#define ALLOCN(ctx, T, N)
Definition context.h:85
#define ALLOC(ctx, T)
Definition context.h:84
@ AL_LIMIT_REACHED
Definition errors.h:90
@ CORRUPTED_ARRAY_LIST
Definition errors.h:93
@ ABSURD
Definition errors.h:87
@ INDEX_OUT_OF_BOUND
Definition errors.h:91
#define ERR(ctx, err, msg)
Definition errors.h:170
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
#define PANIC_IF_NULL(ptr)
Definition utils.h:66
#define PANIC_IF(cond, msg)
Definition utils.h:59
#define PANIC(msg)
Definition utils.h:53