Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
array_list.c File Reference
#include "serene/rt/impl/array_list.h"
#include <stdint.h>
#include <stdio.h>
#include "serene/rt/context.h"
#include "serene/utils.h"
Include dependency graph for array_list.c:

Go to the source code of this file.

Macros

#define AL_LOG(FMT, ...)

Functions

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.
static array_list_elem_tarray_list_create_page (const srn_context_t *ctx)
 Create a PAGE (just slots for the data).
static array_list_node_tarray_list_new_node (const srn_context_t *ctx)
static array_list_node_tarray_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.
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 place.
array_list_t array_list_empty (const srn_context_t *ctx)
 Create an empty array list in the given context ctx.
srn_error_tarray_list_push (array_list_t *al, array_list_elem_t x)
 Push the given element x to the end of array_list al.
static srn_error_tarray_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.
array_list_maybe_element_t array_list_get (const array_list_t *al, array_list_index_t n)
 Negative index is not supported.
srn_error_tarray_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.
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 promotion that array_list_push does.
static void array_list_drop_last (array_list_t *al)
 Drop the final element, keeping the tail as the physical end of the sequence.
srn_error_tarray_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 to keep the sequence contiguous.

Macro Definition Documentation

◆ AL_LOG

#define AL_LOG ( FMT,
... )

Definition at line 30 of file array_list.c.

Function Documentation

◆ array_list_create_page()

array_list_elem_t * array_list_create_page ( const srn_context_t * ctx)
inlinestatic

Create a PAGE (just slots for the data).

Definition at line 42 of file array_list.c.

42 {
45
46 for (size_t i = 0; i < AL_BR; i++) {
47 p[i] = nullptr;
48 }
49 return p;
50}
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_BR
branching factor (power of two)
Definition array_list.h:103
#define ALLOCN(ctx, T, N)
Definition context.h:85
#define PANIC_IF_NULL(ptr)
Definition utils.h:66
Here is the caller graph for this function:

◆ array_list_depth_index()

uint16_t array_list_depth_index ( const uint8_t depth,
const uint64_t index )
inlinestatic

Find the array index of the given index, considering the given depth.

depth can be thought of, as which 5 bits of the index are we looking at (AL_SHIFT is 5).

Definition at line 37 of file array_list.c.

37 {
38 return (uint16_t)((index >> (uint8_t)(depth * AL_SHIFT)) & AL_MASK);
39}
#define AL_SHIFT
log2(AL_BR)
Definition array_list.h:106
#define AL_MASK
Definition array_list.h:107
Here is the caller graph for this function:

◆ array_list_drop_last()

void array_list_drop_last ( array_list_t * al)
static

Drop the final element, keeping the tail as the physical end of the sequence.

Definition at line 288 of file array_list.c.

288 {
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}
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
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
Here is the call graph for this function:
Here is the caller graph for this function:

◆ array_list_empty()

array_list_t array_list_empty ( const srn_context_t * ctx)
nodiscard

Create an empty array list in the given context ctx.

Definition at line 101 of file array_list.c.

101 {
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}
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_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
const srn_context_t * ctx
The context that owns every allocation the array list retains.
Definition array_list.h:183
Here is the call graph for this function:
Here is the caller graph for this function:

◆ array_list_get()

array_list_maybe_element_t array_list_get ( const array_list_t * al,
array_list_index_t n )
nodiscard

Negative index is not supported.

Look up the element at the given index n in the given array list al.

Definition at line 235 of file array_list.c.

235 {
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}
int n
Definition acutest.h:525
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
array_list_elem_t data
Definition array_list.h:142
Here is the call graph for this function:
Here is the caller graph for this function:

◆ array_list_new_node()

array_list_node_t * array_list_new_node ( const srn_context_t * ctx)
inlinestatic

Definition at line 52 of file array_list.c.

52 {
55 n->children = array_list_create_page(ctx);
56 return n;
57}
#define ALLOC(ctx, T)
Definition context.h:84
We have two type of node that both are implemented using the same data structure.
Definition array_list.h:157
Here is the call graph for this function:
Here is the caller graph for this function:

◆ array_list_new_path()

array_list_node_t * array_list_new_path ( const srn_context_t * ctx,
uint8_t depth,
array_list_node_t * leaf )
static

Walk from the root to a possible leaf node and create all the inner and leaf nodes if necessary.

We use this only when we insert new data

Definition at line 64 of file array_list.c.

64 {
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}
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 array_list_node_t * array_list_new_node(const srn_context_t *ctx)
Definition array_list.c:52
Here is the call graph for this function:
Here is the caller graph for this function:

◆ array_list_pull_last_leaf()

void array_list_pull_last_leaf ( array_list_t * al)
static

Detach the rightmost leaf from the trie and hand its buffer back as the tail, the inverse of the promotion that array_list_push does.

Only called when the tail has drained to empty while the trie still holds elements, so the trie holds whole leaves only and len is a multiple of AL_BR. The trie depth is left as is, an over-tall trie stays correct for reads, updates, and pushes.

Definition at line 264 of file array_list.c.

264 {
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}
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
array_list_elem_t * children
We allocate children to be a buffer of AL_BR number of pointers.
Definition array_list.h:159
Here is the call graph for this function:
Here is the caller graph for this function:

◆ array_list_push()

srn_error_t * array_list_push ( array_list_t * al,
array_list_elem_t x )
nodiscard

Push the given element x to the end of array_list al.

Definition at line 114 of file array_list.c.

114 {
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}
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
#define AL_LOG(FMT,...)
Definition array_list.c:30
#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
#define AL_MAX_DEPTH
Maximum trie depth, meaning the level of the root.
Definition array_list.h:116
@ AL_LIMIT_REACHED
Definition errors.h:90
#define ERR(ctx, err, msg)
Definition errors.h:170
Here is the call graph for this function:
Here is the caller graph for this function:

◆ array_list_push_leaf()

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 )
static

Insert leaf below node at the position of logical index index, mutating node and its descendants in place.

Missing inner nodes along the path are created on the way down.

Definition at line 78 of file array_list.c.

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}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ array_list_remove()

srn_error_t * array_list_remove ( array_list_t * al,
array_list_index_t n )
nodiscard

Remove the element at the given index n from the array list al, shifting every later element down one to keep the sequence contiguous.

Returns null on success, or an error when n is out of bounds.

Definition at line 301 of file array_list.c.

301 {
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}
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
size_t array_list_index_t
Definition array_list.h:96
A runtime error, a tag classifying the failure and a human-readable message.
Definition errors.h:147
#define PANIC_IF(cond, msg)
Definition utils.h:59
Here is the call graph for this function:
Here is the caller graph for this function:

◆ array_list_slot()

srn_error_t * array_list_slot ( const array_list_t * al,
array_list_index_t n,
array_list_elem_t ** out_slot )
static

Resolve the address of the slot backing logical index n into *out_slot.

Returns null on success and an error otherwise, leaving *out_slot null when it fails.

Definition at line 198 of file array_list.c.

198 {
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}
void * srn_allocate(const srn_context_t *ctx, size_t size, size_t alignment)
Definition context.c:73
@ CORRUPTED_ARRAY_LIST
Definition errors.h:93
@ ABSURD
Definition errors.h:87
@ INDEX_OUT_OF_BOUND
Definition errors.h:91
#define PANIC(msg)
Definition utils.h:53
Here is the call graph for this function:
Here is the caller graph for this function:

◆ array_list_update()

srn_error_t * array_list_update ( array_list_t * al,
array_list_index_t n,
array_list_elem_t x )
nodiscard

Update the given index n of the array list al with the new element x.

Returns null on success, or an error when n is out of bounds.

Definition at line 246 of file array_list.c.

246 {
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}
Here is the call graph for this function:
Here is the caller graph for this function: