aboutsummaryrefslogtreecommitdiffstats
path: root/core/module-list.c
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-05 06:54:21 +0100
committereunhhu <[email protected]>2026-10-05 06:54:21 +0100
commit07791a8b9f9c25e83ef849e1cea431ed25be7553 (patch)
treef6bd1fa52ce5f3dec163a8df5ff43d62372480a0 /core/module-list.c
parentce4be172725d0aca0aa55409d4077bf788374dde (diff)
downloadpure-07791a8b9f9c25e83ef849e1cea431ed25be7553.tar.gz
pure-07791a8b9f9c25e83ef849e1cea431ed25be7553.zip
Keep an event's lists in arrays, flat or at their keys, and read a field by key as one (021)
- pure_list_arrays: an event's list (never shared, journaled or undone) keeps its items in arrays: FLAT in order, or DENSE at their keys when keyed by small whole numbers. A position asked of a DENSE list makes it FLAT; moves past a few times its items, arrays past 64 MiB, or a third change of form make it a tree. Answers are the tree's; the stored bytes stand for its items in the memory budget whatever it keeps them in. - pure_list_add need not say where an item went: a list no index state refers to is not asked. - A LIST_FIND whose index the next ITEM_GET of its list takes at once is joined to it at load (PLAN_JOINED): pure_list_lookup reads the field by key, at the key in a DENSE list. - test-module arrays_equivalence: beside a tree, every step of a random run (adds, inserts, removes, changes, finds, reads, runs, clears) gives the same answers through every form and change of form. - A search (100,000 nodes, 1,000,000 edges, core only): 187 to 126 ms.
Diffstat (limited to 'core/module-list.c')
-rw-r--r--core/module-list.c452
1 files changed, 444 insertions, 8 deletions
diff --git a/core/module-list.c b/core/module-list.c
index ea15fb1..0d53c60 100644
--- a/core/module-list.c
+++ b/core/module-list.c
@@ -81,6 +81,18 @@ struct PureList {
uint64_t *members;
size_t member_capacity, member_count;
bool bitmap;
+ /* An event's list may keep its items in arrays rather than a tree
+ * (pure_list_arrays, docs/design/021): FLAT, each field's cells side by
+ * side in the list's order; DENSE (keyed by small whole numbers, its keys
+ * as bits), each field's cells at its key. What the arrays cannot answer
+ * at once (a position in a DENSE list; a change logged to be undone, a
+ * record, a share) makes them FLAT, or a tree, first. */
+ uint8_t form;
+ bool arrays; /* it may be FLAT or DENSE: an event's list */
+ uint8_t flattened; /* DENSE made FLAT since it was cleared: twice, and it stays FLAT or a tree */
+ void *columns[16]; /* each field's cells */
+ size_t room; /* the rows the columns hold: FLAT items, DENSE keys */
+ size_t moved; /* FLAT: items moved by changes inside it since it was cleared */
};
/* The log of an event's changes: a removed item's cells wait in the log's
@@ -738,6 +750,249 @@ static void take_out(PureList *list, Step *path, unsigned depth, unsigned within
++list->version;
}
+/* ---- An event's list in arrays (docs/design/021) */
+
+enum { TREE, FLAT, DENSE };
+/* The arrays a clear keeps for the next event; the most they grow to (a
+ * larger list is a tree: doubling arrays would hold twice its items); a
+ * DENSE list's slack beyond four times its items (so it never holds far more
+ * than a tree would); the items a FLAT list moves freely. */
+enum { ARRAYS_KEPT = 1 << 22, ARRAYS_LARGE = 1 << 26, DENSE_SLACK = 1 << 21, FLAT_SMALL = 64 };
+
+static size_t width(const Layout *l, unsigned f) { return pure_cell_text(l->kinds[f]) ? sizeof(PureSpan) : 8; }
+
+static size_t row_width(const Layout *l) {
+ size_t row = 0;
+ for (unsigned f = 0; f < l->fields; ++f) row += width(l, f);
+ return row ? row : 1;
+}
+
+static PureCell array_cell(const PureList *list, size_t i, unsigned f) {
+ PureCell cell = {0};
+ if (!pure_cell_text(list->layout.kinds[f])) memcpy(&cell.i64, (const char *)list->columns[f] + 8 * i, 8);
+ else {
+ PureSpan text = ((const PureSpan *)list->columns[f])[i];
+ cell.text = (unsigned char *)text.data, cell.size = text.size;
+ }
+ return cell;
+}
+
+static void array_put(PureList *list, size_t i, unsigned f, PureCell cell) {
+ if (pure_cell_text(list->layout.kinds[f])) ((PureSpan *)list->columns[f])[i] = (PureSpan){cell.text, cell.size};
+ else memcpy((char *)list->columns[f] + 8 * i, &cell.i64, 8);
+}
+
+/* Row from's cells to row to, of the same columns or of others. */
+static void array_copy(const Layout *l, void *const *to_columns, size_t to, void *const *from_columns, size_t from) {
+ for (unsigned f = 0; f < l->fields; ++f)
+ memcpy((char *)to_columns[f] + to * width(l, f), (const char *)from_columns[f] + from * width(l, f), width(l, f));
+}
+
+static void array_move(PureList *list, size_t to, size_t from, size_t count) {
+ const Layout *l = &list->layout;
+ for (unsigned f = 0; f < l->fields; ++f)
+ memmove((char *)list->columns[f] + to * width(l, f), (const char *)list->columns[f] + from * width(l, f),
+ count * width(l, f));
+}
+
+/* Columns of room rows each; false without memory (each as it was or grown). */
+static bool arrays_room(PureList *list, size_t room) {
+ const Layout *l = &list->layout;
+ for (unsigned f = 0; f < l->fields; ++f) {
+ void *grown = realloc(list->columns[f], room * width(l, f));
+ if (!grown) return false;
+ list->columns[f] = grown;
+ }
+ list->room = room;
+ return true;
+}
+
+static void arrays_free(PureList *list) {
+ for (unsigned f = 0; f < 16; ++f) free(list->columns[f]), list->columns[f] = NULL;
+ list->room = 0;
+}
+
+/* A DENSE list's i-th key's slot, counted along its bits (no memory needed). */
+static size_t dense_slot(const PureList *list, size_t i) {
+ for (size_t w = 0; w < list->member_capacity; ++w) {
+ size_t bits = (size_t)__builtin_popcountll(list->members[w]);
+ if (i < bits) {
+ uint64_t word = list->members[w];
+ for (; i; --i) word &= word - 1;
+ return w * 64 + (size_t)__builtin_ctzll(word);
+ }
+ i -= bits;
+ }
+ return SIZE_MAX;
+}
+
+/* The texts of every item of a FLAT or DENSE list let go. */
+static void let_go_rows(PureList *list) {
+ const Layout *l = &list->layout;
+ if (!l->texts) return;
+ for (size_t i = 0; list->form == FLAT && i < list->count; ++i)
+ for (unsigned t = 0; t < l->texts; ++t) free_text(array_cell(list, i, l->text_field[t]).text);
+ for (size_t w = 0; list->form == DENSE && w < list->member_capacity; ++w)
+ for (uint64_t word = list->members[w]; word; word &= word - 1)
+ for (unsigned t = 0; t < l->texts; ++t)
+ free_text(array_cell(list, w * 64 + (size_t)__builtin_ctzll(word), l->text_field[t]).text);
+}
+
+/* The keys a DENSE list may hold: below four times its items and a slack. */
+static size_t dense_limit(const PureList *list) {
+ size_t row = row_width(&list->layout), limit = 4 * (list->count + 1) + DENSE_SLACK / row;
+ return limit < ARRAYS_LARGE / row ? limit : ARRAYS_LARGE / row;
+}
+
+/* Whether a full FLAT list would grow its arrays past ARRAYS_LARGE. */
+static bool flat_large(const PureList *list) {
+ return list->count == list->room && 2 * list->room * row_width(&list->layout) > ARRAYS_LARGE;
+}
+
+static bool dense_holds(const PureList *list, double key) { return small_key(key) && (size_t)key < dense_limit(list); }
+
+/* A FLAT list keyed by small whole numbers (its keys as bits) as DENSE,
+ * room for key too; false when it cannot be (it stays FLAT). */
+static bool make_dense(PureList *list, double key) {
+ const Layout *l = &list->layout;
+ if (l->key < 0 || !list->members || !list->bitmap || list->flattened >= 2 || !dense_holds(list, key)) return false;
+ const double *keys = list->columns[l->key];
+ size_t top = (size_t)key;
+ if (list->count) {
+ double last = keys[list->count - 1]; /* the greatest: FLAT keeps key order */
+ if (!dense_holds(list, last)) return false;
+ if ((size_t)last > top) top = (size_t)last;
+ }
+ size_t room = top + 1 + (top + 1) / 2, limit = dense_limit(list);
+ if (room > limit) room = limit;
+ if (room <= top) return false;
+ void *columns[16] = {0};
+ for (unsigned f = 0; f < l->fields; ++f)
+ if (!(columns[f] = malloc(room * width(l, f)))) {
+ for (unsigned g = 0; g < f; ++g) free(columns[g]);
+ return false;
+ }
+ for (size_t i = 0; i < list->count; ++i) array_copy(l, columns, (size_t)keys[i], list->columns, i);
+ arrays_free(list);
+ memcpy(list->columns, columns, sizeof(columns));
+ list->room = room;
+ list->form = DENSE;
+ return true;
+}
+
+/* A DENSE list as FLAT, its items in key order; false without memory. */
+static bool make_flat(PureList *list) {
+ const Layout *l = &list->layout;
+ size_t room = list->count > 16 ? list->count : 16, row = 0;
+ void *columns[16] = {0};
+ for (unsigned f = 0; f < l->fields; ++f)
+ if (!(columns[f] = malloc(room * width(l, f)))) {
+ for (unsigned g = 0; g < f; ++g) free(columns[g]);
+ return false;
+ }
+ for (size_t w = 0; w < list->member_capacity; ++w)
+ for (uint64_t word = list->members[w]; word; word &= word - 1)
+ array_copy(l, columns, row++, list->columns, w * 64 + (size_t)__builtin_ctzll(word));
+ arrays_free(list);
+ memcpy(list->columns, columns, sizeof(columns));
+ list->room = room;
+ list->form = FLAT;
+ list->moved = 0;
+ ++list->flattened;
+ return true;
+}
+
+/* Every node below node freed, its texts not (they are a FLAT list's still). */
+static void free_nodes(Node *node) {
+ if (!node) return;
+ for (unsigned i = 0; !node->leaf && i < node->count; ++i) free_nodes(child(node, i));
+ free(node);
+}
+
+/* A FLAT or DENSE list as a tree, its items placed in order (a tree that
+ * grows at its end fills its leaves); false without memory (it is as it was). */
+static bool make_tree(PureList *list) {
+ if (list->form == TREE) return true;
+ if (list->form == DENSE && !make_flat(list)) return false;
+ size_t count = list->count;
+ list->form = TREE;
+ list->count = 0;
+ for (size_t i = 0; i < count; ++i) {
+ PureCell cells[16];
+ for (unsigned f = 0; f < list->layout.fields; ++f) cells[f] = array_cell(list, i, f);
+ Place p;
+ if (!prepare(list, list->count, &p)) {
+ free_nodes(list->root);
+ list->root = NULL;
+ list->count = count;
+ list->form = FLAT;
+ return false;
+ }
+ place(list, &p, cells);
+ }
+ arrays_free(list);
+ return true;
+}
+
+/* An item (its cells the list's own) into a FLAT list before at. */
+static bool flat_insert(PureList *list, size_t at, const PureCell *item) {
+ if (list->count == list->room && !arrays_room(list, list->room ? 2 * list->room : 16)) return false;
+ if (at < list->count) {
+ array_move(list, at + 1, at, list->count - at);
+ list->moved += list->count - at;
+ }
+ for (unsigned f = 0; f < list->layout.fields; ++f) array_put(list, at, f, item[f]);
+ if (list->members) member_add(list, item[list->layout.key].num);
+ ++list->count;
+ ++list->version;
+ return true;
+}
+
+/* Whether a FLAT list may move the items after at (else it becomes a tree):
+ * a few always, more while the moves stay within a few times its items. */
+static bool flat_moves(const PureList *list, size_t at) {
+ size_t after = list->count - at;
+ return list->count < FLAT_SMALL || list->moved + after <= 8 * list->count + 4096;
+}
+
+/* An item (its cells the list's own) into a DENSE list at its key (one it
+ * may hold); false without memory (the list as it was). */
+static bool dense_add(PureList *list, const PureCell *item, double number) {
+ size_t key = (size_t)number;
+ if (key >= list->room) {
+ size_t room = list->room + list->room / 2, limit = dense_limit(list);
+ if (room <= key) room = key + 1 + (key + 1) / 2;
+ if (room > limit) room = limit;
+ if (!arrays_room(list, room)) return false;
+ }
+ if (key / 64 >= list->member_capacity) { /* the bits grown first: adding a key must not give them up */
+ size_t capacity = list->member_capacity ? list->member_capacity : 1;
+ while (capacity <= key / 64) capacity *= 2;
+ uint64_t *grown = realloc(list->members, capacity * sizeof(*grown));
+ if (!grown) return false;
+ memset(grown + list->member_capacity, 0, (capacity - list->member_capacity) * sizeof(*grown));
+ list->members = grown, list->member_capacity = capacity;
+ }
+ for (unsigned f = 0; f < list->layout.fields; ++f) array_put(list, key, f, item[f]);
+ member_add(list, number);
+ ++list->count;
+ ++list->version;
+ return true;
+}
+
+/* The list as a form that answers by position (FLAT or a tree): a DENSE
+ * one made FLAT, a tree when it was made FLAT too often; false without memory. */
+static bool positioned(PureList *list) {
+ if (list->form != DENSE) return true;
+ if (list->flattened >= 2) return make_tree(list);
+ return make_flat(list);
+}
+
+/* The list as a tree, for a change logged to be undone or recorded. */
+static bool logged(PureList *list, const PureUndo *undo) {
+ return list->form == TREE || (!undo && !list->journaled) || make_tree(list);
+}
+
/* ---- The list */
PureList *pure_list_new(unsigned fields, const uint8_t *kinds) {
@@ -774,6 +1029,7 @@ PureResult pure_list_keyed(PureList *list, int field) {
Layout *l = &list->layout;
int key = field >= 0 && (unsigned)field < l->fields && l->kinds[field] == PURE_CELL_NUMBER ? field : -1;
if (key == l->key) return PURE_OK;
+ if (list->count && !make_tree(list)) return PURE_ALLOCATION;
l->key = key;
if (list->root && !rekey(list, &list->root)) {
l->key = -1; /* searched without leads, which some nodes lack */
@@ -799,6 +1055,8 @@ void pure_list_free(PureList *list) {
if (list->copy) {
if (__atomic_sub_fetch(&list->refs, 1, __ATOMIC_ACQ_REL)) return;
} else {
+ if (list->form != TREE) let_go_rows(list);
+ arrays_free(list);
free(list->journal);
free(list->members);
let_go_tree(list, list->root);
@@ -811,11 +1069,21 @@ void pure_list_free(PureList *list) {
}
void pure_list_clear(PureList *list) {
- if (!list->count) return;
- let_go_tree(list, list->root);
- list->root = NULL;
- list->count = list->bytes = 0;
- ++list->version;
+ bool had = list->count != 0;
+ if (had) {
+ if (list->form != TREE) let_go_rows(list);
+ let_go_tree(list, list->root);
+ list->root = NULL;
+ list->count = list->bytes = 0;
+ ++list->version;
+ }
+ if (list->arrays) { /* FLAT again, its arrays kept for the next event unless large */
+ list->form = FLAT;
+ list->flattened = 0;
+ list->moved = 0;
+ if (list->room * row_width(&list->layout) > ARRAYS_KEPT) arrays_free(list);
+ }
+ if (!had) return;
if (!list->members) return;
if (list->bitmap && list->member_capacity <= BITMAP_KEPT) { /* small bits are kept for the next event */
memset(list->members, 0, list->member_capacity * sizeof(*list->members));
@@ -849,11 +1117,37 @@ bool pure_list_contains(const PureList *list, double key, bool *found) {
return true;
}
+/* An event's arrays are its items, as a tree's nodes are: neither counts
+ * here (the stored bytes stand for both), so a list's room runs out at the
+ * same item whatever it keeps them in (021). */
size_t pure_list_extra(const PureList *list) { return list->member_capacity * sizeof(*list->members); }
+bool pure_list_arrays(PureList *list) {
+ if (list->form != TREE || list->arrays) return true;
+ if (list->copy || list->journaled || list->count) return false;
+ list->arrays = true;
+ list->form = FLAT;
+ return true;
+}
+
+unsigned pure_list_form(const PureList *list) { return list->form; }
+
+bool pure_list_lookup(const PureList *list, unsigned key_field, PureCell key, unsigned field, PureCell *cell) {
+ bool found;
+ if (list->form == DENSE && (int)key_field == list->layout.key) { /* at its key, by its bits */
+ if (!pure_list_contains(list, key.num, &found) || !found) return false;
+ *cell = array_cell(list, (size_t)key.num, field);
+ return true;
+ }
+ size_t at = pure_list_seek(list, key_field, key, &found);
+ if (found) *cell = pure_list_get(list, at, field);
+ return found;
+}
+
size_t pure_list_count(const PureList *list) { return list->count; }
PureList *pure_list_share(PureList *list, PureRetired *retired, uint64_t epoch) {
+ if (!make_tree(list)) return NULL; /* a copy reads a tree */
PureList *copy = malloc(sizeof(*copy));
if (!copy) return NULL;
*copy = (PureList){.layout = list->layout, .root = list->root, .count = list->count, .bytes = list->bytes,
@@ -1063,6 +1357,8 @@ static unsigned char *record_head(unsigned char *out, unsigned kind, size_t at)
}
void pure_list_journal(PureList *list, bool journaled) {
+ if (journaled && list->form != TREE && !make_tree(list)) abort(); /* an event's list is never journaled */
+ if (journaled) list->arrays = false;
list->journaled = journaled;
if (!journaled) pure_list_forget(list, 0);
}
@@ -1099,12 +1395,20 @@ static bool encode_leaf(void *context, const Node *leaf) {
size_t pure_list_size(const PureList *list) { return list->bytes; }
+/* An event's list is never stored, shared or sorted: one that was is made a
+ * tree first (memory for it or none: the process stops rather than lose items). */
+static void tree_now(const PureList *list) {
+ if (list->form != TREE && !make_tree((PureList *)list)) abort();
+}
+
void pure_list_encode(const PureList *list, unsigned char *out) {
+ tree_now(list);
Encoding encoding = {&list->layout, out};
visit_leaves(list->root, encode_leaf, &encoding);
}
size_t pure_list_encode_part(const PureList *list, size_t *at, unsigned char *out, size_t capacity, size_t *needed) {
+ tree_now(list);
const Layout *l = &list->layout;
PureRun run;
size_t used = 0;
@@ -1135,6 +1439,7 @@ static bool decode_item(const Layout *l, const unsigned char *bytes, size_t size
}
PURE_COLD PureResult pure_list_read(PureList *list, const unsigned char *bytes, size_t size) {
+ if (!make_tree(list)) return PURE_ALLOCATION;
PureCell cells[16];
for (size_t at = 0; at < size;) {
if (!decode_item(&list->layout, bytes, size, &at, cells)) return PURE_STORE_ERROR;
@@ -1151,6 +1456,7 @@ static bool record_cell(const Layout *l, bool compact, unsigned field, const uns
}
PURE_COLD PureResult pure_list_replay(PureList *list, const unsigned char *bytes, size_t size) {
+ if (!make_tree(list)) return PURE_ALLOCATION;
const Layout *l = &list->layout;
bool journaled = list->journaled;
PureResult result = PURE_OK;
@@ -1188,10 +1494,21 @@ PURE_COLD PureResult pure_list_replay(PureList *list, const unsigned char *bytes
PureResult pure_list_insert(PureList *list, size_t at, const PureCell *cells, PureUndo *undo) {
const Layout *l = &list->layout;
if (at > list->count || list->count == PURE_LIST_ITEMS) return PURE_LIMIT;
+ if (!logged(list, undo) || !positioned(list) ||
+ (list->form == FLAT && ((at < list->count && !flat_moves(list, at)) || flat_large(list)) && !make_tree(list)))
+ return PURE_ALLOCATION;
PureCell item[16];
unsigned owned = 0;
for (; owned < l->fields; ++owned)
if (!own_cell(list, owned, cells[owned], &item[owned])) break;
+ if (list->form == FLAT) {
+ if (owned < l->fields || !flat_insert(list, at, item)) {
+ for (unsigned f = 0; f < owned; ++f) free_text(item[f].text);
+ return PURE_ALLOCATION;
+ }
+ list->bytes += cells_bytes(l, item);
+ return PURE_OK;
+ }
size_t size = RECORD_HEAD;
for (unsigned f = 0; list->journaled && f < l->fields; ++f) size += compact_bound(l, f, cells[f]);
unsigned char *record = list->journaled ? journal_room(list, size) : NULL;
@@ -1219,16 +1536,54 @@ PureResult pure_list_add(PureList *list, const PureCell *cells, PureUndo *undo,
const Layout *l = &list->layout;
*exists = false;
if (l->key < 0) return PURE_INVALID; /* searched and inserted apart, then */
+ if (!logged(list, undo)) return PURE_ALLOCATION;
+ if (list->form != TREE) {
+ double key = cells[l->key].num;
+ bool found = false;
+ if (list->form == DENSE) { /* by its bits, at its key: no position, unless asked */
+ if (pure_list_contains(list, key, &found) && found && !at) return *exists = true, PURE_OK;
+ if (!at && !found && dense_holds(list, key)) {
+ PureCell item[16];
+ unsigned owned = 0;
+ for (; owned < l->fields; ++owned)
+ if (!own_cell(list, owned, cells[owned], &item[owned])) break;
+ if (owned < l->fields || !dense_add(list, item, key)) {
+ for (unsigned f = 0; f < owned; ++f) free_text(item[f].text);
+ return PURE_ALLOCATION;
+ }
+ list->bytes += cells_bytes(l, item);
+ return PURE_OK;
+ }
+ if (!positioned(list)) return PURE_ALLOCATION;
+ }
+ if (list->form == FLAT) {
+ const double *keys = list->columns[l->key];
+ size_t n = list->count, low = n ? lower(keys, (unsigned)n, key) : 0;
+ if (low < n && keys[low] == key) {
+ if (at) *at = low;
+ return *exists = true, PURE_OK;
+ }
+ if (low < n && !at && n >= FLAT_SMALL && make_dense(list, key)) return pure_list_add(list, cells, undo, at, exists);
+ if (low < n && !flat_moves(list, low) && !make_tree(list)) return PURE_ALLOCATION;
+ if (list->form == FLAT) {
+ if (at) *at = low;
+ return pure_list_insert(list, low, cells, undo);
+ }
+ }
+ }
if (list->members) { /* the set says at once when the key is there */
bool found;
if (pure_list_contains(list, cells[l->key].num, &found) && found) {
- *at = pure_list_seek(list, (unsigned)l->key, cells[l->key], exists);
+ size_t where = pure_list_seek(list, (unsigned)l->key, cells[l->key], exists);
+ if (at) *at = where;
return PURE_OK;
}
}
if (list->count == PURE_LIST_ITEMS) return PURE_LIMIT;
if (!list->root && !(list->root = new_node(list, true))) return PURE_ALLOCATION;
Place p = {.extras = 0, .used = 0};
+ size_t where;
+ if (!at) at = &where;
if (!descend_key(list, cells[l->key].num, &p, at, exists)) return PURE_ALLOCATION;
if (*exists) return PURE_OK;
p.appending = *at == list->count;
@@ -1274,6 +1629,21 @@ static PureCell *log_cells(PureUndo *undo, unsigned fields) {
PureResult pure_list_remove(PureList *list, size_t at, PureUndo *undo) {
const Layout *l = &list->layout;
if (at >= list->count) return PURE_NOT_FOUND;
+ if (!logged(list, undo) || !positioned(list) ||
+ (list->form == FLAT && at + 1 < list->count && !flat_moves(list, at + 1) && !make_tree(list)))
+ return PURE_ALLOCATION;
+ if (list->form == FLAT) {
+ PureCell cells[16];
+ for (unsigned f = 0; f < l->fields; ++f) cells[f] = array_cell(list, at, f);
+ if (list->members) member_remove(list, cells[l->key].num);
+ list->bytes -= cells_bytes(l, cells);
+ let_go_cells(list, cells);
+ array_move(list, at, at + 1, list->count - at - 1);
+ list->moved += list->count - at - 1;
+ --list->count;
+ ++list->version;
+ return PURE_OK;
+ }
Step path[DEPTH];
unsigned within, depth = descend(list, at, path, &within);
if (!depth) return PURE_ALLOCATION;
@@ -1300,6 +1670,17 @@ PureResult pure_list_remove(PureList *list, size_t at, PureUndo *undo) {
PureResult pure_list_set(PureList *list, size_t at, unsigned field, PureCell value, PureUndo *undo) {
const Layout *l = &list->layout;
if (at >= list->count) return PURE_NOT_FOUND;
+ if (!logged(list, undo) || !positioned(list)) return PURE_ALLOCATION;
+ if (list->form == FLAT) {
+ PureCell copy, old = array_cell(list, at, field);
+ if (!own_cell(list, field, value, &copy)) return PURE_ALLOCATION;
+ list->bytes += cell_bytes(l, field, copy) - cell_bytes(l, field, old);
+ array_put(list, at, field, copy);
+ let_go_text(list, old.text);
+ if ((int)field == l->key && list->members) member_remove(list, old.num), member_add(list, copy.num);
+ ++list->version;
+ return PURE_OK;
+ }
Step path[DEPTH];
unsigned within, depth = 0;
bool keyed = (int)field == l->key;
@@ -1343,6 +1724,10 @@ PureResult pure_list_set(PureList *list, size_t at, unsigned field, PureCell val
}
PureCell pure_list_get(const PureList *list, size_t at, unsigned field) {
+ if (list->form != TREE) { /* a DENSE list by position: FLAT from now, or counted along its bits without memory */
+ if (list->form == DENSE && !positioned((PureList *)list)) return array_cell(list, dense_slot(list, at), field);
+ if (list->form == FLAT) return array_cell(list, at, field);
+ }
size_t within;
const Node *leaf = leaf_of(list, at, &within); /* first: an argument beside it would be read in either order */
return cell_at(&list->layout, leaf, (unsigned)within, field);
@@ -1356,8 +1741,29 @@ static void open_run(const Layout *l, const Node *leaf, PureRun *run) {
else run->column[f].numbers = numbers(l, leaf, f);
}
+/* A FLAT list's run from at: up to PURE_LIST_RUN items of its arrays; a
+ * DENSE one's (made FLAT without memory) its item at alone. */
+static void open_array_run(const PureList *list, size_t at, PureRun *run) {
+ const Layout *l = &list->layout;
+ size_t row = list->form == DENSE ? dense_slot(list, at) : at;
+ run->start = at;
+ run->count = list->form == DENSE ? 1 : (unsigned)(list->count - at < PURE_LIST_RUN ? list->count - at : PURE_LIST_RUN);
+ run->depth = 0;
+ for (unsigned f = 0; f < l->fields; ++f)
+ if (pure_cell_text(l->kinds[f])) run->column[f].texts = (const PureSpan *)list->columns[f] + row;
+ else if (l->kinds[f] == PURE_CELL_INTEGER) run->column[f].integers = (const int64_t *)list->columns[f] + row;
+ else run->column[f].numbers = (const double *)list->columns[f] + row;
+}
+
bool pure_list_run(const PureList *list, size_t at, PureRun *run) {
if (at >= list->count) return false;
+ if (list->form != TREE) {
+ if (list->form == DENSE) (void)positioned((PureList *)list);
+ if (list->form != TREE) {
+ open_array_run(list, at, run);
+ return true;
+ }
+ }
const Node *node = list->root;
size_t start = 0;
unsigned depth = 0;
@@ -1379,6 +1785,11 @@ bool pure_list_run(const PureList *list, size_t at, PureRun *run) {
* first children to a leaf. The leaf after it (a sibling) is fetched ahead:
* leaves copied at different times lie far apart in memory. */
bool pure_list_next(const PureList *list, PureRun *run) {
+ if (list->form != TREE) {
+ if (run->start + run->count >= list->count) return false;
+ open_array_run(list, run->start + run->count, run);
+ return true;
+ }
size_t start = run->start + run->count;
unsigned depth = run->depth;
while (depth && run->taken[depth - 1] + 1u >= ((const Node *)run->path[depth - 1])->count) --depth;
@@ -1417,10 +1828,34 @@ static int order(const Layout *l, unsigned field, PureCell a, PureCell b) {
* the children's first leaves. */
size_t pure_list_seek(const PureList *list, unsigned field, PureCell key, bool *found) {
const Layout *l = &list->layout;
+ *found = false;
+ if (!list->count) return 0;
+ if (list->form == DENSE && !positioned((PureList *)list) && (int)field == l->key) { /* counted along its bits */
+ size_t below = 0, all = list->member_capacity * 64; /* the keys below key: whole numbers below its ceiling */
+ size_t limit = key.num <= 0 ? 0 : key.num >= (double)all ? all : (size_t)ceil(key.num);
+ for (size_t w = 0; w < list->member_capacity && w * 64 < limit; ++w) {
+ uint64_t word = list->members[w];
+ if ((w + 1) * 64 > limit) word &= (UINT64_C(1) << (limit - w * 64)) - 1;
+ below += (size_t)__builtin_popcountll(word);
+ }
+ pure_list_contains(list, key.num, found);
+ return below;
+ }
+ if (list->form == FLAT) {
+ size_t low = 0, high = list->count;
+ if ((int)field == l->key) low = lower(list->columns[field], (unsigned)list->count, key.num);
+ else
+ while (low < high) {
+ size_t middle = low + (high - low) / 2;
+ if (order(l, field, array_cell(list, middle, field), key) < 0) low = middle + 1;
+ else high = middle;
+ }
+ *found = low < list->count && !order(l, field, array_cell(list, low, field), key);
+ return low;
+ }
const Node *node = list->root;
size_t start = 0;
- *found = false;
- if (!node || !list->count) return 0;
+ if (!node) return 0;
if ((int)field == l->key) {
/* The finger's leaf, when its keys reach round key, holds its place
* (a slice's high bound is near its low one, found just before). */
@@ -1497,6 +1932,7 @@ static bool gather_leaf(void *context, const Node *leaf) {
}
PURE_COLD PureResult pure_list_sorted(const PureList *list, unsigned field, PureList **out, size_t *moved) {
+ tree_now(list);
const Layout *l = &list->layout;
*out = NULL;
Gathered gathered = {list->count ? malloc(list->count * sizeof(Ref)) : NULL, 0};