aboutsummaryrefslogtreecommitdiffstats
path: root/core/module-list.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/module-list.c')
-rw-r--r--core/module-list.c203
1 files changed, 195 insertions, 8 deletions
diff --git a/core/module-list.c b/core/module-list.c
index a798c41..9f576a8 100644
--- a/core/module-list.c
+++ b/core/module-list.c
@@ -86,9 +86,11 @@ struct PureList {
/* 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. */
+ * as bits), each field's cells at its key; HASHED (keyed by numbers that
+ * came out of order), each field's cells at its key's slot in its
+ * members' table (as a DENSE list's at its key). What the arrays cannot answer at
+ * once (a position in a DENSE or HASHED 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 */
@@ -777,7 +779,7 @@ static void take_out(PureList *list, Step *path, unsigned depth, unsigned within
/* ---- An event's list in arrays (docs/design/021) */
-enum { TREE, FLAT, DENSE };
+enum { TREE, FLAT, DENSE, HASHED };
/* 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
@@ -985,7 +987,7 @@ static bool arrays_room(PureList *list, size_t room) {
/* Field f's column bytes wide (wider): false without memory (as it was). */
static bool widen(PureList *list, unsigned f, uint8_t bytes) {
- size_t rows = list->form == DENSE ? list->room : list->count;
+ size_t rows = list->form == DENSE || list->form == HASHED ? list->room : list->count;
void *made = malloc((list->room ? list->room : 1) * bytes);
if (!made) return false;
for (size_t i = 0; i < rows; ++i) cell_out(made, bytes, i, array_cell(list, i, f));
@@ -1094,6 +1096,8 @@ static void let_go_rows(PureList *list) {
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 i = 0; list->form == HASHED && i < list->member_capacity; ++i)
+ for (unsigned t = 0; list->members[i] != VACANT && 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)
@@ -1178,6 +1182,157 @@ static bool make_flat(PureList *list) {
return true;
}
+/* Cell from of a column of cells w bytes wide copied to cell to of another. */
+static inline void cell_copy(void *to_column, size_t to, const void *from_column, size_t from, size_t w) {
+ char *t = (char *)to_column + to * w;
+ const char *f = (const char *)from_column + from * w;
+ switch (w) {
+ case 1: *t = *f; break;
+ case 4: memcpy(t, f, 4); break;
+ case 8: memcpy(t, f, 8); break;
+ default: memcpy(t, f, sizeof(PureSpan));
+ }
+}
+
+/* A HASHED list's slot of key (its cells there), or SIZE_MAX. */
+static size_t hashed_slot(const PureList *list, double key) {
+ uint64_t bits = key_bits(key);
+ size_t mask = list->member_capacity - 1;
+ for (size_t at = member_slot(bits, mask);; at = (at + 1) & mask) {
+ if (list->members[at] == bits) return at;
+ if (list->members[at] == VACANT) return SIZE_MAX;
+ }
+}
+
+/* A HASHED list's table and columns of capacity slots (a power of two
+ * above twice its items), each item's cells at its key's slot: from a FLAT
+ * list's rows, or a HASHED one's slots. False without memory (the list as
+ * it was). */
+static bool hashed_table(PureList *list, size_t capacity) {
+ const Layout *l = &list->layout;
+ uint64_t *table = malloc(capacity * sizeof(*table));
+ void *columns[16] = {0};
+ bool ok = table != NULL;
+ for (unsigned f = 0; ok && f < l->fields; ++f) ok = !list->width[f] || (columns[f] = malloc(capacity * list->width[f])) != NULL;
+ if (!ok) {
+ free(table);
+ for (unsigned f = 0; f < 16; ++f) free(columns[f]);
+ return false;
+ }
+ for (size_t i = 0; i < capacity; ++i) table[i] = VACANT;
+ bool hashed = list->form == HASHED;
+ const double *keys = list->columns[l->key];
+ size_t mask = capacity - 1, rows = hashed ? list->member_capacity : list->count;
+ for (size_t row = 0; row < rows; ++row) {
+ if (hashed && list->members[row] == VACANT) continue;
+ uint64_t bits = hashed ? list->members[row] : key_bits(keys[row]);
+ size_t at = member_slot(bits, mask);
+ while (table[at] != VACANT) at = (at + 1) & mask;
+ table[at] = bits;
+ for (unsigned f = 0; f < l->fields; ++f)
+ if (list->width[f]) cell_copy(columns[f], at, list->columns[f], row, list->width[f]);
+ }
+ arrays_free(list);
+ memcpy(list->columns, columns, sizeof(columns));
+ list->room = capacity;
+ free(list->members);
+ ++list->removes;
+ list->members = table, list->member_capacity = capacity, list->member_count = list->count;
+ list->bitmap = false;
+ return true;
+}
+
+/* A FLAT list whose keys come out of order as HASHED; false when it may
+ * not be (made FLAT twice since it was cleared, or packed) or without
+ * memory: it stays FLAT. */
+static bool make_hashed(PureList *list) {
+ if (list->layout.key < 0 || list->pack || list->flattened >= 2) return false;
+ size_t capacity = 16;
+ while (capacity < 2 * (list->count + 1)) capacity *= 2;
+ if (!hashed_table(list, capacity)) return false;
+ list->form = HASHED;
+ list->full = false, list->keys_seen = 0; /* a DENSE list's keys: counted again when it is one again */
+ ++list->version;
+ return true;
+}
+
+/* Whether a HASHED list's next item would grow its arrays past ARRAYS_LARGE. */
+static bool hashed_large(const PureList *list) {
+ return 2 * (list->count + 1) > list->member_capacity && 2 * list->member_capacity * row_width(&list->layout) > ARRAYS_LARGE;
+}
+
+/* An item (its cells the list's own, its key not there) into a HASHED
+ * list at its key's slot; false without memory (as it was). */
+static bool hashed_add(PureList *list, const PureCell *item) {
+ const Layout *l = &list->layout;
+ if (2 * (list->count + 1) > list->member_capacity && !hashed_table(list, 2 * list->member_capacity)) return false;
+ uint64_t bits = key_bits(item[l->key].num);
+ size_t mask = list->member_capacity - 1, at = member_slot(bits, mask);
+ while (list->members[at] != VACANT) at = (at + 1) & mask;
+ list->members[at] = bits;
+ for (unsigned f = 0; f < l->fields; ++f) array_put(list, at, f, item[f]);
+ ++list->member_count;
+ ++list->count;
+ ++list->version;
+ return true;
+}
+
+typedef struct { uint64_t key; uint32_t slot; } Ordered;
+
+/* A HASHED list as FLAT, its items in key order: its slots sorted by their
+ * keys' bits as unsigned numbers in the keys' order (-0 as +0), eight bits
+ * a pass from the lowest (a pass whose bits every key shares skipped), each
+ * column made again in that order; its members' table stays its keys' set.
+ * False without memory (the list as it was). */
+static bool make_ordered(PureList *list) {
+ const Layout *l = &list->layout;
+ size_t n = list->count, room = n > 16 ? n : 16;
+ Ordered *a = malloc((n ? n : 1) * sizeof(*a)), *b = malloc((n ? n : 1) * sizeof(*b));
+ void *columns[16] = {0};
+ bool ok = a && b;
+ for (unsigned f = 0; ok && f < l->fields; ++f) ok = !list->width[f] || (columns[f] = malloc(room * list->width[f])) != NULL;
+ if (!ok) {
+ free(a), free(b);
+ for (unsigned f = 0; f < 16; ++f) free(columns[f]);
+ return false;
+ }
+ for (size_t slot = 0, i = 0; slot < list->member_capacity; ++slot) {
+ uint64_t bits = list->members[slot];
+ if (bits != VACANT) a[i++] = (Ordered){bits >> 63 ? ~bits : bits | UINT64_C(1) << 63, (uint32_t)slot};
+ }
+ for (unsigned shift = 0; n && shift < 64; shift += 8) {
+ size_t counts[256] = {0};
+ for (size_t i = 0; i < n; ++i) ++counts[a[i].key >> shift & 255];
+ if (counts[a[0].key >> shift & 255] == n) continue;
+ for (size_t d = 0, sum = 0; d < 256; ++d) {
+ size_t c = counts[d];
+ counts[d] = sum, sum += c;
+ }
+ for (size_t i = 0; i < n; ++i) b[counts[a[i].key >> shift & 255]++] = a[i];
+ Ordered *t = a;
+ a = b, b = t;
+ }
+ for (unsigned f = 0; f < l->fields; ++f) {
+ size_t w = list->width[f];
+ for (size_t i = 0; w && i < n; ++i) cell_copy(columns[f], i, list->columns[f], a[i].slot, w);
+ }
+ free(a), free(b);
+ arrays_free(list);
+ memcpy(list->columns, columns, sizeof(columns));
+ list->room = room;
+ list->form = FLAT;
+ list->moved = 0;
+ ++list->flattened;
+ ++list->version;
+ return true;
+}
+
+/* A list read by position that may be HASHED made FLAT: memory for it or
+ * none (the process stops rather than read items out of their order). */
+static void ordered_now(const PureList *list) {
+ if (list->form == HASHED && !make_ordered((PureList *)list)) abort();
+}
+
/* Every node below node freed, its texts not (they are a FLAT list's still). */
static void free_nodes(Node *node) {
if (!node) return;
@@ -1241,6 +1396,7 @@ static bool build_tree(PureList *list, Node **root) {
static bool make_tree(PureList *list) {
if (list->form == TREE) return true;
if (list->form == DENSE && !make_flat(list)) return false;
+ if (list->form == HASHED && !make_ordered(list)) return false;
Node *root;
if (!build_tree(list, &root)) return false;
arrays_free(list);
@@ -1368,6 +1524,7 @@ static bool dense_add(PureList *list, const PureCell *item, double number) {
/* 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 == HASHED) return make_ordered(list);
if (list->form != DENSE) return true;
if (list->flattened >= 2) return make_tree(list);
return make_flat(list);
@@ -1510,10 +1667,10 @@ bool pure_list_full(PureList *list) { return list->form == DENSE && list->count
bool pure_list_suits(PureList *list, unsigned needs, unsigned narrow) {
if (list->form == DENSE && !dense_keys(list)) return false; /* its keys counted: full as it is (the tier reads it, LIST_FULL_AT) */
- if (needs & 1 && list->form != DENSE) return false; /* by key: at its keys */
+ if (needs & 1 && list->form != DENSE && !(list->form == HASHED && needs == 1)) return false; /* by key: at its keys, or its rows (by key alone) */
if (needs & 2 && list->form != FLAT && !(list->form == DENSE && dense_keys(list) && list->full)) return false; /* by position */
if (needs & 4 && !(list->form == DENSE && dense_keys(list) && list->full)) return false; /* a key its position */
- if (list->form != FLAT && list->form != DENSE) return false;
+ if (list->form != FLAT && list->form != DENSE && list->form != HASHED) return false;
for (unsigned f = 0; f < list->layout.fields; ++f) { /* its cells of one byte widened to four: the tier's code reads four */
if (!(narrow >> f & 1)) continue;
if (list->width[f] == 1 && !widen(list, f, 4)) return false;
@@ -1590,6 +1747,12 @@ bool pure_list_lookup(const PureList *list, unsigned key_field, PureCell key, un
*cell = array_cell(list, (size_t)key.num, field);
return true;
}
+ if (list->form == HASHED && (int)key_field == list->layout.key) { /* at its key's slot, by its table */
+ size_t slot = hashed_slot(list, key.num);
+ if (slot == SIZE_MAX) return false;
+ *cell = array_cell(list, slot, 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;
@@ -2018,6 +2181,23 @@ PureResult pure_list_add(PureList *list, const PureCell *cells, PureUndo *undo,
if (list->form != TREE) {
double key = cells[l->key].num;
bool found = false;
+ if (list->form == HASHED) { /* by its table, at its end: no position, unless asked */
+ if (hashed_slot(list, key) != SIZE_MAX && !at) return *exists = true, PURE_OK;
+ if (!at && !hashed_large(list)) {
+ if (list->count == PURE_LIST_ITEMS) return PURE_LIMIT;
+ 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 || !arrays_fit(list, cells) || !hashed_add(list, 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;
+ }
+ if (!positioned(list)) return PURE_ALLOCATION;
+ }
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)) {
@@ -2041,7 +2221,10 @@ PureResult pure_list_add(PureList *list, const PureCell *cells, PureUndo *undo,
if (at) *at = low;
return *exists = true, PURE_OK;
}
- if (!at && n >= FLAT_SMALL && (low < n || !list->flattened) && make_dense(list, key)) /* small keys: DENSE (in order too, 023) */
+ if (!at && (n >= FLAT_SMALL || (low == n && key == (double)n)) && (low < n || !list->flattened) && make_dense(list, key))
+ /* small keys: DENSE (in order too, and from the first when each is the count, 023) */
+ return pure_list_add(list, cells, undo, at, exists);
+ if (!at && n >= FLAT_SMALL && low < n && make_hashed(list)) /* other keys out of order: HASHED (023) */
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) {
@@ -2208,6 +2391,7 @@ 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) {
+ ordered_now(list);
if (list->form != TREE) { /* a DENSE list by position: FLAT from now, or counted along its bits without memory */
if (list->form == DENSE) return array_cell(list, dense_slot(list, at), field); /* at its slot (dense_slot) */
if (list->form == FLAT) return array_cell(list, at, field);
@@ -2251,6 +2435,7 @@ static void open_array_run(const PureList *list, size_t at, PureRun *run) {
bool pure_list_run(const PureList *list, size_t at, PureRun *run) {
if (at >= list->count) return false;
+ ordered_now(list);
if (list->form != TREE) {
{
open_array_run(list, at, run);
@@ -2289,6 +2474,7 @@ 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) {
+ ordered_now(list);
if (list->form != TREE) {
if (run->start + run->count >= list->count) return false;
open_array_run(list, run->start + run->count, run);
@@ -2351,6 +2537,7 @@ size_t pure_list_seek(const PureList *list, unsigned field, PureCell key, bool *
const Layout *l = &list->layout;
*found = false;
if (!list->count) return 0;
+ ordered_now(list);
if (list->form == DENSE && (int)field != l->key) (void)positioned((PureList *)list); /* another field: in order */
if (list->form == DENSE) { /* the keys below key (whole numbers below its ceiling), counted (dense_rank) */
size_t all = list->member_capacity * 64;