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:
@@ -1602,62 +1602,6 @@ static lfs_ssize_t lfsr_rbyd_get(lfs_t *lfs, const lfsr_rbyd_t *rbyd,
|
||||
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
|
||||
static int lfsr_rbyd_prog(lfs_t *lfs, lfsr_rbyd_t *rbyd_,
|
||||
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 ///
|
||||
|
||||
|
||||
// convenience operations
|
||||
|
||||
#define LFSR_BTREE_NULL ((lfsr_btree_t){.weight=0})
|
||||
|
||||
static bool lfsr_btree_isnull(const lfsr_btree_t *btree) {
|
||||
return btree->weight == 0;
|
||||
}
|
||||
#define LFSR_BTREE_NULL ((lfsr_btree_t){.weight=0x80000000})
|
||||
|
||||
static bool lfsr_btree_isinlined(const lfsr_btree_t *btree) {
|
||||
return btree->weight < 0;
|
||||
}
|
||||
|
||||
static bool lfsr_btree_istree(const lfsr_btree_t *btree) {
|
||||
return btree->weight > 0;
|
||||
return btree->weight & 0x80000000;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// a this point we must be a tree
|
||||
LFS_ASSERT(lfsr_btree_istree(btree));
|
||||
|
||||
// descend down the btree looking for our bid
|
||||
lfsr_rbyd_t branch = btree->root;
|
||||
lfs_ssize_t rid = bid;
|
||||
@@ -2842,6 +2873,7 @@ static int lfsr_btree_parent(lfs_t *lfs,
|
||||
}
|
||||
|
||||
// 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,
|
||||
const lfsr_btree_t *btree, lfs_size_t id,
|
||||
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_,
|
||||
void *buffer, lfs_size_t size) {
|
||||
// an empty tree?
|
||||
if (lfsr_btree_isnull(btree)) {
|
||||
if (lfsr_btree_weight(btree) == 0) {
|
||||
return LFS_ERR_NOENT;
|
||||
}
|
||||
|
||||
@@ -3034,11 +3066,12 @@ static lfs_ssize_t lfsr_btree_nameget(lfs_t *lfs,
|
||||
|
||||
// core btree algorithm
|
||||
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],
|
||||
lfs_size_t attr_count) {
|
||||
// other layers should check for inlined btrees before this
|
||||
LFS_ASSERT(lfsr_btree_istree(btree));
|
||||
LFS_ASSERT(!lfsr_btree_isinlined(btree));
|
||||
|
||||
while (true) {
|
||||
// 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
|
||||
lfsr_rbyd_t rbyd_;
|
||||
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?
|
||||
// allocate a new rbyd
|
||||
err = lfsr_rbyd_alloc(lfs, &rbyd_, rbyd->rev+1);
|
||||
@@ -3196,6 +3247,7 @@ static int lfsr_btree_commit(lfs_t *lfs,
|
||||
}
|
||||
|
||||
*rbyd = parent;
|
||||
cutoff = -1;
|
||||
continue;
|
||||
|
||||
split:;
|
||||
@@ -3386,6 +3438,7 @@ static int lfsr_btree_commit(lfs_t *lfs,
|
||||
}
|
||||
|
||||
*rbyd = parent;
|
||||
cutoff = -1;
|
||||
continue;
|
||||
|
||||
merge:;
|
||||
@@ -3530,8 +3583,8 @@ static int lfsr_btree_commit(lfs_t *lfs,
|
||||
// collapse our parent, decreasing the height of the tree
|
||||
LFS_ASSERT(btree->root.block == parent.block
|
||||
&& btree->root.trunk == parent.trunk);
|
||||
btree->root = rbyd_;
|
||||
return 0;
|
||||
*rbyd = rbyd_;
|
||||
break;
|
||||
|
||||
} else {
|
||||
// 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;
|
||||
cutoff = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// at this point rbyd should be the trunk of our tree
|
||||
btree->root = *rbyd;
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// null btree?
|
||||
if (lfsr_btree_isnull(btree)) {
|
||||
if (lfsr_btree_isinlined(btree) && lfsr_btree_weight(btree) == 0) {
|
||||
LFS_ASSERT(bid == 0);
|
||||
|
||||
btree->weight = -weight;
|
||||
btree->weight = lfsr_btree_setinlined(weight);
|
||||
btree->inlined.tag = tag;
|
||||
|
||||
LFS_ASSERT(size <= LFSR_BTREE_INLINE_SIZE);
|
||||
LFS_ASSERT(size <= LFSR_BTREE_INLINESIZE);
|
||||
memcpy(btree->inlined.buffer, buffer, size);
|
||||
btree->inlined.size = size;
|
||||
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
|
||||
// limit our bid to an id in the tree, which is what this min
|
||||
// is doing
|
||||
lfs_ssize_t bid_ = lfs_min32(bid, lfsr_btree_weight(btree)-1);
|
||||
lfsr_rbyd_t rbyd;
|
||||
lfs_ssize_t rid;
|
||||
lfs_size_t rweight;
|
||||
lfs_ssize_t size = lfsr_btree_lookup(lfs, btree, bid_,
|
||||
//
|
||||
// note it is possible for our btree to have a weight of zero here,
|
||||
// since we defer inlining until compaction time
|
||||
lfs_size_t bid_ = lfs_min32(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);
|
||||
if (size < 0) {
|
||||
return size;
|
||||
if (rsize < 0 && rsize != LFS_ERR_NOENT) {
|
||||
return rsize;
|
||||
}
|
||||
|
||||
// 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
|
||||
// of the rest
|
||||
return lfsr_btree_commit(lfs, btree, bid_, &rbyd, LFSR_BTREE_ATTRS(
|
||||
LFSR_ATTR_(rid, lfsr_tag_setmk(tag), +weight, buffer, size)));
|
||||
int degenerate = lfsr_btree_commit(lfs, btree, bid_, 0, &rbyd,
|
||||
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,
|
||||
const void *buffer, lfs_size_t size) {
|
||||
LFS_ASSERT(bid < lfsr_btree_weight(btree));
|
||||
LFS_ASSERT(lfsr_btree_weight(btree) > 0);
|
||||
|
||||
// inlined btree?
|
||||
if (lfsr_btree_isinlined(btree)) {
|
||||
LFS_ASSERT(bid == lfsr_btree_weight(btree)-1);
|
||||
|
||||
btree->weight = -weight;
|
||||
btree->weight = lfsr_btree_setinlined(weight);
|
||||
btree->inlined.tag = tag;
|
||||
|
||||
LFS_ASSERT(size <= LFSR_BTREE_INLINE_SIZE);
|
||||
LFS_ASSERT(size <= LFSR_BTREE_INLINESIZE);
|
||||
memcpy(btree->inlined.buffer, buffer, size);
|
||||
btree->inlined.size = size;
|
||||
return 0;
|
||||
@@ -3660,30 +3737,47 @@ static int lfsr_btree_update(lfs_t *lfs, lfsr_btree_t *btree,
|
||||
lfsr_tag_t rtag;
|
||||
lfs_ssize_t rid;
|
||||
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);
|
||||
if (size < 0) {
|
||||
return size;
|
||||
if (rsize < 0) {
|
||||
return rsize;
|
||||
}
|
||||
|
||||
// commit our id into the tree, letting lfsr_btree_commit take care
|
||||
// of the rest
|
||||
return lfsr_btree_commit(lfs, btree, bid, &rbyd, LFSR_BTREE_ATTRS(
|
||||
(tag != rtag
|
||||
? LFSR_ATTR_(rid, lfsr_tag_setrm(rtag), 0, NULL, 0)
|
||||
: LFSR_ATTR_NOOP),
|
||||
LFSR_ATTR_(rid, tag, 0, buffer, size),
|
||||
LFSR_ATTR(rid, UNR, +weight-rweight, NULL, 0)));
|
||||
int degenerate = lfsr_btree_commit(lfs, btree, bid, 1, &rbyd,
|
||||
LFSR_BTREE_ATTRS(
|
||||
(tag != rtag
|
||||
? LFSR_ATTR_(rid, lfsr_tag_setrm(rtag), 0, NULL, 0)
|
||||
: LFSR_ATTR_NOOP),
|
||||
LFSR_ATTR_(rid, tag, 0, buffer, size),
|
||||
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) {
|
||||
LFS_ASSERT(bid < lfsr_btree_weight(btree));
|
||||
LFS_ASSERT(lfsr_btree_weight(btree) > 0);
|
||||
|
||||
// inlined btree?
|
||||
if (lfsr_btree_isinlined(btree)) {
|
||||
LFS_ASSERT(bid == lfsr_btree_weight(btree)-1);
|
||||
btree->weight = 0;
|
||||
btree->weight = lfsr_btree_setinlined(0);
|
||||
return 0;
|
||||
|
||||
// a normal btree
|
||||
@@ -3693,75 +3787,74 @@ static int lfsr_btree_pop(lfs_t *lfs, lfsr_btree_t *btree, lfs_size_t bid) {
|
||||
lfsr_tag_t rtag;
|
||||
lfs_ssize_t rid;
|
||||
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);
|
||||
if (size < 0) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// can we collapse into an inlined btree?
|
||||
if (btree->root.block == rbyd.block
|
||||
&& btree->root.trunk == rbyd.trunk) {
|
||||
// last child? try the left sibling
|
||||
lfs_ssize_t sid;
|
||||
if ((lfs_size_t)rid == rbyd.weight-1) {
|
||||
sid = rid-rweight;
|
||||
// not last child? try the right sibling
|
||||
} else {
|
||||
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;
|
||||
lfs_off_t off;
|
||||
lfs_size_t size;
|
||||
err = lfsr_rbyd_lookup(lfs, &rbyd, sid, LFSR_TAG_STRUCT,
|
||||
NULL, &stag, NULL, &off, &size);
|
||||
if (err && err != LFS_ERR_NOENT) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// no sibling? null btree
|
||||
if (err == LFS_ERR_NOENT
|
||||
|| lfsr_tag_suptype(stag) != LFSR_TAG_STRUCT) {
|
||||
btree->weight = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// one sibling? inline!
|
||||
if (rweight + sweight == lfsr_btree_weight(btree)) {
|
||||
btree->weight = -sweight;
|
||||
btree->inlined.tag = stag;
|
||||
|
||||
LFS_ASSERT(size <= LFSR_BTREE_INLINE_SIZE);
|
||||
err = lfs_bd_read(lfs,
|
||||
&lfs->pcache, &lfs->rcache, size,
|
||||
rbyd.block, off, btree->inlined.buffer, size);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
btree->inlined.size = size;
|
||||
return 0;
|
||||
}
|
||||
if (rsize < 0) {
|
||||
return rsize;
|
||||
}
|
||||
|
||||
// remove our id, letting lfsr_btree_commit take care
|
||||
// of the rest
|
||||
return lfsr_btree_commit(lfs, btree, bid, &rbyd, LFSR_BTREE_ATTRS(
|
||||
LFSR_ATTR(rid, MKUNR, -rweight, NULL, 0)));
|
||||
//
|
||||
// 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;
|
||||
// left sibling
|
||||
if ((lfs_size_t)rid == rbyd.weight-1) {
|
||||
sid = rid-rweight;
|
||||
// right sibling
|
||||
} else {
|
||||
sid = rid+1;
|
||||
}
|
||||
|
||||
lfsr_tag_t stag;
|
||||
lfs_size_t sweight;
|
||||
lfs_off_t off;
|
||||
lfs_size_t size;
|
||||
int err = lfsr_rbyd_lookup(lfs, &rbyd, sid, LFSR_TAG_NAME,
|
||||
&sid, &stag, &sweight, &off, &size);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (lfsr_tag_suptype(stag) == LFSR_TAG_NAME) {
|
||||
// TODO what if we don't find a struct? ENOENT?
|
||||
err = lfsr_rbyd_lookup(lfs, &rbyd, sid, LFSR_TAG_STRUCT,
|
||||
NULL, &stag, NULL, &off, &size);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
LFS_ASSERT(sweight+rweight == rbyd.weight);
|
||||
btree->weight = lfsr_btree_setinlined(sweight);
|
||||
btree->inlined.tag = stag;
|
||||
|
||||
LFS_ASSERT(size <= LFSR_BTREE_INLINESIZE);
|
||||
err = lfs_bd_read(lfs,
|
||||
&lfs->pcache, &lfs->rcache, size,
|
||||
rbyd.block, off, btree->inlined.buffer, size);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
btree->inlined.size = size;
|
||||
|
||||
// revert to a null btree
|
||||
} else if (degenerate) {
|
||||
btree->weight = lfsr_btree_setinlined(0);
|
||||
}
|
||||
|
||||
return 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,
|
||||
const void *buffer2, lfs_size_t size2) {
|
||||
LFS_ASSERT(bid < lfsr_btree_weight(btree));
|
||||
LFS_ASSERT(lfsr_btree_weight(btree) > 0);
|
||||
|
||||
// inlined btree, need to expand into an rbyd
|
||||
if (lfsr_btree_isinlined(btree)) {
|
||||
@@ -3809,23 +3903,31 @@ static int lfsr_btree_split(lfs_t *lfs, lfsr_btree_t *btree,
|
||||
lfsr_rbyd_t rbyd;
|
||||
lfs_ssize_t rid;
|
||||
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);
|
||||
if (size < 0) {
|
||||
return size;
|
||||
if (rsize < 0) {
|
||||
return rsize;
|
||||
}
|
||||
|
||||
// commit our bid into the tree, letting lfsr_btree_commit take care
|
||||
// of the rest
|
||||
return lfsr_btree_commit(lfs, btree, bid, &rbyd, LFSR_BTREE_ATTRS(
|
||||
LFSR_ATTR(rid, UNR, +weight1-rweight, NULL, 0),
|
||||
LFSR_ATTR_(rid-(rweight-1)+weight1-1, tag1, 0,
|
||||
buffer1, size1),
|
||||
// TODO should we always be making name entries?
|
||||
LFSR_ATTR(rid-(rweight-1)+weight1, MKBNAME, +weight2,
|
||||
name, name_size),
|
||||
LFSR_ATTR_(rid-(rweight-1)+weight1+weight2-1, tag2, 0,
|
||||
buffer2, size2)));
|
||||
int degenerate = lfsr_btree_commit(lfs, btree, bid, -1, &rbyd,
|
||||
LFSR_BTREE_ATTRS(
|
||||
LFSR_ATTR(rid, UNR, +weight1-rweight, NULL, 0),
|
||||
LFSR_ATTR_(rid-(rweight-1)+weight1-1, tag1, 0,
|
||||
buffer1, size1),
|
||||
// TODO should we always be making name entries?
|
||||
LFSR_ATTR(rid-(rweight-1)+weight1, MKBNAME, +weight2,
|
||||
name, name_size),
|
||||
LFSR_ATTR_(rid-(rweight-1)+weight1+weight2-1, tag2, 0,
|
||||
buffer2, size2)));
|
||||
if (degenerate < 0) {
|
||||
return degenerate;
|
||||
}
|
||||
|
||||
// this should never happen
|
||||
LFS_ASSERT(!degenerate);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -360,21 +360,23 @@ typedef struct lfsr_rbyd {
|
||||
//
|
||||
// Pointers we store:
|
||||
// - block addresses => 1 leb128 => 5 bytes (worst case)
|
||||
#define LFSR_BTREE_INLINE_SIZE 5
|
||||
#define LFSR_BTREE_INLINESIZE 5
|
||||
|
||||
typedef union lfsr_btree {
|
||||
// note this lines up with weight in lfsr_rbyd_t
|
||||
//
|
||||
// weight=0 => null btree
|
||||
// weight<0 => inlined btree
|
||||
// weight>0 => normal btree
|
||||
lfs_ssize_t weight;
|
||||
// sign(weight)=1 => inlined btree
|
||||
// sign(weight)=0 => normal btree
|
||||
//
|
||||
// note due to defered inlining, both normal and inlined
|
||||
// btrees can have a weight of 0
|
||||
lfs_size_t weight;
|
||||
lfsr_rbyd_t root;
|
||||
struct {
|
||||
lfs_ssize_t weight;
|
||||
lfsr_tag_t tag;
|
||||
uint16_t size;
|
||||
uint8_t buffer[LFSR_BTREE_INLINE_SIZE];
|
||||
uint8_t buffer[LFSR_BTREE_INLINESIZE];
|
||||
} inlined;
|
||||
} lfsr_btree_t;
|
||||
|
||||
@@ -391,7 +393,7 @@ typedef union lfsr_btree {
|
||||
// union {
|
||||
// struct {
|
||||
// uint8_t size;
|
||||
// uint8_t buffer[LFSR_BTREE_INLINE_SIZE];
|
||||
// uint8_t buffer[LFSR_BTREE_INLINESIZE];
|
||||
// } inlined;
|
||||
//
|
||||
// // if we're not inlined, point to the trunk rbyd block of the btree
|
||||
|
||||
Reference in New Issue
Block a user