refactor(extmarks): use a more efficient representation

marktree.c was originally constructed as a "generic" datatype,
to make the prototyping of its internal logic as simple as possible
and also as the usecases for various kinds of extmarks/decorations was not yet decided.
As a consequence of this, various extra indirections and allocations was
needed to use marktree to implement extmarks (ns/id pairs) and
decorations of different kinds (some which is just a single highlight
id, other an allocated list of virtual text/lines)

This change removes a lot of indirection, by making Marktree specialized
for the usecase. In particular, the namespace id and mark id is stored
directly, instead of the 64-bit global id particular to the Marktree
struct. This removes the two maps needed to convert between global and
per-ns ids.

Also, "small" decorations are stored inline, i.e. those who
doesn't refer to external heap memory anyway. That is highlights (with
priority+flags) are stored inline, while virtual text, which anyway
occurs a lot of heap allocations, do not. (previously a hack was used
to elide heap allocations for highlights with standard prio+flags)

TODO(bfredl): the functionaltest-lua CI version of gcc is having
severe issues with uint16_t bitfields, so splitting up compound
assignments and redundant casts are needed. Clean this up once we switch
to a working compiler version.
This commit is contained in:
Björn Linse
2021-10-25 21:51:29 +02:00
parent c09147aad9
commit 95ab979fde
15 changed files with 462 additions and 460 deletions

View File

@@ -48,28 +48,29 @@
# include "extmark.c.generated.h"
#endif
static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put)
static uint32_t *buf_ns_ref(buf_T *buf, uint32_t ns_id, bool put)
{
return map_ref(uint64_t, ExtmarkNs)(buf->b_extmark_ns, ns_id, put);
return map_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, put);
}
/// Create or update an extmark
///
/// must not be used during iteration!
/// @returns the internal mark id
uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T col, int end_row,
colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity,
ExtmarkOp op)
void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col, int end_row,
colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity,
ExtmarkOp op)
{
ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
assert(ns != NULL);
mtpos_t old_pos;
uint64_t mark = 0;
uint64_t id = idp ? *idp : 0;
uint32_t *ns = buf_ns_ref(buf, ns_id, true);
uint32_t id = idp ? *idp : 0;
bool decor_full = false;
uint8_t decor_level = kDecorLevelNone; // no decor
if (decor) {
if (kv_size(decor->virt_text) || kv_size(decor->virt_lines)) {
decor_full = true;
decor = xmemdup(decor, sizeof *decor);
}
decor_level = kDecorLevelVisible; // decor affects redraw
if (kv_size(decor->virt_lines)) {
decor_level = kDecorLevelVirtLine; // decor affects horizontal size
@@ -77,50 +78,64 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T
}
if (id == 0) {
id = ns->free_id++;
id = ++*ns;
} else {
uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id);
if (old_mark) {
if (old_mark & MARKTREE_PAIRED_FLAG || end_row > -1) {
MarkTreeIter itr[1] = { 0 };
mtkey_t old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
if (old_mark.id) {
if (mt_paired(old_mark) || end_row > -1) {
extmark_del(buf, ns_id, id);
} else {
MarkTreeIter itr[1] = { 0 };
old_pos = marktree_lookup(buf->b_marktree, old_mark, itr);
// TODO(bfredl): we need to do more if "revising" a decoration mark.
assert(itr->node);
if (old_pos.row == row && old_pos.col == col) {
ExtmarkItem it = map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index,
old_mark);
if (it.decor) {
decor_remove(buf, row, row, it.decor);
if (old_mark.pos.row == row && old_mark.pos.col == col) {
if (marktree_decor_level(old_mark) > kDecorLevelNone) {
decor_remove(buf, row, row, old_mark.decor_full);
old_mark.decor_full = NULL;
}
mark = marktree_revise(buf->b_marktree, itr, decor_level);
old_mark.flags = 0;
if (decor_full) {
old_mark.decor_full = decor;
} else if (decor) {
old_mark.hl_id = decor->hl_id;
// Workaround: the gcc compiler of functionaltest-lua build
// apparently incapable of handling basic integer constants.
// This can be underanged as soon as we bump minimal gcc version.
old_mark.flags = (uint16_t)(old_mark.flags
| (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0));
old_mark.priority = decor->priority;
}
marktree_revise(buf->b_marktree, itr, decor_level, old_mark);
goto revised;
}
marktree_del_itr(buf->b_marktree, itr, false);
}
} else {
ns->free_id = MAX(ns->free_id, id+1);
*ns = MAX(*ns, id);
}
}
if (end_row > -1) {
mark = marktree_put_pair(buf->b_marktree,
row, col, right_gravity,
end_row, end_col, end_right_gravity, decor_level);
} else {
mark = marktree_put(buf->b_marktree, row, col, right_gravity, decor_level);
mtkey_t mark = { { row, col }, ns_id, id, 0,
mt_flags(right_gravity, decor_level), 0, NULL };
if (decor_full) {
mark.decor_full = decor;
} else if (decor) {
mark.hl_id = decor->hl_id;
// workaround: see above
mark.flags = (uint16_t)(mark.flags | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0));
mark.priority = decor->priority;
}
revised:
map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark,
(ExtmarkItem){ ns_id, id, decor });
map_put(uint64_t, uint64_t)(ns->map, id, mark);
marktree_put(buf->b_marktree, mark, end_row, end_col, end_right_gravity);
revised:
if (op != kExtmarkNoUndo) {
// TODO(bfredl): this doesn't cover all the cases and probably shouldn't
// be done "prematurely". Any movement in undo history might necessitate
// adding new marks to old undo headers.
u_extmark_set(buf, mark, row, col);
// adding new marks to old undo headers. add a test case for this (doesn't
// fail extmark_spec.lua, and it should)
uint64_t mark_id = mt_lookup_id(ns_id, id, false);
u_extmark_set(buf, mark_id, row, col);
}
if (decor) {
@@ -133,18 +148,17 @@ revised:
if (idp) {
*idp = id;
}
return mark;
}
static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
{
MarkTreeIter itr[1] = { 0 };
mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr);
if (pos.row == -1) {
mtkey_t key = marktree_lookup(buf->b_marktree, mark, itr);
if (key.pos.row == -1) {
return false;
}
if (pos.row == row && pos.col == col) {
if (key.pos.row == row && key.pos.col == col) {
return true;
}
@@ -154,45 +168,35 @@ static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
// Remove an extmark
// Returns 0 on missing id
bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id)
bool extmark_del(buf_T *buf, uint32_t ns_id, uint32_t id)
{
ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
if (!ns) {
return false;
}
uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id);
if (!mark) {
return false;
}
MarkTreeIter itr[1] = { 0 };
mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr);
assert(pos.row >= 0);
mtkey_t key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
if (!key.id) {
return false;
}
assert(key.pos.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
mtpos_t pos2 = pos;
if (mark & MARKTREE_PAIRED_FLAG) {
pos2 = marktree_lookup(buf->b_marktree, mark|MARKTREE_END_FLAG, itr);
assert(pos2.row >= 0);
mtkey_t key2 = key;
if (mt_paired(key)) {
key2 = marktree_lookup_ns(buf->b_marktree, ns_id, id, true, itr);
assert(key2.pos.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
}
if (item.decor) {
decor_remove(buf, pos.row, pos2.row, item.decor);
if (marktree_decor_level(key) > kDecorLevelNone) {
decor_remove(buf, key.pos.row, key2.pos.row, key.decor_full);
}
map_del(uint64_t, uint64_t)(ns->map, id);
map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
// TODO(bfredl): delete it from current undo header, opportunistically?
return true;
}
// Free extmarks in a ns between lines
// if ns = 0, it means clear all namespaces
bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col)
bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col)
{
if (!map_size(buf->b_extmark_ns)) {
return false;
@@ -201,68 +205,58 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r
bool marks_cleared = false;
bool all_ns = (ns_id == 0);
ExtmarkNs *ns = NULL;
uint32_t *ns = NULL;
if (!all_ns) {
ns = buf_ns_ref(buf, ns_id, false);
if (!ns) {
// nothing to do
return false;
}
// TODO(bfredl): if map_size(ns->map) << buf->b_marktree.n_nodes
// it could be faster to iterate over the map instead
}
// the value is either zero or the lnum (row+1) if highlight was present.
static Map(uint64_t, ssize_t) delete_set = MAP_INIT;
typedef struct { Decoration *decor; int row1; } DecorItem;
typedef struct { int row1; } DecorItem;
static kvec_t(DecorItem) decors;
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
while (true) {
mtmark_t mark = marktree_itr_current(itr);
if (mark.row < 0
|| mark.row > u_row
|| (mark.row == u_row && mark.col > u_col)) {
mtkey_t mark = marktree_itr_current(itr);
if (mark.pos.row < 0
|| mark.pos.row > u_row
|| (mark.pos.row == u_row && mark.pos.col > u_col)) {
break;
}
ssize_t *del_status = map_ref(uint64_t, ssize_t)(&delete_set, mark.id,
ssize_t *del_status = map_ref(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark),
false);
if (del_status) {
marktree_del_itr(buf->b_marktree, itr, false);
if (*del_status >= 0) { // we had a decor_id
DecorItem it = kv_A(decors, *del_status);
decor_remove(buf, it.row1, mark.row, it.decor);
decor_remove(buf, it.row1, mark.pos.row, mark.decor_full);
}
map_del(uint64_t, ssize_t)(&delete_set, mark.id);
map_del(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark));
continue;
}
uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
start_id);
assert(item.ns_id > 0 && item.mark_id > 0);
if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) {
assert(mark.ns > 0 && mark.id > 0);
if (mark.ns == ns_id || all_ns) {
marks_cleared = true;
if (mark.id & MARKTREE_PAIRED_FLAG) {
uint64_t other = mark.id ^ MARKTREE_END_FLAG;
if (mt_paired(mark)) {
uint64_t other = mt_lookup_id(mark.ns, mark.id, !mt_end(mark));
ssize_t decor_id = -1;
if (item.decor) {
if (marktree_decor_level(mark) > kDecorLevelNone) {
// Save the decoration and the first pos. Clear the decoration
// later when we know the full range.
decor_id = (ssize_t)kv_size(decors);
kv_push(decors,
((DecorItem) { .decor = item.decor, .row1 = mark.row }));
((DecorItem) { .row1 = mark.pos.row }));
}
map_put(uint64_t, ssize_t)(&delete_set, other, decor_id);
} else if (item.decor) {
decor_remove(buf, mark.row, mark.row, item.decor);
} else if (mark.decor_full) {
decor_remove(buf, mark.pos.row, mark.pos.row, mark.decor_full);
}
ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns;
map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id);
map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, start_id);
marktree_del_itr(buf->b_marktree, itr, false);
} else {
marktree_itr_next(buf->b_marktree, itr);
@@ -271,12 +265,12 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r
uint64_t id;
ssize_t decor_id;
map_foreach(&delete_set, id, decor_id, {
mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr);
mtkey_t mark = marktree_lookup(buf->b_marktree, id, itr);
assert(itr->node);
marktree_del_itr(buf->b_marktree, itr, false);
if (decor_id >= 0) {
DecorItem it = kv_A(decors, decor_id);
decor_remove(buf, it.row1, pos.row, it.decor);
decor_remove(buf, it.row1, mark.pos.row, mark.decor_full);
}
});
map_clear(uint64_t, ssize_t)(&delete_set);
@@ -290,7 +284,7 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r
// will be searched to the start, or end
// dir can be set to control the order of the array
// amount = amount of marks to find or -1 for all
ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_row,
ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row,
colnr_T u_col, int64_t amount, bool reverse)
{
ExtmarkInfoArray array = KV_INITIAL_VALUE;
@@ -300,30 +294,24 @@ ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_co
itr, reverse, false, NULL);
int order = reverse ? -1 : 1;
while ((int64_t)kv_size(array) < amount) {
mtmark_t mark = marktree_itr_current(itr);
mtpos_t endpos = { -1, -1 };
if (mark.row < 0
|| (mark.row - u_row) * order > 0
|| (mark.row == u_row && (mark.col - u_col) * order > 0)) {
mtkey_t mark = marktree_itr_current(itr);
if (mark.pos.row < 0
|| (mark.pos.row - u_row) * order > 0
|| (mark.pos.row == u_row && (mark.pos.col - u_col) * order > 0)) {
break;
}
if (mark.id & MARKTREE_END_FLAG) {
if (mt_end(mark)) {
goto next_mark;
} else if (mark.id & MARKTREE_PAIRED_FLAG) {
endpos = marktree_lookup(buf->b_marktree, mark.id | MARKTREE_END_FLAG,
NULL);
}
ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
mark.id);
if (item.ns_id == ns_id) {
kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id,
.mark_id = item.mark_id,
.row = mark.row, .col = mark.col,
if (mark.ns == ns_id) {
mtpos_t endpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
kv_push(array, ((ExtmarkInfo) { .ns_id = mark.ns,
.mark_id = mark.id,
.row = mark.pos.row, .col = mark.pos.col,
.end_row = endpos.row,
.end_col = endpos.col,
.decor = item.decor }));
.decor = get_decor(mark) }));
}
next_mark:
if (reverse) {
@@ -336,36 +324,23 @@ next_mark:
}
// Lookup an extmark by id
ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id)
ExtmarkInfo extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id)
{
ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, NULL };
if (!ns) {
ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, DECORATION_INIT };
mtkey_t mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL);
if (!mark.id) {
return ret;
}
uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id);
if (!mark) {
return ret;
}
mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL);
mtpos_t endpos = { -1, -1 };
if (mark & MARKTREE_PAIRED_FLAG) {
endpos = marktree_lookup(buf->b_marktree, mark | MARKTREE_END_FLAG, NULL);
}
assert(pos.row >= 0);
ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
mark);
assert(mark.pos.row >= 0);
mtpos_t endpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
ret.ns_id = ns_id;
ret.mark_id = id;
ret.row = pos.row;
ret.col = pos.col;
ret.row = mark.pos.row;
ret.col = mark.pos.col;
ret.end_row = endpos.row;
ret.end_col = endpos.col;
ret.decor = item.decor;
ret.decor = get_decor(mark);
return ret;
}
@@ -378,25 +353,26 @@ void extmark_free_all(buf_T *buf)
return;
}
uint64_t id;
ExtmarkNs ns;
ExtmarkItem item;
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, 0, 0, itr);
while (true) {
mtkey_t mark = marktree_itr_current(itr);
if (mark.pos.row < 0) {
break;
}
// don't free mark.decor_full twice for a paired mark.
if (!(mt_paired(mark) && mt_end(mark))) {
decor_free(mark.decor_full);
}
marktree_itr_next(buf->b_marktree, itr);
}
marktree_clear(buf->b_marktree);
map_foreach(buf->b_extmark_ns, id, ns, {
(void)id;
map_destroy(uint64_t, uint64_t)(ns.map);
});
map_destroy(uint64_t, ExtmarkNs)(buf->b_extmark_ns);
map_init(uint64_t, ExtmarkNs, buf->b_extmark_ns);
map_foreach(buf->b_extmark_index, id, item, {
(void)id;
decor_free(item.decor);
});
map_destroy(uint64_t, ExtmarkItem)(buf->b_extmark_index);
map_init(uint64_t, ExtmarkItem, buf->b_extmark_index);
map_destroy(uint32_t, uint32_t)(buf->b_extmark_ns);
map_init(uint32_t, uint32_t, buf->b_extmark_ns);
}
@@ -437,16 +413,16 @@ void u_extmark_copy(buf_T *buf, int l_row, colnr_T l_col, int u_row, colnr_T u_c
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
while (true) {
mtmark_t mark = marktree_itr_current(itr);
if (mark.row < 0
|| mark.row > u_row
|| (mark.row == u_row && mark.col > u_col)) {
mtkey_t mark = marktree_itr_current(itr);
if (mark.pos.row < 0
|| mark.pos.row > u_row
|| (mark.pos.row == u_row && mark.pos.col > u_col)) {
break;
}
ExtmarkSavePos pos;
pos.mark = mark.id;
pos.old_row = mark.row;
pos.old_col = mark.col;
pos.mark = mt_lookup_key(mark);
pos.old_row = mark.pos.row;
pos.old_col = mark.pos.col;
pos.row = -1;
pos.col = -1;