Implemented deferred btree inlining via cutoff parameter

This finally provides a solution for deferred B-tree inlining without
needing to evaluate attrs.

Deferred inlining is the idea that instead of inlining B-trees as soon
as the number of entries drops to either 1 or 0, we wait until a
compaction occurs to inline a B-tree. This accomplishes a few things:

1. Limits any extra reads for conditions to compaction time.

2. Avoids wasting erased bytes if we drop to 1 or 0 entries only
   temporarily.

3. Avoids excessive erase costs if we oscillate between ~1 and ~2
   entries.

Unfortunately after moving away from evaluating attrs, deferred inlining
became deceptively tricky.

In the current, non-evaluating-attr implementation, our btree commits
always lag one commit behind. When we compact, we first compact
everything currently in the rbyd, and then append any pending attr.
Never needing to evaluate the attrs removes a big chunk of logic as long
as we can assert that the largest attr set fits after compaction.

But this lagging of commits presents a problem for deferred inlining, if
we detect an inlinable tree during compaction, we can't be sure it's
_actually_ inlinable until we evaluate our attr. Which we really don't
want to do.

The solution here is to move the problem up a level. Instead of trying
to determine when to inline purely from the provided attr, we require
higher-level functions to provide this info in the form of a "cutoff".
Where, if compaction results in fewer entries than this cutoff, the
higher-level function can instead inline.

This effectively allows the higher-level functions to intercept
unnecessary compactions that can be inlined.

So far this solution seems to work quite well, with the added plus of
consolidating the corner cases around inlined/inlining btrees in these
higher-level functions.

---

Note that this has the peculiar side-effect of allowing zero-weight,
non-inlined B-trees. Our previous internal B-tree struct using the sign
of an integer to determine inline-ness, this was changed to use just the
sign-bit for the condition as a sort of ones-complement width field.

I think this sort of encoding may actually bit a tiny bit more
efficient. I was poking around with thumb code and noticed there is no
actual "abs" instruction, with gcc outputing an "it" sequence. But there
is a cheap bit-clear "bic" instruction.
This commit is contained in:
Christopher Haster
2023-04-10 14:35:06 -05:00
parent 5a5598930e
commit f35061c7eb
2 changed files with 287 additions and 183 deletions
+241 -139
View File
@@ -1602,62 +1602,6 @@ static lfs_ssize_t lfsr_rbyd_get(lfs_t *lfs, const lfsr_rbyd_t *rbyd,
return size_; return size_;
} }
static lfs_ssize_t lfsr_rbyd_bisect(lfs_t *lfs, const lfsr_rbyd_t *rbyd) {
// find the best id to split an rbyd evenly
//
// theres a few heuristics we can use here, this attempts to split to
// maintain even on-disk size across the rbyds
//
// TODO try a purely id based heuristic?
// TODO does an id based heuristic even work? we might end up overflowing
// our sibling. should look into this
// find the total on-disk size
lfsr_tag_t tag = 0;
lfs_ssize_t id = 0;
lfs_size_t dsize = 0;
while (true) {
lfs_size_t size;
int err = lfsr_rbyd_lookup(lfs, rbyd,
id, lfsr_tag_next(tag),
&id, &tag, NULL, NULL, &size);
if (err && err != LFS_ERR_NOENT) {
return err;
}
if (err == LFS_ERR_NOENT) {
break;
}
// assume worst case tag encoding so many small tags aren't missed
dsize += LFSR_TAG_DSIZE + size;
}
// traverse again to find the actual midpoint,
tag = 0;
id = 0;
lfs_size_t bsize = 0;
while (true) {
lfs_size_t size;
int err = lfsr_rbyd_lookup(lfs, rbyd,
id, lfsr_tag_next(tag),
&id, &tag, NULL, NULL, &size);
if (err) {
return err;
}
// assume worst case tag encoding so many small tags aren't missed
bsize += LFSR_TAG_DSIZE + size;
if (bsize >= dsize/2) {
// well this shouldn't happen unless attr limits have gone wrong
LFS_ASSERT((lfs_size_t)id + 1 < rbyd->weight);
// round up so that we always include at least one id in the
// first rbyd
return id + 1;
}
}
}
// TODO this should be a bd operation of some sort // TODO this should be a bd operation of some sort
static int lfsr_rbyd_prog(lfs_t *lfs, lfsr_rbyd_t *rbyd_, static int lfsr_rbyd_prog(lfs_t *lfs, lfsr_rbyd_t *rbyd_,
const void *buffer, lfs_size_t size, uint32_t *crc) { const void *buffer, lfs_size_t size, uint32_t *crc) {
@@ -2533,27 +2477,117 @@ failed:;
} }
// the following are mostly btree helpers, but since they operate on rbyds,
// exist in the rbyd namespace
static int lfsr_rbyd_cutoff(lfs_t *lfs, const lfsr_rbyd_t *rbyd,
lfs_ssize_t cutoff) {
// determine if there are fewer than "cutoff" unique ids in the rbyd,
// this is used to determine if the underlying rbyd is degenerate and can
// be reverted to an inlined btree
//
// note cutoff is expected to be quite small, <= 2, so we should make sure
// to exit our traverse early
// cutoff=-1 => no cutoff
if (cutoff < 0) {
return false;
}
// count ids until we exceed our cutoff
lfs_ssize_t id = -1;
lfs_size_t count = 0;
while (true) {
int err = lfsr_rbyd_lookup(lfs, rbyd, id+1, 0,
&id, NULL, NULL, NULL, NULL);
if (err && err != LFS_ERR_NOENT) {
return err;
}
if (err == LFS_ERR_NOENT) {
return true;
}
count += 1;
if (count > (lfs_size_t)cutoff) {
return false;
}
}
}
static lfs_ssize_t lfsr_rbyd_bisect(lfs_t *lfs, const lfsr_rbyd_t *rbyd) {
// find the best id to split an rbyd evenly
//
// theres a few heuristics we can use here, this attempts to split to
// maintain even on-disk size across the rbyds
//
// TODO try a purely id based heuristic?
// TODO does an id based heuristic even work? we might end up overflowing
// our sibling. should look into this
// find the total on-disk size
lfsr_tag_t tag = 0;
lfs_ssize_t id = 0;
lfs_size_t dsize = 0;
while (true) {
lfs_size_t size;
int err = lfsr_rbyd_lookup(lfs, rbyd,
id, lfsr_tag_next(tag),
&id, &tag, NULL, NULL, &size);
if (err && err != LFS_ERR_NOENT) {
return err;
}
if (err == LFS_ERR_NOENT) {
break;
}
// assume worst case tag encoding so many small tags aren't missed
dsize += LFSR_TAG_DSIZE + size;
}
// traverse again to find the actual midpoint,
tag = 0;
id = 0;
lfs_size_t bsize = 0;
while (true) {
lfs_size_t size;
int err = lfsr_rbyd_lookup(lfs, rbyd,
id, lfsr_tag_next(tag),
&id, &tag, NULL, NULL, &size);
if (err) {
return err;
}
// assume worst case tag encoding so many small tags aren't missed
bsize += LFSR_TAG_DSIZE + size;
if (bsize >= dsize/2) {
// well this shouldn't happen unless attr limits have gone wrong
LFS_ASSERT((lfs_size_t)id + 1 < rbyd->weight);
// round up so that we always include at least one id in the
// first rbyd
return id + 1;
}
}
}
/// Rbyd b-tree operations /// /// Rbyd b-tree operations ///
// convenience operations // convenience operations
#define LFSR_BTREE_NULL ((lfsr_btree_t){.weight=0}) #define LFSR_BTREE_NULL ((lfsr_btree_t){.weight=0x80000000})
static bool lfsr_btree_isnull(const lfsr_btree_t *btree) {
return btree->weight == 0;
}
static bool lfsr_btree_isinlined(const lfsr_btree_t *btree) { static bool lfsr_btree_isinlined(const lfsr_btree_t *btree) {
return btree->weight < 0; return btree->weight & 0x80000000;
}
static bool lfsr_btree_istree(const lfsr_btree_t *btree) {
return btree->weight > 0;
} }
static lfs_size_t lfsr_btree_weight(const lfsr_btree_t *btree) { static lfs_size_t lfsr_btree_weight(const lfsr_btree_t *btree) {
return lfs_abs32(btree->weight); return btree->weight & 0x7fffffff;
}
static lfs_size_t lfsr_btree_setinlined(lfs_size_t weight) {
return weight | 0x80000000;
} }
@@ -2644,9 +2678,6 @@ static lfs_ssize_t lfsr_btree_lookup(lfs_t *lfs,
return btree->inlined.size; return btree->inlined.size;
} }
// a this point we must be a tree
LFS_ASSERT(lfsr_btree_istree(btree));
// descend down the btree looking for our bid // descend down the btree looking for our bid
lfsr_rbyd_t branch = btree->root; lfsr_rbyd_t branch = btree->root;
lfs_ssize_t rid = bid; lfs_ssize_t rid = bid;
@@ -2842,6 +2873,7 @@ static int lfsr_btree_parent(lfs_t *lfs,
} }
// TODO do we really need this? // TODO do we really need this?
// TODO why doesn't this match lookup/get in rbyd?
static lfs_ssize_t lfsr_btree_get(lfs_t *lfs, static lfs_ssize_t lfsr_btree_get(lfs_t *lfs,
const lfsr_btree_t *btree, lfs_size_t id, const lfsr_btree_t *btree, lfs_size_t id,
lfs_size_t *id_, lfsr_tag_t *tag_, lfs_size_t *weight_, lfs_size_t *id_, lfsr_tag_t *tag_, lfs_size_t *weight_,
@@ -2858,7 +2890,7 @@ static lfs_ssize_t lfsr_btree_namelookup(lfs_t *lfs,
lfsr_tag_t *tag_, lfs_size_t *weight_, lfsr_tag_t *tag_, lfs_size_t *weight_,
void *buffer, lfs_size_t size) { void *buffer, lfs_size_t size) {
// an empty tree? // an empty tree?
if (lfsr_btree_isnull(btree)) { if (lfsr_btree_weight(btree) == 0) {
return LFS_ERR_NOENT; return LFS_ERR_NOENT;
} }
@@ -3034,11 +3066,12 @@ static lfs_ssize_t lfsr_btree_nameget(lfs_t *lfs,
// core btree algorithm // core btree algorithm
static int lfsr_btree_commit(lfs_t *lfs, static int lfsr_btree_commit(lfs_t *lfs,
lfsr_btree_t *btree, lfs_size_t bid, lfsr_rbyd_t *rbyd, lfsr_btree_t *btree, lfs_size_t bid, lfs_ssize_t cutoff,
lfsr_rbyd_t *rbyd,
lfsr_attr_t attrs[static LFSR_BTREE_SCRATCHATTRS], lfsr_attr_t attrs[static LFSR_BTREE_SCRATCHATTRS],
lfs_size_t attr_count) { lfs_size_t attr_count) {
// other layers should check for inlined btrees before this // other layers should check for inlined btrees before this
LFS_ASSERT(lfsr_btree_istree(btree)); LFS_ASSERT(!lfsr_btree_isinlined(btree));
while (true) { while (true) {
// we will always need our parent, so go ahead and find it // we will always need our parent, so go ahead and find it
@@ -3079,6 +3112,24 @@ static int lfsr_btree_commit(lfs_t *lfs,
// try to compact // try to compact
lfsr_rbyd_t rbyd_; lfsr_rbyd_t rbyd_;
if (err) { if (err) {
// first check if we are a degenerate root and can be reverted to
// an inlined btree
//
// This gets a bit weird since we're defering our pending
// attributes to after the compaction. When we can/can't be inlined
// depends on those attributes, but trying to evaluate attributes
// is complicated and expensive.
//
// Instead we just let the upper layers indicate a cutoff for when
// an rbyd can be inlined, and leave the inlining work up to the
// upper layers.
if (pid == -1) {
int degenerate = lfsr_rbyd_cutoff(lfs, rbyd, cutoff);
if (degenerate) {
return degenerate;
}
}
// TODO were we doing something funky with rev? // TODO were we doing something funky with rev?
// allocate a new rbyd // allocate a new rbyd
err = lfsr_rbyd_alloc(lfs, &rbyd_, rbyd->rev+1); err = lfsr_rbyd_alloc(lfs, &rbyd_, rbyd->rev+1);
@@ -3196,6 +3247,7 @@ static int lfsr_btree_commit(lfs_t *lfs,
} }
*rbyd = parent; *rbyd = parent;
cutoff = -1;
continue; continue;
split:; split:;
@@ -3386,6 +3438,7 @@ static int lfsr_btree_commit(lfs_t *lfs,
} }
*rbyd = parent; *rbyd = parent;
cutoff = -1;
continue; continue;
merge:; merge:;
@@ -3530,8 +3583,8 @@ static int lfsr_btree_commit(lfs_t *lfs,
// collapse our parent, decreasing the height of the tree // collapse our parent, decreasing the height of the tree
LFS_ASSERT(btree->root.block == parent.block LFS_ASSERT(btree->root.block == parent.block
&& btree->root.trunk == parent.trunk); && btree->root.trunk == parent.trunk);
btree->root = rbyd_; *rbyd = rbyd_;
return 0; break;
} else { } else {
// make pid the lower child so the following math is easier // make pid the lower child so the following math is easier
@@ -3556,12 +3609,13 @@ static int lfsr_btree_commit(lfs_t *lfs,
} }
*rbyd = parent; *rbyd = parent;
cutoff = -1;
continue; continue;
} }
// at this point rbyd should be the trunk of our tree // at this point rbyd should be the trunk of our tree
btree->root = *rbyd; btree->root = *rbyd;
return 0; return false;
} }
static int lfsr_btree_push(lfs_t *lfs, lfsr_btree_t *btree, static int lfsr_btree_push(lfs_t *lfs, lfsr_btree_t *btree,
@@ -3570,13 +3624,13 @@ static int lfsr_btree_push(lfs_t *lfs, lfsr_btree_t *btree,
LFS_ASSERT(bid <= lfsr_btree_weight(btree)); LFS_ASSERT(bid <= lfsr_btree_weight(btree));
// null btree? // null btree?
if (lfsr_btree_isnull(btree)) { if (lfsr_btree_isinlined(btree) && lfsr_btree_weight(btree) == 0) {
LFS_ASSERT(bid == 0); LFS_ASSERT(bid == 0);
btree->weight = -weight; btree->weight = lfsr_btree_setinlined(weight);
btree->inlined.tag = tag; btree->inlined.tag = tag;
LFS_ASSERT(size <= LFSR_BTREE_INLINE_SIZE); LFS_ASSERT(size <= LFSR_BTREE_INLINESIZE);
memcpy(btree->inlined.buffer, buffer, size); memcpy(btree->inlined.buffer, buffer, size);
btree->inlined.size = size; btree->inlined.size = size;
return 0; return 0;
@@ -3612,14 +3666,18 @@ static int lfsr_btree_push(lfs_t *lfs, lfsr_btree_t *btree,
// for lfsr_btree_commit operations to work out, we need to // for lfsr_btree_commit operations to work out, we need to
// limit our bid to an id in the tree, which is what this min // limit our bid to an id in the tree, which is what this min
// is doing // is doing
lfs_ssize_t bid_ = lfs_min32(bid, lfsr_btree_weight(btree)-1); //
lfsr_rbyd_t rbyd; // note it is possible for our btree to have a weight of zero here,
lfs_ssize_t rid; // since we defer inlining until compaction time
lfs_size_t rweight; lfs_size_t bid_ = lfs_min32(bid,
lfs_ssize_t size = lfsr_btree_lookup(lfs, btree, bid_, lfs_smax32(lfsr_btree_weight(btree)-1, 0));
lfsr_rbyd_t rbyd = btree->root;
lfs_ssize_t rid = -1;
lfs_size_t rweight = 0;
lfs_ssize_t rsize = lfsr_btree_lookup(lfs, btree, bid_,
NULL, &rbyd, &rid, NULL, &rweight, NULL, 0, false); NULL, &rbyd, &rid, NULL, &rweight, NULL, 0, false);
if (size < 0) { if (rsize < 0 && rsize != LFS_ERR_NOENT) {
return size; return rsize;
} }
// adjust rid for push // adjust rid for push
@@ -3631,8 +3689,26 @@ static int lfsr_btree_push(lfs_t *lfs, lfsr_btree_t *btree,
// commit our id into the tree, letting lfsr_btree_commit take care // commit our id into the tree, letting lfsr_btree_commit take care
// of the rest // of the rest
return lfsr_btree_commit(lfs, btree, bid_, &rbyd, LFSR_BTREE_ATTRS( int degenerate = lfsr_btree_commit(lfs, btree, bid_, 0, &rbyd,
LFSR_ATTR_(rid, lfsr_tag_setmk(tag), +weight, buffer, size))); LFSR_BTREE_ATTRS(
LFSR_ATTR_(rid, lfsr_tag_setmk(tag), +weight,
buffer, size)));
if (degenerate < 0) {
return degenerate;
}
// revert to an inlined btree
if (degenerate) {
btree->weight = lfsr_btree_setinlined(weight);
btree->inlined.tag = tag;
LFS_ASSERT(size <= LFSR_BTREE_INLINESIZE);
memcpy(btree->inlined.buffer, buffer, size);
btree->inlined.size = size;
return 0;
}
return 0;
} }
} }
@@ -3640,15 +3716,16 @@ static int lfsr_btree_update(lfs_t *lfs, lfsr_btree_t *btree,
lfs_size_t bid, lfsr_tag_t tag, lfs_size_t weight, lfs_size_t bid, lfsr_tag_t tag, lfs_size_t weight,
const void *buffer, lfs_size_t size) { const void *buffer, lfs_size_t size) {
LFS_ASSERT(bid < lfsr_btree_weight(btree)); LFS_ASSERT(bid < lfsr_btree_weight(btree));
LFS_ASSERT(lfsr_btree_weight(btree) > 0);
// inlined btree? // inlined btree?
if (lfsr_btree_isinlined(btree)) { if (lfsr_btree_isinlined(btree)) {
LFS_ASSERT(bid == lfsr_btree_weight(btree)-1); LFS_ASSERT(bid == lfsr_btree_weight(btree)-1);
btree->weight = -weight; btree->weight = lfsr_btree_setinlined(weight);
btree->inlined.tag = tag; btree->inlined.tag = tag;
LFS_ASSERT(size <= LFSR_BTREE_INLINE_SIZE); LFS_ASSERT(size <= LFSR_BTREE_INLINESIZE);
memcpy(btree->inlined.buffer, buffer, size); memcpy(btree->inlined.buffer, buffer, size);
btree->inlined.size = size; btree->inlined.size = size;
return 0; return 0;
@@ -3660,30 +3737,47 @@ static int lfsr_btree_update(lfs_t *lfs, lfsr_btree_t *btree,
lfsr_tag_t rtag; lfsr_tag_t rtag;
lfs_ssize_t rid; lfs_ssize_t rid;
lfs_size_t rweight; lfs_size_t rweight;
lfs_ssize_t size = lfsr_btree_lookup(lfs, btree, bid, lfs_ssize_t rsize = lfsr_btree_lookup(lfs, btree, bid,
NULL, &rbyd, &rid, &rtag, &rweight, NULL, 0, false); NULL, &rbyd, &rid, &rtag, &rweight, NULL, 0, false);
if (size < 0) { if (rsize < 0) {
return size; return rsize;
} }
// commit our id into the tree, letting lfsr_btree_commit take care // commit our id into the tree, letting lfsr_btree_commit take care
// of the rest // of the rest
return lfsr_btree_commit(lfs, btree, bid, &rbyd, LFSR_BTREE_ATTRS( int degenerate = lfsr_btree_commit(lfs, btree, bid, 1, &rbyd,
LFSR_BTREE_ATTRS(
(tag != rtag (tag != rtag
? LFSR_ATTR_(rid, lfsr_tag_setrm(rtag), 0, NULL, 0) ? LFSR_ATTR_(rid, lfsr_tag_setrm(rtag), 0, NULL, 0)
: LFSR_ATTR_NOOP), : LFSR_ATTR_NOOP),
LFSR_ATTR_(rid, tag, 0, buffer, size), LFSR_ATTR_(rid, tag, 0, buffer, size),
LFSR_ATTR(rid, UNR, +weight-rweight, NULL, 0))); LFSR_ATTR(rid, UNR, +weight-rweight, NULL, 0)));
if (degenerate < 0) {
return degenerate;
}
// revert to an inlined btree
if (degenerate) {
btree->weight = lfsr_btree_setinlined(weight);
btree->inlined.tag = tag;
LFS_ASSERT(size <= LFSR_BTREE_INLINESIZE);
memcpy(btree->inlined.buffer, buffer, size);
btree->inlined.size = size;
}
return 0;
} }
} }
static int lfsr_btree_pop(lfs_t *lfs, lfsr_btree_t *btree, lfs_size_t bid) { static int lfsr_btree_pop(lfs_t *lfs, lfsr_btree_t *btree, lfs_size_t bid) {
LFS_ASSERT(bid < lfsr_btree_weight(btree)); LFS_ASSERT(bid < lfsr_btree_weight(btree));
LFS_ASSERT(lfsr_btree_weight(btree) > 0);
// inlined btree? // inlined btree?
if (lfsr_btree_isinlined(btree)) { if (lfsr_btree_isinlined(btree)) {
LFS_ASSERT(bid == lfsr_btree_weight(btree)-1); LFS_ASSERT(bid == lfsr_btree_weight(btree)-1);
btree->weight = 0; btree->weight = lfsr_btree_setinlined(0);
return 0; return 0;
// a normal btree // a normal btree
@@ -3693,60 +3787,60 @@ static int lfsr_btree_pop(lfs_t *lfs, lfsr_btree_t *btree, lfs_size_t bid) {
lfsr_tag_t rtag; lfsr_tag_t rtag;
lfs_ssize_t rid; lfs_ssize_t rid;
lfs_size_t rweight; lfs_size_t rweight;
lfs_ssize_t size = lfsr_btree_lookup(lfs, btree, bid, lfs_ssize_t rsize = lfsr_btree_lookup(lfs, btree, bid,
NULL, &rbyd, &rid, &rtag, &rweight, NULL, 0, false); NULL, &rbyd, &rid, &rtag, &rweight, NULL, 0, false);
if (size < 0) { if (rsize < 0) {
return size; return rsize;
} }
// can we collapse into an inlined btree? // remove our id, letting lfsr_btree_commit take care
if (btree->root.block == rbyd.block // of the rest
&& btree->root.trunk == rbyd.trunk) { //
// last child? try the left sibling // note we use a cutoff of 2 here, if we have 2 entries before
// the commit, we should have 1 entry after the commit and can
// revert to an inlined btree
int degenerate = lfsr_btree_commit(lfs, btree, bid, 2, &rbyd,
LFSR_BTREE_ATTRS(
LFSR_ATTR(rid, MKUNR, -rweight, NULL, 0)));
if (degenerate < 0) {
return degenerate;
}
// revert to an inlined btree
if (degenerate && rweight < rbyd.weight) {
lfs_ssize_t sid; lfs_ssize_t sid;
// left sibling
if ((lfs_size_t)rid == rbyd.weight-1) { if ((lfs_size_t)rid == rbyd.weight-1) {
sid = rid-rweight; sid = rid-rweight;
// not last child? try the right sibling // right sibling
} else { } else {
sid = rid+1; sid = rid+1;
} }
// try looking up the sibling
lfs_size_t sweight;
int err = lfsr_rbyd_lookup(lfs, &rbyd, sid, LFSR_TAG_NAME,
&sid, NULL, &sweight, NULL, NULL);
if (err && err != LFS_ERR_NOENT) {
return err;
}
// no sibling? null btree
if (err == LFS_ERR_NOENT) {
btree->weight = 0;
return 0;
}
lfsr_tag_t stag; lfsr_tag_t stag;
lfs_size_t sweight;
lfs_off_t off; lfs_off_t off;
lfs_size_t size; lfs_size_t size;
err = lfsr_rbyd_lookup(lfs, &rbyd, sid, LFSR_TAG_STRUCT, int err = lfsr_rbyd_lookup(lfs, &rbyd, sid, LFSR_TAG_NAME,
NULL, &stag, NULL, &off, &size); &sid, &stag, &sweight, &off, &size);
if (err && err != LFS_ERR_NOENT) { if (err) {
return err; return err;
} }
// no sibling? null btree if (lfsr_tag_suptype(stag) == LFSR_TAG_NAME) {
if (err == LFS_ERR_NOENT // TODO what if we don't find a struct? ENOENT?
|| lfsr_tag_suptype(stag) != LFSR_TAG_STRUCT) { err = lfsr_rbyd_lookup(lfs, &rbyd, sid, LFSR_TAG_STRUCT,
btree->weight = 0; NULL, &stag, NULL, &off, &size);
return 0; if (err) {
return err;
}
} }
// one sibling? inline! LFS_ASSERT(sweight+rweight == rbyd.weight);
if (rweight + sweight == lfsr_btree_weight(btree)) { btree->weight = lfsr_btree_setinlined(sweight);
btree->weight = -sweight;
btree->inlined.tag = stag; btree->inlined.tag = stag;
LFS_ASSERT(size <= LFSR_BTREE_INLINE_SIZE); LFS_ASSERT(size <= LFSR_BTREE_INLINESIZE);
err = lfs_bd_read(lfs, err = lfs_bd_read(lfs,
&lfs->pcache, &lfs->rcache, size, &lfs->pcache, &lfs->rcache, size,
rbyd.block, off, btree->inlined.buffer, size); rbyd.block, off, btree->inlined.buffer, size);
@@ -3754,14 +3848,13 @@ static int lfsr_btree_pop(lfs_t *lfs, lfsr_btree_t *btree, lfs_size_t bid) {
return err; return err;
} }
btree->inlined.size = size; btree->inlined.size = size;
return 0;
} // revert to a null btree
} else if (degenerate) {
btree->weight = lfsr_btree_setinlined(0);
} }
// remove our id, letting lfsr_btree_commit take care return 0;
// of the rest
return lfsr_btree_commit(lfs, btree, bid, &rbyd, LFSR_BTREE_ATTRS(
LFSR_ATTR(rid, MKUNR, -rweight, NULL, 0)));
} }
} }
@@ -3779,6 +3872,7 @@ static int lfsr_btree_split(lfs_t *lfs, lfsr_btree_t *btree,
lfsr_tag_t tag2, lfs_size_t weight2, lfsr_tag_t tag2, lfs_size_t weight2,
const void *buffer2, lfs_size_t size2) { const void *buffer2, lfs_size_t size2) {
LFS_ASSERT(bid < lfsr_btree_weight(btree)); LFS_ASSERT(bid < lfsr_btree_weight(btree));
LFS_ASSERT(lfsr_btree_weight(btree) > 0);
// inlined btree, need to expand into an rbyd // inlined btree, need to expand into an rbyd
if (lfsr_btree_isinlined(btree)) { if (lfsr_btree_isinlined(btree)) {
@@ -3809,15 +3903,16 @@ static int lfsr_btree_split(lfs_t *lfs, lfsr_btree_t *btree,
lfsr_rbyd_t rbyd; lfsr_rbyd_t rbyd;
lfs_ssize_t rid; lfs_ssize_t rid;
lfs_size_t rweight; lfs_size_t rweight;
lfs_ssize_t size = lfsr_btree_lookup(lfs, btree, bid, lfs_ssize_t rsize = lfsr_btree_lookup(lfs, btree, bid,
NULL, &rbyd, &rid, NULL, &rweight, NULL, 0, false); NULL, &rbyd, &rid, NULL, &rweight, NULL, 0, false);
if (size < 0) { if (rsize < 0) {
return size; return rsize;
} }
// commit our bid into the tree, letting lfsr_btree_commit take care // commit our bid into the tree, letting lfsr_btree_commit take care
// of the rest // of the rest
return lfsr_btree_commit(lfs, btree, bid, &rbyd, LFSR_BTREE_ATTRS( int degenerate = lfsr_btree_commit(lfs, btree, bid, -1, &rbyd,
LFSR_BTREE_ATTRS(
LFSR_ATTR(rid, UNR, +weight1-rweight, NULL, 0), LFSR_ATTR(rid, UNR, +weight1-rweight, NULL, 0),
LFSR_ATTR_(rid-(rweight-1)+weight1-1, tag1, 0, LFSR_ATTR_(rid-(rweight-1)+weight1-1, tag1, 0,
buffer1, size1), buffer1, size1),
@@ -3826,6 +3921,13 @@ static int lfsr_btree_split(lfs_t *lfs, lfsr_btree_t *btree,
name, name_size), name, name_size),
LFSR_ATTR_(rid-(rweight-1)+weight1+weight2-1, tag2, 0, LFSR_ATTR_(rid-(rweight-1)+weight1+weight2-1, tag2, 0,
buffer2, size2))); buffer2, size2)));
if (degenerate < 0) {
return degenerate;
}
// this should never happen
LFS_ASSERT(!degenerate);
return 0;
} }
} }
+9 -7
View File
@@ -360,21 +360,23 @@ typedef struct lfsr_rbyd {
// //
// Pointers we store: // Pointers we store:
// - block addresses => 1 leb128 => 5 bytes (worst case) // - block addresses => 1 leb128 => 5 bytes (worst case)
#define LFSR_BTREE_INLINE_SIZE 5 #define LFSR_BTREE_INLINESIZE 5
typedef union lfsr_btree { typedef union lfsr_btree {
// note this lines up with weight in lfsr_rbyd_t // note this lines up with weight in lfsr_rbyd_t
// //
// weight=0 => null btree // sign(weight)=1 => inlined btree
// weight<0 => inlined btree // sign(weight)=0 => normal btree
// weight>0 => normal btree //
lfs_ssize_t weight; // note due to defered inlining, both normal and inlined
// btrees can have a weight of 0
lfs_size_t weight;
lfsr_rbyd_t root; lfsr_rbyd_t root;
struct { struct {
lfs_ssize_t weight; lfs_ssize_t weight;
lfsr_tag_t tag; lfsr_tag_t tag;
uint16_t size; uint16_t size;
uint8_t buffer[LFSR_BTREE_INLINE_SIZE]; uint8_t buffer[LFSR_BTREE_INLINESIZE];
} inlined; } inlined;
} lfsr_btree_t; } lfsr_btree_t;
@@ -391,7 +393,7 @@ typedef union lfsr_btree {
// union { // union {
// struct { // struct {
// uint8_t size; // uint8_t size;
// uint8_t buffer[LFSR_BTREE_INLINE_SIZE]; // uint8_t buffer[LFSR_BTREE_INLINESIZE];
// } inlined; // } inlined;
// //
// // if we're not inlined, point to the trunk rbyd block of the btree // // if we're not inlined, point to the trunk rbyd block of the btree