diff options
Diffstat (limited to 'core')
| -rw-r--r-- | core/module-list.c | 574 | ||||
| -rw-r--r-- | core/module-list.h | 34 |
2 files changed, 369 insertions, 239 deletions
diff --git a/core/module-list.c b/core/module-list.c index cfa9cf9..6e13de0 100644 --- a/core/module-list.c +++ b/core/module-list.c @@ -6,53 +6,75 @@ /* A list's items live in a persistent B+tree: leaves hold up to FANOUT * items, inner nodes up to FANOUT children, and every node knows how many - * items lie below it, their stored bytes and the first of them. So an item - * is found by position, a keyed list is searched by key and an item's place - * in the stored form is computed in O(log n), whatever the list's length; a - * run of positions is read through a finger (the last leaf a thread read). + * items lie below it, their stored bytes and the leaf holding the first of + * them. So an item is found by position, a keyed list is searched by key and + * an item's place in the stored form is computed in O(log n), whatever the + * list's length; a run of positions is read through a finger (the last leaf + * a thread read) or a leaf at a time (pure_list_run). * - * Nodes, like items, carry the list's share count when they were made. One - * made before the last share may be read by a shared copy (a snapshot), so - * it never changes again: the list copies the nodes on the way to a change - * (the path) and retires the ones it replaced, as it does items. A share is - * then a root and a count, however long the list. */ + * A leaf keeps its items field by field: each field's cells side by side, + * numbers as binary64 values and texts as spans of bytes the list owns. So + * reading one field of items after one another reads memory in order, an + * item takes no allocation of its own and a leaf is copied in one piece. + * + * Nodes carry the list's share count when they were made, and so do texts. + * One made before the last share may be read by a shared copy (a snapshot), + * so it never changes again: the list copies the nodes on the way to a change + * (the path) and retires the ones it replaced, and retires a text it lets go + * instead of freeing it. A share is then a root and a count, however long + * the list. */ enum { FANOUT = 32, DEPTH = 16, SPARE_NODES = 8 }; typedef struct Node { - uint64_t made; /* the list's share count when made: shared once below it */ - uint32_t count; /* items (a leaf) or children */ + uint64_t made; /* the list's share count when made: shared once below it */ + uint32_t count; /* items (a leaf) or children */ bool leaf; - size_t items, bytes; /* items below, and their stored bytes */ - PureCell *first; /* the first item below; NULL when there is none */ - void *slot[FANOUT]; /* a leaf's items (PureCell *), an inner node's children (Node *) */ + size_t items, bytes; /* items below, and their stored bytes */ + const struct Node *first; /* the leaf holding the first item below; NULL when there is none */ + struct Node *child[]; /* an inner node's children; a leaf's columns are here instead */ } Node; -struct PureList { - unsigned fields; +enum { INNER_SIZE = sizeof(Node) + FANOUT * sizeof(Node *) }; + +/* Where a leaf keeps each field's cells, and what an item stores. */ +typedef struct { + unsigned fields, texts; uint8_t text_fields[16]; - Node *root; /* NULL when no item was ever placed */ + uint8_t text_field[16]; /* the text fields, in order */ + uint32_t column[16]; /* where each field's cells start in a leaf */ + size_t leaf_size; + size_t number_bytes; /* an item's numbers, stored */ +} Layout; + +struct PureList { + Layout layout; + Node *root; /* NULL when the list has no item */ size_t count; PureDraft *draft; /* the stored copy, or NULL */ size_t header; /* draft bytes before the first item */ uint64_t id, version; /* which list, and a number every change moves on */ - uint64_t shares; /* copies made so far: an item or node made before the last one is shared */ - PureRetired *retired; /* where shared items and nodes go when the list lets them go */ - Node *spare; /* nodes let go that no copy read, kept for the next ones (linked by slot[0]) */ - unsigned spares; + uint64_t shares; /* copies made so far: a node or text made before the last one is shared */ + PureRetired *retired; /* where shared nodes and texts go when the list lets them go */ + Node *spare[2]; /* inner nodes and leaves let go that no copy read, kept for the next ones (linked by first) */ + unsigned spares[2]; unsigned refs; /* a shared copy's references */ - bool copy; /* a shared copy: it owns none of its nodes or items */ + bool copy; /* a shared copy: it owns none of its nodes or texts */ }; -typedef enum { INSERTED, REMOVED, CHANGED, REPLACED } Change; +/* The log of an event's changes: a removed item's cells wait in the log's + * cells (its texts owned by the log) until the event is undone or kept. */ +typedef enum { INSERTED, REMOVED, CHANGED } Change; typedef struct { Change change; + unsigned field; PureList *list; size_t at; - unsigned field; - PureCell *item; /* REMOVED: the removed item; REPLACED: the shared item a copy replaced */ - PureCell old; /* CHANGED: the replaced cell */ + union { + PureCell old; /* CHANGED: the replaced cell */ + size_t cells; /* REMOVED: where the item's cells start in the log's cells */ + }; } Entry; -typedef struct { void *pointer; uint16_t text; uint8_t fields; bool node; } Retired; +typedef struct { void *pointer; bool text; } Retired; static uint64_t lists_made; @@ -61,39 +83,51 @@ static uint64_t lists_made; * the list (its id) as it was (its version) and no other. */ static _Thread_local struct { uint64_t id, version; const Node *leaf; size_t start; } finger; -/* An item's cells follow the list's share count when the item was made. One - * made before the last share may be read by a shared copy, so it never - * changes again (a change copies it) and is retired instead of freed. */ -static PureCell *new_item(const PureList *list) { - uint64_t *made = calloc(1, sizeof(*made) + (list->fields ? list->fields : 1) * sizeof(PureCell)); - if (!made) return NULL; - *made = list->shares; - return (PureCell *)(made + 1); -} +static Node *child(const Node *node, unsigned i) { return node->child[i]; } +static double *numbers(const Layout *l, const Node *leaf, unsigned f) { return (double *)((char *)leaf + l->column[f]); } +static PureSpan *texts(const Layout *l, const Node *leaf, unsigned f) { return (PureSpan *)((char *)leaf + l->column[f]); } -static bool shared(const PureList *list, const PureCell *item) { return ((const uint64_t *)item)[-1] < list->shares; } -static bool shared_node(const PureList *list, const Node *node) { return node->made < list->shares; } -static Node *child(const Node *node, unsigned i) { return node->slot[i]; } -static PureCell *item_in(const Node *leaf, unsigned i) { return leaf->slot[i]; } +static PureCell cell_at(const Layout *l, const Node *leaf, unsigned i, unsigned f) { + if (!l->text_fields[f]) return (PureCell){numbers(l, leaf, f)[i], NULL, 0}; + PureSpan text = texts(l, leaf, f)[i]; + return (PureCell){0, (unsigned char *)text.data, text.size}; +} -static void free_cells(PureCell *item, unsigned fields, unsigned text) { - for (unsigned f = 0; f < fields; ++f) - if (text >> f & 1) free(item[f].text); - free((uint64_t *)item - 1); +static void put_cells(const Layout *l, Node *leaf, unsigned i, const PureCell *cells) { + for (unsigned f = 0; f < l->fields; ++f) + if (l->text_fields[f]) texts(l, leaf, f)[i] = (PureSpan){cells[f].text, cells[f].size}; + else numbers(l, leaf, f)[i] = cells[f].num; } -static unsigned text_mask(const PureList *list) { - unsigned text = 0; - for (unsigned f = 0; f < list->fields; ++f) text |= (unsigned)(list->text_fields[f] != 0) << f; - return text; +/* count items or children of from, from start on, to at in to (they may overlap). */ +static void move_items(const Layout *l, Node *to, unsigned at, const Node *from, unsigned start, unsigned count) { + if (!count) return; + if (!from->leaf) { + memmove(&to->child[at], &from->child[start], count * sizeof(Node *)); + return; + } + for (unsigned f = 0; f < l->fields; ++f) { + size_t width = l->text_fields[f] ? sizeof(PureSpan) : sizeof(double); + memmove((char *)to + l->column[f] + at * width, (const char *)from + l->column[f] + start * width, count * width); + } } -static void free_item(const PureList *list, PureCell *item) { - if (item) free_cells(item, list->fields, text_mask(list)); +/* A text's bytes, the list's own: after the list's share count when they + * were made. One made before the last share may be read by a shared copy. */ +static unsigned char *new_text(const PureList *list, const unsigned char *bytes, size_t size) { + uint64_t *made = malloc(sizeof(*made) + size); + if (!made) return NULL; + *made = list->shares; + memcpy(made + 1, bytes, size); + return (unsigned char *)(made + 1); } +static bool shared_text(const PureList *list, const unsigned char *text) { return ((const uint64_t *)text)[-1] < list->shares; } +static bool shared_node(const PureList *list, const Node *node) { return node->made < list->shares; } +static void free_text(const unsigned char *text) { if (text) free((uint64_t *)text - 1); } + /* Kept until the copies that may read it are gone (pure_retired_free). */ -static void retire(PureList *list, void *pointer, bool node) { +static void retire(PureList *list, void *pointer, bool text) { PureRetired *retired = list->retired; if (retired->count == retired->capacity) { size_t capacity = retired->capacity ? 2 * retired->capacity : 16; @@ -102,32 +136,37 @@ static void retire(PureList *list, void *pointer, bool node) { retired->entries = grown; retired->capacity = capacity; } - ((Retired *)retired->entries)[retired->count++] = - (Retired){pointer, node ? 0 : (uint16_t)text_mask(list), node ? 0 : (uint8_t)list->fields, node}; + ((Retired *)retired->entries)[retired->count++] = (Retired){pointer, text}; +} + +/* The list no longer has text: free it, or retire it while a copy may read it. */ +static void let_go_text(PureList *list, const unsigned char *text) { + if (!text) return; + if (!shared_text(list, text)) free_text(text); + else retire(list, (void *)text, true); } -/* The list no longer has item: free it, or retire it while a copy may read it. */ -static void let_go(PureList *list, PureCell *item) { - if (!shared(list, item)) free_item(list, item); - else retire(list, item, false); +/* The texts of an item the list no longer has. */ +static void let_go_cells(PureList *list, const PureCell *cells) { + for (unsigned t = 0; t < list->layout.texts; ++t) let_go_text(list, cells[list->layout.text_field[t]].text); } void pure_retired_free(PureRetired *retired) { Retired *entries = retired->entries; for (size_t i = 0; i < retired->count; ++i) { - if (entries[i].node) free(entries[i].pointer); - else free_cells(entries[i].pointer, entries[i].fields, entries[i].text); + if (entries[i].text) free_text(entries[i].pointer); + else free(entries[i].pointer); } free(entries); *retired = (PureRetired){0}; } static Node *new_node(PureList *list, bool leaf) { - Node *node = list->spare; + Node *node = list->spare[leaf]; if (node) { - list->spare = node->slot[0]; - --list->spares; - } else if (!(node = malloc(sizeof(*node)))) + list->spare[leaf] = (Node *)node->first; + --list->spares[leaf]; + } else if (!(node = malloc(leaf ? list->layout.leaf_size : INNER_SIZE))) return NULL; node->made = list->shares; node->count = 0; @@ -140,11 +179,11 @@ static Node *new_node(PureList *list, bool leaf) { /* A node the list no longer has (its items and children dealt with): * retired while a copy may read it, else kept for the next or freed. */ static void drop_node(PureList *list, Node *node) { - if (shared_node(list, node)) retire(list, node, true); - else if (list->spares < SPARE_NODES) { - node->slot[0] = list->spare; - list->spare = node; - ++list->spares; + if (shared_node(list, node)) retire(list, node, false); + else if (list->spares[node->leaf] < SPARE_NODES) { + node->first = list->spare[node->leaf]; + list->spare[node->leaf] = node; + ++list->spares[node->leaf]; } else free(node); } @@ -152,26 +191,30 @@ static void drop_node(PureList *list, Node *node) { /* ---- The stored form: each field in order, a number as its binary64 bits * (little-endian), a text as a u32le length and its bytes. */ -static size_t cell_bytes(const PureList *list, unsigned field, PureCell cell) { - return list->text_fields[field] ? 4 + cell.size : 8; -} +static size_t cell_bytes(const Layout *l, unsigned field, PureCell cell) { return l->text_fields[field] ? 4 + cell.size : 8; } -static size_t item_bytes(const PureList *list, const PureCell *item) { - size_t size = 0; - for (unsigned f = 0; f < list->fields; ++f) size += cell_bytes(list, f, item[f]); +static size_t item_bytes(const Layout *l, const Node *leaf, unsigned i) { + size_t size = l->number_bytes; + for (unsigned t = 0; t < l->texts; ++t) size += 4 + texts(l, leaf, l->text_field[t])[i].size; return size; } /* A node's summary, from its items or children. */ static void sum(const PureList *list, Node *node) { + const Layout *l = &list->layout; size_t items = 0, bytes = 0; - for (unsigned i = 0; i < node->count; ++i) { - if (node->leaf) ++items, bytes += item_bytes(list, item_in(node, i)); - else items += child(node, i)->items, bytes += child(node, i)->bytes; - } + if (node->leaf) { + items = node->count; + bytes = items * l->number_bytes; + for (unsigned t = 0; t < l->texts; ++t) { + const PureSpan *column = texts(l, node, l->text_field[t]); + for (unsigned i = 0; i < node->count; ++i) bytes += 4 + column[i].size; + } + } else + for (unsigned i = 0; i < node->count; ++i) items += child(node, i)->items, bytes += child(node, i)->bytes; node->items = items; node->bytes = bytes; - node->first = !node->count ? NULL : node->leaf ? item_in(node, 0) : child(node, 0)->first; + node->first = !node->count ? NULL : node->leaf ? node : child(node, 0)->first; } /* ---- Finding positions */ @@ -201,6 +244,7 @@ static const Node *leaf_of(const PureList *list, size_t at, size_t *within) { /* Where an item starts in the draft: the bytes of the children before it, * level by level. */ static size_t item_offset(const PureList *list, size_t at) { + const Layout *l = &list->layout; size_t offset = list->header; const Node *node = list->root; if (!node) return offset; @@ -213,23 +257,27 @@ static size_t item_offset(const PureList *list, size_t at) { } node = child(node, i); } - for (unsigned i = 0; i < at && i < node->count; ++i) offset += item_bytes(list, item_in(node, i)); + if (!l->texts) return offset + (at < node->count ? at : node->count) * l->number_bytes; + for (unsigned i = 0; i < at && i < node->count; ++i) offset += item_bytes(l, node, i); return offset; } /* The node at *place, the list's own to change: a copy (the node retired) * while a shared copy of the list may read it. NULL without memory. A copy * moves the version on even when the change it is for does not happen: the - * list no longer has the nodes its shared copies have. */ + * list no longer has the nodes its shared copies have. A leaf's copy has the + * texts of the leaf it copies: they stay shared, and are retired, not freed, + * when the list lets them go. */ static Node *own(PureList *list, Node **place) { Node *node = *place; if (!shared_node(list, node)) return node; Node *mine = new_node(list, node->leaf); if (!mine) return NULL; uint64_t made = mine->made; - memcpy(mine, node, sizeof(*mine)); + memcpy(mine, node, node->leaf ? list->layout.leaf_size : INNER_SIZE); mine->made = made; - retire(list, node, true); + if (mine->leaf) mine->first = mine->count ? mine : NULL; + retire(list, node, false); *place = mine; ++list->version; return mine; @@ -237,7 +285,8 @@ static Node *own(PureList *list, Node **place) { /* The path to the leaf holding position at, each node the list's own, and * at's place in the leaf: the number of steps, 0 without memory (the list - * is as it was: copies replace what they copy). */ + * is as it was: copies replace what they copy). The nodes on the path then + * name the leaf that holds their first item, a copied one too. */ static unsigned descend(PureList *list, size_t at, Step path[DEPTH], unsigned *within) { Node **place = &list->root; for (unsigned depth = 0; depth < DEPTH; ++depth) { @@ -246,12 +295,13 @@ static unsigned descend(PureList *list, size_t at, Step path[DEPTH], unsigned *w if (node->leaf) { path[depth] = (Step){node, 0}; *within = (unsigned)at; + for (unsigned d = depth; d-- > 0;) path[d].node->first = child(path[d].node, 0)->first; return depth + 1; } unsigned i = 0; while (i + 1 < node->count && at >= child(node, i)->items) at -= child(node, i)->items, ++i; path[depth] = (Step){node, i}; - place = (Node **)&node->slot[i]; + place = &node->child[i]; } return 0; } @@ -262,22 +312,24 @@ static void sum_path(const PureList *list, const Step *path, unsigned depth) { /* ---- Placing and removing */ -/* A place for an item: the path to it, and the nodes a split may take (one - * a level and a root), made before anything changes so that placing cannot - * fail. */ +/* A place for an item: the path to it, and the nodes a split may take (a + * leaf, then one a level above it and a root), made before anything changes + * so that placing cannot fail. */ typedef struct { Step path[DEPTH]; unsigned depth, within, extras, used; + bool appending; /* at the end of the list */ Node *extra[DEPTH + 1]; } Place; static bool prepare(PureList *list, size_t at, Place *p) { if (!list->root && !(list->root = new_node(list, true))) return false; p->extras = p->used = 0; + p->appending = at == list->count; if (!(p->depth = descend(list, at, p->path, &p->within))) return false; if (p->path[p->depth - 1].node->count < FANOUT) return true; for (; p->extras <= p->depth; ++p->extras) - if (!(p->extra[p->extras] = new_node(list, false))) { + if (!(p->extra[p->extras] = new_node(list, !p->extras))) { while (p->extras) drop_node(list, p->extra[--p->extras]); return false; } @@ -288,37 +340,47 @@ static void release_extras(PureList *list, Place *p) { while (p->extras > p->used) drop_node(list, p->extra[--p->extras]); } -/* pointer into the node at the end of the path, before index: a full node - * splits in two halves and the new half goes into its parent, up to a new - * root. */ -static void place(PureList *list, Place *p, void *pointer) { +/* An item's cells (the list owns its texts from then on) into the leaf at + * the end of the path, before its within: a full node splits and the new + * node goes into its parent, up to a new root. A node splits in halves, but + * at the end of the list it stays full and the new one starts with the item + * alone: a list that grows at its end (a history, a keyed list filled in key + * order) fills its leaves. */ +static void place(PureList *list, Place *p, const PureCell *cells) { + const Layout *l = &list->layout; unsigned index = p->within; + Node *pointer = NULL; /* above the leaf: the node that goes in */ for (unsigned d = p->depth; d-- > 0;) { Node *node = p->path[d].node; if (node->count < FANOUT) { - memmove(&node->slot[index + 1], &node->slot[index], (node->count - index) * sizeof(void *)); - node->slot[index] = pointer; + move_items(l, node, index + 1, node, index, node->count - index); + if (node->leaf) put_cells(l, node, index, cells); + else node->child[index] = pointer; ++node->count; sum_path(list, p->path, d + 1); break; } - void *all[FANOUT + 1]; - memcpy(all, node->slot, index * sizeof(void *)); - all[index] = pointer; - memcpy(all + index + 1, node->slot + index, (FANOUT - index) * sizeof(void *)); + unsigned keep = p->appending && index == FANOUT ? FANOUT : (FANOUT + 1) / 2; Node *right = p->extra[p->used++]; - right->leaf = node->leaf; - node->count = (FANOUT + 1) / 2; - right->count = FANOUT + 1 - node->count; - memcpy(node->slot, all, node->count * sizeof(void *)); - memcpy(right->slot, all + node->count, right->count * sizeof(void *)); + if (index < keep) { + move_items(l, right, 0, node, keep - 1, FANOUT - (keep - 1)); + move_items(l, node, index + 1, node, index, keep - 1 - index); + if (node->leaf) put_cells(l, node, index, cells); + else node->child[index] = pointer; + } else { + move_items(l, right, 0, node, keep, index - keep); + if (node->leaf) put_cells(l, right, index - keep, cells); + else right->child[index - keep] = pointer; + move_items(l, right, index - keep + 1, node, index, FANOUT - index); + } + node->count = keep; + right->count = FANOUT + 1 - keep; sum(list, node); sum(list, right); if (!d) { /* the root split: a new one above both halves */ Node *root = p->extra[p->used++]; - root->leaf = false; - root->slot[0] = node; - root->slot[1] = right; + root->child[0] = node; + root->child[1] = right; root->count = 2; sum(list, root); list->root = root; @@ -332,13 +394,14 @@ static void place(PureList *list, Place *p, void *pointer) { ++list->version; } -/* The item at the leaf's within, out of the tree (the caller keeps it): an - * emptied node leaves its parent, a node that fits with a neighbour merges - * into one (when the neighbour can be made the list's own), and a root with - * one child gives way to it. */ +/* The item at the leaf's within, out of the tree (the caller has its cells): + * an emptied node leaves its parent, a node that fits with a neighbour + * merges into one (when the neighbour can be made the list's own), and a + * root with one child gives way to it. */ static void take_out(PureList *list, Step *path, unsigned depth, unsigned within) { + const Layout *l = &list->layout; Node *leaf = path[depth - 1].node; - memmove(&leaf->slot[within], &leaf->slot[within + 1], (leaf->count - within - 1) * sizeof(void *)); + move_items(l, leaf, within, leaf, within + 1, leaf->count - within - 1); --leaf->count; unsigned d = depth - 1; for (;; --d) { @@ -348,21 +411,21 @@ static void take_out(PureList *list, Step *path, unsigned depth, unsigned within Node *parent = path[d - 1].node; unsigned at = path[d - 1].index; if (!node->count) { - memmove(&parent->slot[at], &parent->slot[at + 1], (parent->count - at - 1) * sizeof(void *)); + memmove(&parent->child[at], &parent->child[at + 1], (parent->count - at - 1) * sizeof(Node *)); --parent->count; drop_node(list, node); continue; } unsigned other = at + 1 < parent->count ? at + 1 : at ? at - 1 : at; Node *neighbour = other != at && node->count + child(parent, other)->count <= FANOUT - ? own(list, (Node **)&parent->slot[other]) : NULL; + ? own(list, &parent->child[other]) : NULL; if (!neighbour) break; Node *left = other < at ? neighbour : node, *right = other < at ? node : neighbour; unsigned left_at = other < at ? other : at; - memcpy(&left->slot[left->count], right->slot, right->count * sizeof(void *)); + move_items(l, left, left->count, right, 0, right->count); left->count += right->count; sum(list, left); - memmove(&parent->slot[left_at + 1], &parent->slot[left_at + 2], (parent->count - left_at - 2) * sizeof(void *)); + memmove(&parent->child[left_at + 1], &parent->child[left_at + 2], (parent->count - left_at - 2) * sizeof(Node *)); --parent->count; drop_node(list, right); } @@ -386,20 +449,30 @@ PureList *pure_list_new(unsigned fields, const uint8_t *text_fields) { if (fields > 16) return NULL; PureList *list = calloc(1, sizeof(*list)); if (!list) return NULL; - list->fields = fields; - memcpy(list->text_fields, text_fields, fields); + Layout *l = &list->layout; + l->fields = fields; + size_t at = sizeof(Node); + for (unsigned f = 0; f < fields; ++f) { + l->text_fields[f] = text_fields[f] != 0; + l->column[f] = (uint32_t)at; + at += FANOUT * (l->text_fields[f] ? sizeof(PureSpan) : sizeof(double)); + if (l->text_fields[f]) l->text_field[l->texts++] = (uint8_t)f; + else l->number_bytes += 8; + } + l->leaf_size = at; list->id = __atomic_add_fetch(&lists_made, 1, __ATOMIC_RELAXED); return list; } -/* Every node and item below node let go (retired where a copy may read them). */ +/* Every node and text below node let go (retired where a copy may read them). */ static void let_go_tree(PureList *list, Node *node) { if (!node) return; + const Layout *l = &list->layout; for (unsigned i = 0; i < node->count; ++i) { - if (node->leaf) let_go(list, item_in(node, i)); - else let_go_tree(list, child(node, i)); + if (!node->leaf) let_go_tree(list, child(node, i)); + else for (unsigned t = 0; t < l->texts; ++t) let_go_text(list, texts(l, node, l->text_field[t])[i].data); } - if (shared_node(list, node)) retire(list, node, true); + if (shared_node(list, node)) retire(list, node, false); else free(node); } @@ -409,11 +482,12 @@ void pure_list_free(PureList *list) { if (__atomic_sub_fetch(&list->refs, 1, __ATOMIC_ACQ_REL)) return; } else { let_go_tree(list, list->root); - while (list->spare) { - Node *next = list->spare->slot[0]; - free(list->spare); - list->spare = next; - } + for (unsigned kind = 0; kind < 2; ++kind) + while (list->spare[kind]) { + Node *next = (Node *)list->spare[kind]->first; + free(list->spare[kind]); + list->spare[kind] = next; + } } free(list); } @@ -423,9 +497,8 @@ size_t pure_list_count(const PureList *list) { return list->count; } PureList *pure_list_share(PureList *list, PureRetired *retired) { PureList *copy = malloc(sizeof(*copy)); if (!copy) return NULL; - *copy = (PureList){.fields = list->fields, .root = list->root, .count = list->count, .id = list->id, + *copy = (PureList){.layout = list->layout, .root = list->root, .count = list->count, .id = list->id, .version = list->version, .refs = 1, .copy = true}; - memcpy(copy->text_fields, list->text_fields, sizeof(copy->text_fields)); list->retired = retired; ++list->shares; return copy; @@ -449,22 +522,21 @@ static bool log_change(PureUndo *undo, Entry entry) { return true; } -static bool copy_cell(const PureList *list, unsigned field, PureCell from, PureCell *to) { - *to = (PureCell){.num = list->text_fields[field] ? 0 : from.num}; - if (!list->text_fields[field] || !from.size) return true; - to->text = malloc(from.size); - if (!to->text) return false; - memcpy(to->text, from.text, from.size); +/* A cell the list can keep: a text's bytes copied (NULL when empty). */ +static bool own_cell(const PureList *list, unsigned field, PureCell from, PureCell *to) { + *to = (PureCell){.num = list->layout.text_fields[field] ? 0 : from.num}; + if (!list->layout.text_fields[field] || !from.size) return true; + if (!(to->text = new_text(list, from.text, from.size))) return false; to->size = from.size; return true; } -static unsigned char *encode_cell(const PureList *list, unsigned field, PureCell cell, unsigned char *out) { +static unsigned char *encode_cell(const Layout *l, unsigned field, PureCell cell, unsigned char *out) { uint64_t bits = cell.size; unsigned width = 4; - if (!list->text_fields[field]) { memcpy(&bits, &cell.num, sizeof(bits)); width = 8; } + if (!l->text_fields[field]) { memcpy(&bits, &cell.num, sizeof(bits)); width = 8; } for (unsigned i = 0; i < width; ++i) *out++ = (unsigned char)(bits >> (8 * i)); - if (list->text_fields[field] && cell.size) { memcpy(out, cell.text, cell.size); out += cell.size; } + if (l->text_fields[field] && cell.size) { memcpy(out, cell.text, cell.size); out += cell.size; } return out; } @@ -481,18 +553,21 @@ void pure_list_mirror(PureList *list, PureDraft *draft, size_t header) { list->header = header; } -/* Every item in order, to visit (false stops it). */ -static bool visit_items(const Node *node, bool (*visit)(void *, PureCell *), void *context) { - for (unsigned i = 0; node && i < node->count; ++i) - if (node->leaf ? !visit(context, item_in(node, i)) : !visit_items(child(node, i), visit, context)) return false; +/* Every leaf in order, to visit (false stops it). */ +static bool visit_leaves(const Node *node, bool (*visit)(void *, const Node *), void *context) { + if (!node) return true; + if (node->leaf) return visit(context, node); + for (unsigned i = 0; i < node->count; ++i) + if (!visit_leaves(child(node, i), visit, context)) return false; return true; } -typedef struct { const PureList *list; unsigned char *at; } Encoding; +typedef struct { const Layout *layout; unsigned char *at; } Encoding; -static bool encode_item(void *context, PureCell *item) { +static bool encode_leaf(void *context, const Node *leaf) { Encoding *e = context; - for (unsigned f = 0; f < e->list->fields; ++f) e->at = encode_cell(e->list, f, item[f], e->at); + for (unsigned i = 0; i < leaf->count; ++i) + for (unsigned f = 0; f < e->layout->fields; ++f) e->at = encode_cell(e->layout, f, cell_at(e->layout, leaf, i, f), e->at); return true; } @@ -500,23 +575,24 @@ PURE_COLD PureResult pure_list_write(PureList *list) { size_t size = list->root ? list->root->bytes : 0; unsigned char *bytes = size ? malloc(size) : NULL; if (size && !bytes) return PURE_ALLOCATION; - Encoding encoding = {list, bytes}; - visit_items(list->root, encode_item, &encoding); + Encoding encoding = {&list->layout, bytes}; + visit_leaves(list->root, encode_leaf, &encoding); PureResult result = mirror(list, 0, 0, 0, bytes, size); free(bytes); return result; } PURE_COLD PureResult pure_list_read(PureList *list, const unsigned char *bytes, size_t size) { + const Layout *l = &list->layout; PureCell cells[16]; for (size_t at = 0; at < size;) { - for (unsigned f = 0; f < list->fields; ++f) { - size_t width = list->text_fields[f] ? 4 : 8; + for (unsigned f = 0; f < l->fields; ++f) { + size_t width = l->text_fields[f] ? 4 : 8; if (size - at < width) return PURE_STORE_ERROR; uint64_t bits = 0; for (size_t i = 0; i < width; ++i) bits |= (uint64_t)bytes[at + i] << (8 * i); at += width; - if (!list->text_fields[f]) { + if (!l->text_fields[f]) { cells[f] = (PureCell){0}; memcpy(&cells[f].num, &bits, sizeof(bits)); if (!isfinite(cells[f].num)) return PURE_STORE_ERROR; @@ -534,24 +610,26 @@ PURE_COLD PureResult pure_list_read(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; - PureCell *item = new_item(list); - if (!item) return PURE_ALLOCATION; - for (unsigned f = 0; f < list->fields; ++f) - if (!copy_cell(list, f, cells[f], &item[f])) { free_item(list, item); return PURE_ALLOCATION; } - size_t size = list->draft ? item_bytes(list, item) : 0; + PureCell item[16]; + unsigned owned = 0; + for (; owned < l->fields; ++owned) + if (!own_cell(list, owned, cells[owned], &item[owned])) break; + size_t size = 0; + for (unsigned f = 0; list->draft && f < l->fields; ++f) size += cell_bytes(l, f, cells[f]); unsigned char *bytes = size ? malloc(size) : NULL, *end = bytes; Place p; - if ((size && !bytes) || !prepare(list, at, &p)) { + if (owned < l->fields || (size && !bytes) || !prepare(list, at, &p)) { free(bytes); - free_item(list, item); + for (unsigned f = 0; f < owned; ++f) free_text(item[f].text); return PURE_ALLOCATION; } - for (unsigned f = 0; bytes && f < list->fields; ++f) end = encode_cell(list, f, item[f], end); + for (unsigned f = 0; bytes && f < l->fields; ++f) end = encode_cell(l, f, item[f], end); if (undo && !log_change(undo, (Entry){.change = INSERTED, .list = list, .at = at})) { release_extras(list, &p); free(bytes); - free_item(list, item); + for (unsigned f = 0; f < l->fields; ++f) free_text(item[f].text); return PURE_ALLOCATION; } PureResult result = mirror(list, at, 0, 0, bytes, size); @@ -559,69 +637,79 @@ PureResult pure_list_insert(PureList *list, size_t at, const PureCell *cells, Pu if (result != PURE_OK) { if (undo) --undo->count; release_extras(list, &p); - free_item(list, item); + for (unsigned f = 0; f < l->fields; ++f) free_text(item[f].text); return result; } place(list, &p, item); return PURE_OK; } +/* Room for one more item's cells in the log. */ +static PureCell *log_cells(PureUndo *undo, unsigned fields) { + if (undo->cell_count + fields > undo->cell_capacity) { + size_t capacity = undo->cell_capacity ? 2 * undo->cell_capacity : 64; + while (capacity < undo->cell_count + fields) capacity *= 2; + PureCell *cells = realloc(undo->cells, capacity * sizeof(*cells)); + if (!cells) return NULL; + undo->cells = cells; + undo->cell_capacity = capacity; + } + return undo->cells + undo->cell_count; +} + PureResult pure_list_remove(PureList *list, size_t at, PureUndo *undo) { + const Layout *l = &list->layout; if (at >= list->count) return PURE_NOT_FOUND; Step path[DEPTH]; unsigned within, depth = descend(list, at, path, &within); if (!depth) return PURE_ALLOCATION; - PureCell *item = item_in(path[depth - 1].node, within); - if (!log_change(undo, (Entry){.change = REMOVED, .list = list, .at = at, .item = item})) return PURE_ALLOCATION; - PureResult result = mirror(list, at, 0, item_bytes(list, item), NULL, 0); - if (result != PURE_OK) { --undo->count; return result; } + Node *leaf = path[depth - 1].node; + PureCell *cells = undo ? log_cells(undo, l->fields) : NULL, kept[16]; + if (undo && (!cells || !log_change(undo, (Entry){.change = REMOVED, .list = list, .at = at, .cells = undo->cell_count}))) + return PURE_ALLOCATION; + if (!cells) cells = kept; + for (unsigned f = 0; f < l->fields; ++f) cells[f] = cell_at(l, leaf, within, f); + PureResult result = mirror(list, at, 0, item_bytes(l, leaf, within), NULL, 0); + if (result != PURE_OK) { + if (undo) --undo->count; + return result; + } + if (undo) undo->cell_count += l->fields; + else let_go_cells(list, cells); take_out(list, path, depth, within); return PURE_OK; } -/* A shared item's copy with every field but one (left empty). */ -static PureCell *copy_item(const PureList *list, const PureCell *item, unsigned except) { - PureCell *fresh = new_item(list); - for (unsigned f = 0; fresh && f < list->fields; ++f) - if (f != except && !copy_cell(list, f, item[f], &fresh[f])) { - free_item(list, fresh); - fresh = NULL; - } - return fresh; -} - 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; Step path[DEPTH]; unsigned within, depth = descend(list, at, path, &within); PureCell copy; - if (!depth || !copy_cell(list, field, value, ©)) return PURE_ALLOCATION; + if (!depth || !own_cell(list, field, value, ©)) return PURE_ALLOCATION; Node *leaf = path[depth - 1].node; - PureCell *item = item_in(leaf, within); - PureCell *fresh = shared(list, item) ? copy_item(list, item, field) : item; - size_t size = list->draft ? cell_bytes(list, field, copy) : 0; + PureCell old = cell_at(l, leaf, within, field); + size_t size = list->draft ? cell_bytes(l, field, copy) : 0; unsigned char *bytes = size ? malloc(size) : NULL; - Entry entry = fresh == item ? (Entry){.change = CHANGED, .list = list, .at = at, .field = field, .old = item[field]} : - (Entry){.change = REPLACED, .list = list, .at = at, .item = item}; - if (!fresh || (size && !bytes) || !log_change(undo, entry)) { + if ((size && !bytes) || + (undo && !log_change(undo, (Entry){.change = CHANGED, .list = list, .at = at, .field = field, .old = old}))) { free(bytes); - free(copy.text); - if (fresh != item) free_item(list, fresh); + free_text(copy.text); return PURE_ALLOCATION; } - if (bytes) encode_cell(list, field, copy, bytes); + if (bytes) encode_cell(l, field, copy, bytes); size_t offset = 0; - for (unsigned f = 0; f < field; ++f) offset += cell_bytes(list, f, item[f]); - PureResult result = mirror(list, at, offset, cell_bytes(list, field, item[field]), bytes, size); + for (unsigned f = 0; f < field; ++f) offset += cell_bytes(l, f, cell_at(l, leaf, within, f)); + PureResult result = mirror(list, at, offset, cell_bytes(l, field, old), bytes, size); free(bytes); if (result != PURE_OK) { - --undo->count; - free(copy.text); - if (fresh != item) free_item(list, fresh); + if (undo) --undo->count; + free_text(copy.text); return result; } - fresh[field] = copy; - leaf->slot[within] = fresh; + if (l->text_fields[field]) texts(l, leaf, field)[within] = (PureSpan){copy.text, copy.size}; + else numbers(l, leaf, field)[within] = copy.num; + if (!undo) let_go_text(list, old.text); sum_path(list, path, depth); ++list->version; return PURE_OK; @@ -629,13 +717,26 @@ 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) { size_t within; - return item_in(leaf_of(list, at, &within), (unsigned)within)[field]; + return cell_at(&list->layout, leaf_of(list, at, &within), (unsigned)within, field); +} + +bool pure_list_run(const PureList *list, size_t at, PureRun *run) { + const Layout *l = &list->layout; + if (at >= list->count) return false; + size_t within; + const Node *leaf = leaf_of(list, at, &within); + run->start = at - within; + run->count = leaf->count; + for (unsigned f = 0; f < l->fields; ++f) + if (l->text_fields[f]) run->column[f].texts = texts(l, leaf, f); + else run->column[f].numbers = numbers(l, leaf, f); + return true; } /* How two cells of a field order: numbers by value (-0 and +0 are one key), * texts by their bytes. */ -static int order(const PureList *list, unsigned field, PureCell a, PureCell b) { - if (!list->text_fields[field]) return (a.num > b.num) - (a.num < b.num); +static int order(const Layout *l, unsigned field, PureCell a, PureCell b) { + if (!l->text_fields[field]) return (a.num > b.num) - (a.num < b.num); size_t shorter = a.size < b.size ? a.size : b.size; int bytes = shorter ? memcmp(a.text, b.text, shorter) : 0; return bytes ? (bytes > 0) - (bytes < 0) : (a.size > b.size) - (a.size < b.size); @@ -644,6 +745,7 @@ static int order(const PureList *list, unsigned field, PureCell a, PureCell b) { /* Down the child whose first item's key is the last at or below key (the * first child when none is), then the leaf's first item not below it. */ size_t pure_list_seek(const PureList *list, unsigned field, PureCell key, bool *found) { + const Layout *l = &list->layout; const Node *node = list->root; size_t start = 0; *found = false; @@ -652,7 +754,7 @@ size_t pure_list_seek(const PureList *list, unsigned field, PureCell key, bool * unsigned low = 1, high = node->count; while (low < high) { unsigned middle = low + (high - low) / 2; - if (order(list, field, child(node, middle)->first[field], key) <= 0) low = middle + 1; + if (order(l, field, cell_at(l, child(node, middle)->first, 0, field), key) <= 0) low = middle + 1; else high = middle; } for (unsigned i = 0; i + 1 < low; ++i) start += child(node, i)->items; @@ -661,65 +763,74 @@ size_t pure_list_seek(const PureList *list, unsigned field, PureCell key, bool * unsigned low = 0, high = node->count; while (low < high) { unsigned middle = low + (high - low) / 2; - if (order(list, field, item_in(node, middle)[field], key) < 0) low = middle + 1; + if (order(l, field, cell_at(l, node, middle, field), key) < 0) low = middle + 1; else high = middle; } - *found = low < node->count && !order(list, field, item_in(node, low)[field], key); + *found = low < node->count && !order(l, field, cell_at(l, node, low, field), key); return start + low; } +/* An item where it is: its leaf and its place there. */ +typedef struct { const Node *leaf; unsigned index; } Ref; + +static PureCell ref_cell(const Layout *l, Ref r, unsigned field) { return cell_at(l, r.leaf, r.index, field); } + /* Positions sorted by their items' field, stably (spare holds count). */ -PURE_COLD static void merge_sort(const PureList *list, unsigned field, PureCell *const *items, size_t *at, size_t *spare, +PURE_COLD static void merge_sort(const Layout *l, unsigned field, const Ref *items, size_t *at, size_t *spare, size_t count) { if (count < 2) return; size_t half = count / 2, i = 0, j = half, k = 0; - merge_sort(list, field, items, at, spare, half); - merge_sort(list, field, items, at + half, spare, count - half); + merge_sort(l, field, items, at, spare, half); + merge_sort(l, field, items, at + half, spare, count - half); while (i < half && j < count) - spare[k++] = order(list, field, items[at[j]][field], items[at[i]][field]) < 0 ? at[j++] : at[i++]; + spare[k++] = order(l, field, ref_cell(l, items[at[j]], field), ref_cell(l, items[at[i]], field)) < 0 ? at[j++] : at[i++]; while (i < half) spare[k++] = at[i++]; while (j < count) spare[k++] = at[j++]; memcpy(at, spare, count * sizeof(*at)); } -typedef struct { PureCell **items; size_t count; } Gathered; +typedef struct { Ref *items; size_t count; } Gathered; -static bool gather_item(void *context, PureCell *item) { +static bool gather_leaf(void *context, const Node *leaf) { Gathered *g = context; - g->items[g->count++] = item; + for (unsigned i = 0; i < leaf->count; ++i) g->items[g->count++] = (Ref){leaf, i}; return true; } PURE_COLD PureResult pure_list_sorted(const PureList *list, unsigned field, PureList **out, size_t *moved) { + const Layout *l = &list->layout; *out = NULL; - Gathered gathered = {list->count ? malloc(list->count * sizeof(PureCell *)) : NULL, 0}; + Gathered gathered = {list->count ? malloc(list->count * sizeof(Ref)) : NULL, 0}; if (list->count && !gathered.items) return PURE_ALLOCATION; - visit_items(list->root, gather_item, &gathered); - PureCell **items = gathered.items; + visit_leaves(list->root, gather_leaf, &gathered); + const Ref *items = gathered.items; bool ordered = true; - for (size_t i = 1; i < list->count && ordered; ++i) ordered = order(list, field, items[i - 1][field], items[i][field]) < 0; + for (size_t i = 1; i < list->count && ordered; ++i) + ordered = order(l, field, ref_cell(l, items[i - 1], field), ref_cell(l, items[i], field)) < 0; if (ordered) { - free(items); + free(gathered.items); return PURE_OK; } size_t *at = malloc(2 * list->count * sizeof(*at)); if (!at) { - free(items); + free(gathered.items); return PURE_ALLOCATION; } for (size_t i = 0; i < list->count; ++i) at[i] = i; - merge_sort(list, field, items, at, at + list->count, list->count); + merge_sort(l, field, items, at, at + list->count, list->count); PureResult result = PURE_OK; for (size_t i = 1; i < list->count && result == PURE_OK; ++i) - if (!order(list, field, items[at[i - 1]][field], items[at[i]][field])) result = PURE_CONFLICT; - PureList *sorted = result == PURE_OK ? pure_list_new(list->fields, list->text_fields) : NULL; + if (!order(l, field, ref_cell(l, items[at[i - 1]], field), ref_cell(l, items[at[i]], field))) result = PURE_CONFLICT; + PureList *sorted = result == PURE_OK ? pure_list_new(l->fields, l->text_fields) : NULL; if (result == PURE_OK && !sorted) result = PURE_ALLOCATION; for (size_t i = 0; result == PURE_OK && i < list->count; ++i) { - result = pure_list_insert(sorted, i, items[at[i]], NULL); + PureCell cells[16]; + for (unsigned f = 0; f < l->fields; ++f) cells[f] = ref_cell(l, items[at[i]], f); + result = pure_list_insert(sorted, i, cells, NULL); moved[at[i]] = i; } free(at); - free(items); + free(gathered.items); if (result != PURE_OK) { pure_list_free(sorted); return result; @@ -730,36 +841,36 @@ PURE_COLD PureResult pure_list_sorted(const PureList *list, unsigned field, Pure /* Undoing needs no memory but for a node an item goes back into (the event * took one out and the tree was since reshaped): without it, the process - * stops rather than keep half an event (the store keeps the last save). */ + * stops rather than keep half an event (the store keeps the last save). The + * texts undone were made during the event: no copy reads them. */ void pure_undo_rollback(PureUndo *undo, size_t mark) { Entry *entries = undo->entries; for (size_t i = undo->count; i-- > mark;) { Entry *e = &entries[i]; PureList *list = e->list; + const Layout *l = &list->layout; Step path[DEPTH]; unsigned within, depth; if (e->change == REMOVED) { Place p; if (!prepare(list, e->at, &p)) abort(); - place(list, &p, e->item); + place(list, &p, undo->cells + e->cells); + undo->cell_count = e->cells; continue; } if (!(depth = descend(list, e->at, path, &within))) abort(); Node *leaf = path[depth - 1].node; if (e->change == INSERTED) { - PureCell *item = item_in(leaf, within); + for (unsigned t = 0; t < l->texts; ++t) free_text(texts(l, leaf, l->text_field[t])[within].data); take_out(list, path, depth, within); - free_item(list, item); continue; } - PureCell *item = item_in(leaf, within); - if (e->change == REPLACED) { - free_item(list, item); - leaf->slot[within] = e->item; - } else { - if (list->text_fields[e->field]) free(item[e->field].text); - item[e->field] = e->old; - } + if (l->text_fields[e->field]) { + PureSpan *text = &texts(l, leaf, e->field)[within]; + free_text(text->data); + *text = (PureSpan){e->old.text, e->old.size}; + } else + numbers(l, leaf, e->field)[within] = e->old.num; sum_path(list, path, depth); ++list->version; } @@ -770,13 +881,14 @@ void pure_undo_commit(PureUndo *undo) { Entry *entries = undo->entries; for (size_t i = 0; i < undo->count; ++i) { Entry *e = &entries[i]; - if (e->change == REMOVED || e->change == REPLACED) let_go(e->list, e->item); - else if (e->change == CHANGED && e->list->text_fields[e->field]) free(e->old.text); + if (e->change == REMOVED) let_go_cells(e->list, undo->cells + e->cells); + else if (e->change == CHANGED) let_go_text(e->list, e->old.text); } - undo->count = 0; + undo->count = undo->cell_count = 0; } void pure_undo_free(PureUndo *undo) { free(undo->entries); + free(undo->cells); *undo = (PureUndo){0}; } diff --git a/core/module-list.h b/core/module-list.h index 38eb0f7..a3f4ad6 100644 --- a/core/module-list.h +++ b/core/module-list.h @@ -21,9 +21,23 @@ * successful one frees what it replaced. */ typedef struct { double num; unsigned char *text; size_t size; } PureCell; typedef struct PureList PureList; -typedef struct { void *entries; size_t count, capacity; } PureUndo; -/* Items a list let go while a shared copy may still read them. */ +/* An event's list changes; a removed item's cells wait in cells. */ +typedef struct { + void *entries; + size_t count, capacity; + PureCell *cells; + size_t cell_count, cell_capacity; +} PureUndo; +/* Nodes and texts a list let go while a shared copy may still read them. */ typedef struct { void *entries; size_t count, capacity; } PureRetired; +/* Items one after another, as a leaf of the list keeps them: positions start + * to start + count - 1, each field's cells side by side (a number field's as + * numbers, a text field's as spans). */ +typedef struct { + size_t start; + unsigned count; + union { const double *numbers; const PureSpan *texts; } column[16]; +} PureRun; enum { PURE_LIST_ITEMS = 1 << 24 }; @@ -31,16 +45,17 @@ PureList *pure_list_new(unsigned fields, const uint8_t *text_fields); void pure_list_free(PureList *); size_t pure_list_count(const PureList *); /* Share the items as they are with a read-only copy (for readers on other - * threads), without copying them: from then on the list copies an item it - * changes, and items it lets go go to retired instead of being freed, since - * the copy may still read them. The copy is freed with pure_list_free, on any - * thread, when its last reference goes (pure_list_hold takes one more). Only - * between events (no change waits to be committed or undone). */ + * threads), without copying them: from then on the list copies the nodes it + * changes, and nodes and texts it lets go go to retired instead of being + * freed, since the copy may still read them. The copy is freed with + * pure_list_free, on any thread, when its last reference goes + * (pure_list_hold takes one more). Only between events (no change waits to + * be committed or undone). */ PureList *pure_list_share(PureList *, PureRetired *retired); void pure_list_hold(PureList *copy); /* Whether copy shares list's items as they are now. */ bool pure_list_current(const PureList *copy, const PureList *list); -/* Free items retired while no copy that shared them remains. */ +/* Free what was retired while no copy that shared it remains. */ void pure_retired_free(PureRetired *); /* Insert a copy of cells (text bytes are copied) before position at. A NULL * log means the change is not undone. */ @@ -48,6 +63,9 @@ PureResult pure_list_insert(PureList *, size_t at, const PureCell *cells, PureUn PureResult pure_list_remove(PureList *, size_t at, PureUndo *); PureResult pure_list_set(PureList *, size_t at, unsigned field, PureCell value, PureUndo *); PureCell pure_list_get(const PureList *, size_t at, unsigned field); +/* The run holding position at (false past the end): for reading item after + * item without finding each. Valid until the list changes. */ +bool pure_list_run(const PureList *, size_t at, PureRun *); /* In a list ordered by field (a keyed list): the first position whose item's * field does not order below key (numbers by value, texts by bytes), and * whether that item's field is key. */ |
