Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
array_list.h File Reference

A bit-partitioned trie with a tail buffer, used as a growable indexed sequence in the runtime. More...

#include "serene/rt/errors.h"
#include "serene/utils.h"
Include dependency graph for array_list.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  array_list_maybe_element_t
struct  array_list_node_t
 We have two type of node that both are implemented using the same data structure. More...
struct  array_list_t

Macros

#define AL_BR   32U
 branching factor (power of two)
#define AL_SHIFT   5U
 log2(AL_BR)
#define AL_MASK   (AL_BR - 1u)
#define AL_MAX_DEPTH   11
 Maximum trie depth, meaning the level of the root.
#define AL_MAX_ELEMENTS   (((size_t)1 << (AL_SHIFT * (AL_MAX_DEPTH + 1))) + AL_BR)
 Maximum number of elements an array list can hold: a full trie at AL_MAX_DEPTH plus a full tail.

Typedefs

typedef size_t array_list_index_t
typedef void * array_list_elem_t
 We use generic pointers to refer to internal nodes, leaf nodes and even elements.
typedef struct array_list_maybe_element_t array_list_maybe_element_t
typedef struct array_list_node_t array_list_node_t
 We have two type of node that both are implemented using the same data structure.
typedef struct array_list_t array_list_t

Functions

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.
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.
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.
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.

Detailed Description

A bit-partitioned trie with a tail buffer, used as a growable indexed sequence in the runtime.

It has the same shape as the persistent seq (see seq.h and the Ideal hash trees paper), but this one is mutable.

TL;DR

  • A wide trie (branching factor AL_BR) holds the bulk of the elements, and a small tail buffer holds the most recently pushed ones.
  • Elements are stored by reference as opaque void *. The caller owns their lifetime.
  • Every node lives in the owning context's memory, so nothing is freed until that context is released.
  • It is mutable in place. array_list_push updates the struct and the trie nodes directly. Copying the struct yields an alias that shares storage, not an independent snapshot.
  • Appends are fast. The tail absorbs pushes until it fills, then it becomes a leaf in the trie and a fresh tail starts.
  • Random access is shallow, at most AL_MAX_DEPTH + 1 pointer hops.
  • The maximum number of elements is AL_MAX_ELEMENTS.
  • THIS IS NOT THREAD SAFE. Operating on the array or the values has to be synchronized with a lock or something.

Definition in file array_list.h.

Macro Definition Documentation

◆ AL_BR

#define AL_BR   32U

branching factor (power of two)

Definition at line 103 of file array_list.h.

◆ AL_MASK

#define AL_MASK   (AL_BR - 1u)

Definition at line 107 of file array_list.h.

◆ AL_MAX_DEPTH

#define AL_MAX_DEPTH   11

Maximum trie depth, meaning the level of the root.

At this depth the trie holds AL_BR^(AL_MAX_DEPTH + 1) = 32^12 = 1,152,921,504,606,846,976 elements, which is the hard capacity ceiling of an array list. The bound also keeps the level shift (depth + 1) * AL_SHIFT below the width of size_t, so the index math never shifts by 64 or more.

Definition at line 116 of file array_list.h.

◆ AL_MAX_ELEMENTS

#define AL_MAX_ELEMENTS   (((size_t)1 << (AL_SHIFT * (AL_MAX_DEPTH + 1))) + AL_BR)

Maximum number of elements an array list can hold: a full trie at AL_MAX_DEPTH plus a full tail.

Definition at line 121 of file array_list.h.

◆ AL_SHIFT

#define AL_SHIFT   5U

log2(AL_BR)

Definition at line 106 of file array_list.h.

Typedef Documentation

◆ array_list_elem_t

typedef void* array_list_elem_t

We use generic pointers to refer to internal nodes, leaf nodes and even elements.

It makes the calculation cruical to determining what type of data we are looking at, in each node. array_list_elem_t will be pointing to actual user data when the node is a leaf node (depth == 0) and it will be pointing to the next node in the trie if the node is an inner node (depth != 0).

Definition at line 138 of file array_list.h.

◆ array_list_index_t

typedef size_t array_list_index_t

Definition at line 96 of file array_list.h.

◆ array_list_maybe_element_t

typedef struct array_list_maybe_element_t array_list_maybe_element_t

◆ array_list_node_t

typedef struct array_list_node_t array_list_node_t

We have two type of node that both are implemented using the same data structure.

Inner nodes that point to other inner nodes or leaf nodes, and leaf nodes which points to actual elements of the sequence.

The main factor in determining the nature of the node is the depth of the trie. Depth zero, means a leaf node and an inner node otherwise.

◆ array_list_t

typedef struct array_list_t array_list_t

Function Documentation

◆ 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
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
#define PANIC_IF_NULL(ptr)
Definition utils.h:66
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

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

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
void * array_list_elem_t
We use generic pointers to refer to internal nodes, leaf nodes and even elements.
Definition array_list.h:138
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_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
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
#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
#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
#define ALLOC(ctx, T)
Definition context.h:84
@ AL_LIMIT_REACHED
Definition errors.h:90
#define ERR(ctx, err, msg)
Definition errors.h:170
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
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_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: