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

This is an implementation of bit - partitioned, persistent, immutable sequence For more information, have a look at Ideal hash trees paper. More...

#include <stddef.h>
#include <stdint.h>
#include "serene/rt/errors.h"
Include dependency graph for seq.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  seq_lookup_result_t
struct  seq_node_t
 We have two type of node that both are implemented using the same data structure. More...
struct  seq_t

Macros

#define SEQ_BR   32u
 branching factor (power of two)
#define SEQ_SHIFT   5u
 log2(SEQ_BR)
#define SEQ_MASK   (SEQ_BR - 1u)
#define SEQ_MAX_DEPTH   11
 logical length.

Typedefs

typedef void * seq_elem_t
 We use generic pointers to refer to internal nodes, leaf nodes and even elements.
typedef struct seq_lookup_result_t seq_lookup_result_t
typedef struct seq_node_t seq_node_t
 We have two type of node that both are implemented using the same data structure.
typedef struct seq_t seq_t

Functions

seq_t seq_empty (const srn_context_t *ctx)
 Create an empty seq pinned to ctx.
seq_t seq_push (const seq_t *seq, seq_elem_t x)
seq_lookup_result_t seq_get (const seq_t *seq, size_t n)
 Negative index is not supported.

Detailed Description

This is an implementation of bit - partitioned, persistent, immutable sequence For more information, have a look at Ideal hash trees paper.

TL;DR:

  • Every struct is immutable (from user perspective).
  • Nodes are persistent and immutable.
  • Nodes are heap allocated
  • Heads can be stack allocated
  • We create a new head for any change
  • Heads contain a tail buffer that will be converted to a leaf node when they are full
  • This data structure is NOT effecient for extracting sub sequences
  • This data structure is effecient for tail operations,
  • If you need a subset of the elments in the sequence, create a list view out of them instead of extracting them as a new seq.
  • Deleting operation is O(N)

TODO:

  • Create map and filter function or a generic fold
  • Create a helper function to help creating list view from the seq

Definition in file seq.h.

Macro Definition Documentation

◆ SEQ_BR

#define SEQ_BR   32u

branching factor (power of two)

Definition at line 112 of file seq.h.

◆ SEQ_MASK

#define SEQ_MASK   (SEQ_BR - 1u)

Definition at line 116 of file seq.h.

◆ SEQ_MAX_DEPTH

#define SEQ_MAX_DEPTH   11

logical length.

While techically this implementation will support up to 2^85 elements in each seq, but we will limit it down to (2^64-1)(UINT64_MAX)(on 64bit machines) in order to keep the seq_t as small as possible. 12 is the magic number that we use to limit the depth to. 32^11 = 1,152,921,504,606,846,976 elements

Definition at line 123 of file seq.h.

◆ SEQ_SHIFT

#define SEQ_SHIFT   5u

log2(SEQ_BR)

Definition at line 115 of file seq.h.

Typedef Documentation

◆ seq_elem_t

typedef void* seq_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. seq_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 131 of file seq.h.

◆ seq_lookup_result_t

typedef struct seq_lookup_result_t seq_lookup_result_t

◆ seq_node_t

typedef struct seq_node_t seq_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.

◆ seq_t

typedef struct seq_t seq_t

Function Documentation

◆ seq_empty()

seq_t seq_empty ( const srn_context_t * ctx)
nodiscard

Create an empty seq pinned to ctx.

The pinned context owns every allocation the seq (and all of its future versions) retains, so pick a context that lives at least as long as the seq.

Definition at line 100 of file seq.c.

100 {
101 PANIC_IF_NULL(ctx);
102 seq_t seq;
103 seq.len = 0;
104 seq.tail_len = 0;
105 seq.tail = seq_create_page(ctx);
106 seq.maybe_error = nullptr;
107 seq.root = nullptr;
108 seq.depth = 0;
109 seq.seq_ctx = ctx;
110 return seq;
111}
static seq_elem_t * seq_create_page(const srn_context_t *ctx)
Create a PAGE (just slots for the data).
Definition seq.c:40
Definition seq.h:155
size_t len
logical length.
Definition seq.h:161
seq_node_t * root
NULL means “all data is in tail”
Definition seq.h:167
const srn_context_t * seq_ctx
The context that owns every allocation the seq retains.
Definition seq.h:177
seq_elem_t * tail
small tail array for fast push/pop.
Definition seq.h:170
uint8_t depth
tree depth in levels (0 == leaf level)
Definition seq.h:165
uint16_t tail_len
0..SEQ_BR
Definition seq.h:163
#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:

◆ seq_get()

seq_lookup_result_t seq_get ( const seq_t * seq,
size_t n )
nodiscard

Negative index is not supported.

Definition at line 205 of file seq.c.

205 {
206 const srn_context_t *ctx = seq->seq_ctx;
207 PANIC_IF_NULL(ctx);
208 seq_lookup_result_t result = {.data = nullptr, .maybe_error = nullptr};
209
210 if (n >= seq->len) {
211 // Size the message by asking snprintf first; a fixed buffer sized from
212 // sizeof(size_t) counts bytes, not digits, and overflows on large
213 // indices.
214 int msg_len = snprintf(nullptr, 0, "%zu", n);
215 PANIC_IF(msg_len < 0, "index formatting failed");
216 char *err_msg = srn_allocate(ctx, (size_t)msg_len + 1, alignof(char));
217 (void)snprintf(err_msg, (size_t)msg_len + 1, "%zu", n);
218
219 result.maybe_error = ERR(ctx, INDEX_OUT_OF_BOUND, err_msg);
220 result.data = nullptr;
221 return result;
222 }
223
224 size_t tail_start = seq->len - (size_t)seq->tail_len;
225 if (n >= tail_start) {
226 // Tail lookup
227 size_t off = n - tail_start; // 0 .. tail_len-1
228 result.data = seq->tail[off];
229 return result;
230 }
231
232 SEQ_LOG("Looking up in trie: TL: %d, L: %zu N: %zu", seq->tail_len, seq->len, n);
233 seq_node_t *node = seq->root;
234 for (int16_t d = seq->depth; d >= 0; d--) {
235 uint16_t index = seq_depth_index(d, n);
236 SEQ_LOG("Looking up in trie: D: %d, I: %hu", d, index);
237
238 if (d == 0) {
239 SEQ_LOG("We're at depth zero");
240 result.data = node->children[index];
241 return result;
242 }
243
244 // A read must not create nodes. Every index below the tail has a full
245 // path, so a missing child is corruption, not laziness.
246 node = (seq_node_t *)node->children[index];
247 if (!node) {
248 result.maybe_error = ERR(ctx, CORRUPTED_SEQ, "missing child");
249 result.data = nullptr;
250 return result;
251 }
252 }
253
254 PANIC("It should never happen");
255 // just a dummy return
256 result.data = nullptr;
257 result.maybe_error = ERR(ctx, ABSURD, "");
258 return result;
259}
int n
Definition acutest.h:525
void * srn_allocate(const srn_context_t *ctx, size_t size, size_t alignment)
Definition context.c:73
@ CORRUPTED_SEQ
Definition errors.h:79
@ ABSURD
Definition errors.h:76
@ INDEX_OUT_OF_BOUND
Definition errors.h:78
#define ERR(ctx, err, msg)
Definition errors.h:148
static uint16_t seq_depth_index(const uint8_t depth, const uint64_t index)
Find the array index of the given index, considering the given depth.
Definition seq.c:35
#define SEQ_LOG(FMT,...)
Definition seq.c:30
seq_elem_t data
Definition seq.h:135
We have two type of node that both are implemented using the same data structure.
Definition seq.h:147
seq_elem_t * children
We allocate children to be a buffer of SEQ_BR number of pointers.
Definition seq.h:149
#define PANIC_IF(cond, msg)
Definition utils.h:59
#define PANIC(msg)
Definition utils.h:53
Here is the call graph for this function:
Here is the caller graph for this function:

◆ seq_push()

seq_t seq_push ( const seq_t * seq,
seq_elem_t x )
nodiscard

Definition at line 113 of file seq.c.

113 {
114 const srn_context_t *ctx = seq->seq_ctx;
115 PANIC_IF_NULL(ctx);
116
117 if (seq->len >= SIZE_MAX) {
118 seq_t failed_seq;
119 failed_seq.maybe_error =
120 ERR(ctx, SEQ_LIMIT_REACHED, "Can't fit more elements in this sequence");
121 failed_seq.len = 0;
122 failed_seq.tail_len = 0;
123 failed_seq.depth = 0;
124 failed_seq.root = nullptr;
125 failed_seq.tail = nullptr;
126 failed_seq.seq_ctx = seq->seq_ctx;
127 return failed_seq;
128 }
129
130 if (seq->tail_len < SEQ_BR) {
131 // The tail buffer is shared with every older version of this seq, so
132 // the new version appends into its own copy.
133 seq_t new_seq = *seq;
134 new_seq.maybe_error = nullptr;
135 new_seq.tail = seq_create_page(ctx);
136
137 for (uint16_t i = 0; i < seq->tail_len; i++) {
138 new_seq.tail[i] = seq->tail[i];
139 }
140
141 new_seq.tail[new_seq.tail_len++] = x;
142 new_seq.len++;
143 SEQ_LOG("Tail has space, TL: %d, L: %zu, D: %d", new_seq.tail_len, new_seq.len, new_seq.depth);
144 return new_seq;
145 }
146
147 // elements already in the tree (multiple of SEQ_BR)
148 size_t sz_without_tail = seq->len - (size_t)seq->tail_len;
149 // Build a leaf around the current tail buffer. Sharing the buffer is safe;
150 // it is full, and a push onto any version holding it promotes instead of
151 // writing into it.
152 seq_node_t *leaf = ALLOC(ctx, seq_node_t);
153 leaf->children = seq->tail;
154
155 // Tail buffer is full and we need to to move it (no copy) inside the trie
156 // and allocate a new tail
157 if (seq->root == nullptr) {
158 // This happens only once, at index
159 // (SEQ_BR + 1) and technically we can just
160 seq_t new_seq = *seq;
161 new_seq.maybe_error = nullptr;
162 new_seq.tail_len = 1;
163 new_seq.len++;
164 new_seq.tail = seq_create_page(ctx);
165 new_seq.tail[new_seq.tail_len - 1] = x;
166 new_seq.root = leaf;
167 new_seq.depth = 0;
168 SEQ_LOG("Root is null, TL: %d, L: %zu, D: %d", new_seq.tail_len, new_seq.len, new_seq.depth);
169
170 return new_seq;
171 }
172 // Root is not Null, so we have inner children and we need to find the slot
173 // which we need to move the tail into
174
175 // If the tree is full at current depth, grow a new root
176 if (sz_without_tail >> ((seq->depth + 1) * SEQ_SHIFT)) {
177 // ^^ == floor(sz_without_tail / 2^(5*(d+1)))
178
179 seq_node_t *new_root = seq_new_node(ctx); // internal node
180 new_root->children[0] = seq->root;
181 new_root->children[1] = seq_new_path(ctx, seq->depth, leaf);
182 seq_t new_seq = *seq;
183 new_seq.maybe_error = nullptr;
184 new_seq.root = new_root;
185 new_seq.depth = seq->depth + 1;
186 new_seq.tail = seq_create_page(ctx);
187 new_seq.tail_len = 1;
188 new_seq.len = seq->len + 1;
189 new_seq.tail[0] = x;
190 return new_seq;
191 }
192 // Insert the leaf at the right fringe, cloning the path from the root so
193 // no node shared with older versions is written.
194 seq_t new_seq = *seq;
195 new_seq.maybe_error = nullptr;
196 new_seq.root = seq_push_leaf(ctx, seq->root, seq->depth, (uint64_t)sz_without_tail, leaf);
197 new_seq.tail = seq_create_page(ctx);
198 new_seq.tail_len = 1;
199 new_seq.len = seq->len + 1;
200 new_seq.tail[0] = x;
201 return new_seq;
202}
#define ALLOC(ctx, T)
Definition context.h:84
@ SEQ_LIMIT_REACHED
Definition errors.h:77
static seq_node_t * seq_new_node(const srn_context_t *ctx)
Definition seq.c:49
static seq_node_t * seq_new_path(const srn_context_t *ctx, uint8_t depth, seq_node_t *leaf)
Walk from the root to a possible leaf node and create all the inner and leaf nodes if necessary.
Definition seq.c:58
static seq_node_t * seq_push_leaf(const srn_context_t *ctx, const seq_node_t *node, uint8_t depth, uint64_t index, seq_node_t *leaf)
Insert leaf below node at the position of logical index index, cloning every node along the path.
Definition seq.c:82
#define SEQ_BR
branching factor (power of two)
Definition seq.h:112
#define SEQ_SHIFT
log2(SEQ_BR)
Definition seq.h:115
Here is the call graph for this function:
Here is the caller graph for this function: