From a871e02354ba70fd890788e9a58e6945463343fc Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Mon, 21 Jul 2025 16:36:50 -0500 Subject: [PATCH] btree: Reworked btree traversal to leverage leaf caches This comes from an observation that we never actually use the leaf cache during traversals, and there is surprisingly little risk of a lookup creating a conflict in the future. Btree traversal fall into two categories: 1. Full traversals, where we traverse a full btree all at once. These are unlikely to have lookup conflicts because everything is usually self-contained in one chunk of logic. 2. Incremental traversals. These _are_ at risk, but in our current design limited to lfs3_trv_t, which already creates a fully bshrub/btree copy for tracking purposes. This copy unintentionally, but conveniently, protects against lookup conflicts. So, why not reuse the btree leaf cache to hold the rbyd state during traversals? In theory this makes lfs3_btree_traverse the same cost and lfs3_btree_lookupnext, drops the need for lfs3_btrv_t, and simplifies the internal API. The only extra bit of state we need is the current target bid, which is now expected as a caller-incremented argument similar to lfs3_btree_lookupnext iteration. There was a bit of futzing around with bid=-1 being necessary to initialize traversal (to avoid conflicts with bid=-1 => 0 caused by empty btrees). But the end result is a btree traversal that only needs one extra word of state. --- Unfortunately, in practice, the savings were not as great as expected: code stack ctx before: 36792 2400 684 after: 36876 (+0.2%) 2384 (-0.7%) 684 (+0.0%) This does claw back some stack, but less than a full rbyd due to the union with the mtortoise in lfs3_trv_t. The mtortoise now dominates. It might be possible to union the mtortoise and the bshrub/btree state better (both are not needed at the same time), but strict aliasing rules in C make this tricky. The new lfs3_btree_traverse is also a bit more complicated in terms of code cost. In theory this would be offset by the simpler traversal setup logic, but we only actually call lfs3_btree_traverse twice: 1. In lfs3_mtree_traverse 2. In lfs3_file_ck Still, some stack savings + a simpler internal API makes this worthwhile for now. lfs3_trv_t is also due for a revisit, and hopefully it's possible to better union things with btree leaf caches somehow. --- lfs3.c | 162 +++++++++++++++++++++++------------------ lfs3.h | 9 +-- tests/test_btree.toml | 12 +-- tests/test_fwrite.toml | 60 +++++---------- 4 files changed, 115 insertions(+), 128 deletions(-) diff --git a/lfs3.c b/lfs3.c index 0235eaa7..e5979ee2 100644 --- a/lfs3.c +++ b/lfs3.c @@ -6313,49 +6313,64 @@ static lfs3_scmp_t lfs3_btree_namelookup(lfs3_t *lfs3, lfs3_btree_t *btree, // incremental btree traversal // -// note this is different from iteration, iteration should use -// lfs3_btree_lookupnext, traversal includes inner btree nodes - +// when bid is initialized to -1, incrementally traverses all btree +// nodes using the leaf rbyd to keep track of state +// +// unlike lfs3_btree_lookupnext, this includes inner btree nodes +// +// just don't call lfs3_btree_lookupnext/lookup/commit or anything else +// that uses the leaf rbyd mid-traversal or things will break! #ifndef LFS3_2BONLY -static void lfs3_btrv_init(lfs3_btrv_t *btrv) { - btrv->bid = 0; - btrv->branch = NULL; - btrv->rid = 0; -} -#endif +static lfs3_stag_t lfs3_btree_traverse(lfs3_t *lfs3, lfs3_btree_t *btree, + lfs3_sbid_t bid, + lfs3_sbid_t *bid_, lfs3_bid_t *weight_, lfs3_data_t *data_) { + // restart from the root? + if (bid == -1 || btree->leaf.rbyd.weight == 0) { + // end of traversal? + if (bid >= (lfs3_sbid_t)btree->r.weight) { + return LFS3_ERR_NOENT; + } -#ifndef LFS3_2BONLY -static lfs3_stag_t lfs3_btree_traverse(lfs3_t *lfs3, const lfs3_btree_t *btree, - lfs3_btrv_t *btrv, - lfs3_bid_t *bid_, lfs3_bid_t *weight_, lfs3_data_t *data_) { - // explicitly traverse the root even if weight=0 - if (!btrv->branch) { - btrv->branch = &btree->r; - btrv->rid = btrv->bid; + // restart from the root + btree->leaf.bid = btree->r.weight-1; + btree->leaf.rbyd = btree->r; - // traverse the root - if (btrv->bid == 0 - // unless we don't even have a root yet - && lfs3_rbyd_trunk(&btree->r) != 0 - // or are a shrub - && !lfs3_rbyd_isshrub(&btree->r)) { - if (bid_) { - *bid_ = btree->r.weight-1; + // explicitly traverse the root even if weight=0 + if (bid == -1) { + // unless we don't even have a root yet + if (lfs3_rbyd_trunk(&btree->r) != 0 + // or are a shrub + && !lfs3_rbyd_isshrub(&btree->r)) { + if (bid_) { + *bid_ = btree->r.weight-1; + } + if (weight_) { + *weight_ = btree->r.weight; + } + if (data_) { + // note we point data_ at the actual root here! this + // avoids redundant fetches if the traversal fetches + // btree nodes + data_->u.buffer = (const uint8_t*)&btree->r; + } + return LFS3_TAG_BRANCH; } - if (weight_) { - *weight_ = btree->r.weight; - } - if (data_) { - data_->u.buffer = (const uint8_t*)btrv->branch; - } - return LFS3_TAG_BRANCH; + + bid = btree->leaf.bid+1; } } - // need to restart from the root? - if (btrv->rid >= (lfs3_srid_t)btrv->branch->weight) { - btrv->branch = &btree->r; - btrv->rid = btrv->bid; + // did someone mess with the leaf rbyd? + LFS3_ASSERT(bid >= (lfs3_sbid_t)( + btree->leaf.bid-(btree->leaf.rbyd.weight-1)) + && bid <= (lfs3_sbid_t)( + btree->leaf.bid+1)); + + // the user increments bid to move the traversal forward, but if we + // were at a btree inner node we remap this to descending down the + // tree + if (bid == (lfs3_sbid_t)(btree->leaf.bid+1)) { + bid = btree->leaf.bid-(btree->leaf.rbyd.weight-1); } // descend down the tree @@ -6363,8 +6378,8 @@ static lfs3_stag_t lfs3_btree_traverse(lfs3_t *lfs3, const lfs3_btree_t *btree, lfs3_srid_t rid__; lfs3_rid_t weight__; lfs3_data_t data__; - lfs3_stag_t tag__ = lfs3_rbyd_lookupnext(lfs3, btrv->branch, - btrv->rid, 0, + lfs3_stag_t tag__ = lfs3_rbyd_lookupnext(lfs3, &btree->leaf.rbyd, + bid - (btree->leaf.bid-(btree->leaf.rbyd.weight-1)), 0, &rid__, &weight__, &data__); if (tag__ < 0) { return tag__; @@ -6372,7 +6387,8 @@ static lfs3_stag_t lfs3_btree_traverse(lfs3_t *lfs3, const lfs3_btree_t *btree, // if we found a bname, lookup the branch if (tag__ == LFS3_TAG_BNAME) { - tag__ = lfs3_rbyd_lookup(lfs3, btrv->branch, rid__, LFS3_TAG_BRANCH, + tag__ = lfs3_rbyd_lookup(lfs3, &btree->leaf.rbyd, + rid__, LFS3_TAG_BRANCH, &data__); if (tag__ < 0) { LFS3_ASSERT(tag__ != LFS3_ERR_NOENT); @@ -6380,43 +6396,49 @@ static lfs3_stag_t lfs3_btree_traverse(lfs3_t *lfs3, const lfs3_btree_t *btree, } } + // adjust bid__ with subtree's weight + lfs3_bid_t bid__ = (btree->leaf.bid-(btree->leaf.rbyd.weight-1)) + + rid__; + // found another branch if (tag__ == LFS3_TAG_BRANCH) { - // adjust rid with subtree's weight - btrv->rid -= (rid__ - (weight__-1)); - // fetch the next branch + lfs3_rbyd_t rbyd__; int err = lfs3_data_fetchbranch(lfs3, &data__, weight__, - &btrv->rbyd); + &rbyd__); if (err) { return err; } - btrv->branch = &btrv->rbyd; + btree->leaf.bid = bid__; + btree->leaf.rbyd = rbyd__; // return inner btree nodes if this is the first time we've // seen them - if (btrv->rid == 0) { + if (bid - (btree->leaf.bid-(btree->leaf.rbyd.weight-1)) == 0) { if (bid_) { - *bid_ = btrv->bid + (rid__ - btrv->rid); + *bid_ = btree->leaf.bid; } if (weight_) { - *weight_ = weight__; + *weight_ = btree->leaf.rbyd.weight; } if (data_) { - data_->u.buffer = (const uint8_t*)btrv->branch; + data_->u.buffer = (const uint8_t*)&btree->leaf.rbyd; } return LFS3_TAG_BRANCH; } // found our bid } else { - // move on to the next rid + // discard the leaf when we're done with it to restart from + // the root on the next call + if (rid__ == (lfs3_srid_t)(btree->leaf.rbyd.weight-1)) { + lfs3_btree_discardleaf(btree); + } + + // otherwise we let the user increment bid to step through + // the full leaf // - // note this effectively traverses a full leaf without redoing - // the btree walk - lfs3_bid_t bid__ = btrv->bid + (rid__ - btrv->rid); - btrv->bid = bid__ + 1; - btrv->rid = rid__ + 1; + // this + leaf caching avoids redoing the full btree walk if (bid_) { *bid_ = bid__; @@ -6783,11 +6805,10 @@ static lfs3_stag_t lfs3_bshrub_lookup(lfs3_t *lfs3, lfs3_bshrub_t *bshrub, #endif #ifndef LFS3_2BONLY -static lfs3_stag_t lfs3_bshrub_traverse(lfs3_t *lfs3, - const lfs3_bshrub_t *bshrub, - lfs3_btrv_t *btrv, - lfs3_bid_t *bid_, lfs3_bid_t *weight_, lfs3_data_t *data_) { - return lfs3_btree_traverse(lfs3, &bshrub->shrub, btrv, +static lfs3_stag_t lfs3_bshrub_traverse(lfs3_t *lfs3, lfs3_bshrub_t *bshrub, + lfs3_sbid_t bid, + lfs3_sbid_t *bid_, lfs3_bid_t *weight_, lfs3_data_t *data_) { + return lfs3_btree_traverse(lfs3, &bshrub->shrub, bid, bid_, weight_, data_); } #endif @@ -9871,7 +9892,7 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_trv_t *trv, } // transition to traversing the mtree - lfs3_btrv_init(&trv->u.btrv); + trv->u.bid = -2; lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_MTREE); continue; @@ -9927,7 +9948,7 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_trv_t *trv, // here, lfs3_bshrub_fetch ignores these for us if (err != LFS3_ERR_NOENT) { // start traversing - lfs3_btrv_init(&trv->u.btrv); + trv->u.bid = -2; lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_BTREE); continue; @@ -9972,7 +9993,7 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_trv_t *trv, // transition to traversing the file const lfs3_file_t *file = (const lfs3_file_t*)trv->htrv; trv->b.shrub = file->b.shrub; - lfs3_btrv_init(&trv->u.btrv); + trv->u.bid = -2; lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_OBTREE); continue; #endif @@ -9984,8 +10005,8 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_trv_t *trv, case LFS3_TSTATE_BTREE:; case LFS3_TSTATE_OBTREE:; // traverse through our bshrub/btree - tag = lfs3_bshrub_traverse(lfs3, &trv->b, &trv->u.btrv, - NULL, NULL, &data); + tag = lfs3_bshrub_traverse(lfs3, &trv->b, trv->u.bid+1, + &trv->u.bid, NULL, &data); if (tag < 0) { if (tag == LFS3_ERR_NOENT) { // clear the bshrub state @@ -11671,8 +11692,7 @@ static void lfs3_file_close_(lfs3_t *lfs3, const lfs3_file_t *file); static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file, const lfs3_name_t *name); #endif -static int lfs3_file_ck(lfs3_t *lfs3, const lfs3_file_t *file, - uint32_t flags); +static int lfs3_file_ck(lfs3_t *lfs3, lfs3_file_t *file, uint32_t flags); int lfs3_file_opencfg_(lfs3_t *lfs3, lfs3_file_t *file, const char *path, uint32_t flags, @@ -14085,15 +14105,13 @@ failed:; // file check functions #if !defined(LFS3_KVONLY) && !defined(LFS3_2BONLY) -static int lfs3_file_ck(lfs3_t *lfs3, const lfs3_file_t *file, - uint32_t flags) { +static int lfs3_file_ck(lfs3_t *lfs3, lfs3_file_t *file, uint32_t flags) { // traverse the file's bshrub/btree - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; while (true) { lfs3_data_t data; - lfs3_stag_t tag = lfs3_bshrub_traverse(lfs3, &file->b, &btrv, - NULL, NULL, &data); + lfs3_stag_t tag = lfs3_bshrub_traverse(lfs3, &file->b, bid+1, + &bid, NULL, &data); if (tag < 0) { if (tag == LFS3_ERR_NOENT) { break; diff --git a/lfs3.h b/lfs3.h index 5b07f333..69c30260 100644 --- a/lfs3.h +++ b/lfs3.h @@ -768,13 +768,6 @@ typedef struct lfs3_dir { lfs3_off_t pos; } lfs3_dir_t; -typedef struct lfs3_btrv { - lfs3_bid_t bid; - const lfs3_rbyd_t *branch; - lfs3_srid_t rid; - lfs3_rbyd_t rbyd; -} lfs3_btrv_t; - // littlefs traversal type typedef struct lfs3_trv { // mdir/bshrub/btree state, this also includes our traversal @@ -790,7 +783,7 @@ typedef struct lfs3_trv { uint8_t power; } mtortoise; // btree traversal state - lfs3_btrv_t btrv; + lfs3_sbid_t bid; } u; // recalculate gcksum when traversing with ckmeta diff --git a/tests/test_btree.toml b/tests/test_btree.toml index 127a607b..67fe09d5 100644 --- a/tests/test_btree.toml +++ b/tests/test_btree.toml @@ -4281,17 +4281,15 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; for (lfs3_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i <= 2*N); - lfs3_bid_t bid; lfs3_stag_t tag; lfs3_bid_t weight; lfs3_data_t data; - tag = lfs3_btree_traverse(&lfs3, &btree, &btrv, + tag = lfs3_btree_traverse(&lfs3, &btree, bid+1, &bid, &weight, &data); assert(tag >= 0 || tag == LFS3_ERR_NOENT); if (tag == LFS3_ERR_NOENT) { @@ -4439,17 +4437,15 @@ code = ''' uint8_t *seen = malloc((BLOCK_COUNT+7)/8); memset(seen, 0, (BLOCK_COUNT+7)/8); - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; for (lfs3_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i <= 2*N); - lfs3_bid_t bid; lfs3_stag_t tag; lfs3_bid_t weight; lfs3_data_t data; - tag = lfs3_btree_traverse(&lfs3, &btree, &btrv, + tag = lfs3_btree_traverse(&lfs3, &btree, bid+1, &bid, &weight, &data); assert(tag >= 0 || tag == LFS3_ERR_NOENT); if (tag == LFS3_ERR_NOENT) { diff --git a/tests/test_fwrite.toml b/tests/test_fwrite.toml index c437aa41..4999f308 100644 --- a/tests/test_fwrite.toml +++ b/tests/test_fwrite.toml @@ -183,17 +183,15 @@ code = ''' lfs3_size_t fragments = 0; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; for (lfs3_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfs3_bid_t bid; lfs3_stag_t tag; lfs3_bid_t weight; lfs3_data_t data; - tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv, + tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1, &bid, &weight, &data); assert(tag >= 0 || tag == LFS3_ERR_NOENT); if (tag == LFS3_ERR_NOENT) { @@ -331,17 +329,15 @@ code = ''' lfs3_block_t blocks = 0; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; for (lfs3_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfs3_bid_t bid; lfs3_stag_t tag; lfs3_bid_t weight; lfs3_data_t data; - tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv, + tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1, &bid, &weight, &data); assert(tag >= 0 || tag == LFS3_ERR_NOENT); if (tag == LFS3_ERR_NOENT) { @@ -597,17 +593,15 @@ code = ''' lfs3_size_t fragments = 0; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; for (lfs3_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfs3_bid_t bid; lfs3_stag_t tag; lfs3_bid_t weight; lfs3_data_t data; - tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv, + tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1, &bid, &weight, &data); assert(tag >= 0 || tag == LFS3_ERR_NOENT); if (tag == LFS3_ERR_NOENT) { @@ -760,17 +754,15 @@ code = ''' lfs3_block_t blocks = 0; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; for (lfs3_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfs3_bid_t bid; lfs3_stag_t tag; lfs3_bid_t weight; lfs3_data_t data; - tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv, + tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1, &bid, &weight, &data); assert(tag >= 0 || tag == LFS3_ERR_NOENT); if (tag == LFS3_ERR_NOENT) { @@ -2088,17 +2080,15 @@ code = ''' lfs3_size_t fragments = 0; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; for (lfs3_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfs3_bid_t bid; lfs3_stag_t tag; lfs3_bid_t weight; lfs3_data_t data; - tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv, + tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1, &bid, &weight, &data); assert(tag >= 0 || tag == LFS3_ERR_NOENT); if (tag == LFS3_ERR_NOENT) { @@ -2240,17 +2230,15 @@ code = ''' lfs3_size_t fragments = 0; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; for (lfs3_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfs3_bid_t bid; lfs3_stag_t tag; lfs3_bid_t weight; lfs3_data_t data; - tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv, + tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1, &bid, &weight, &data); assert(tag >= 0 || tag == LFS3_ERR_NOENT); if (tag == LFS3_ERR_NOENT) { @@ -2539,17 +2527,15 @@ code = ''' lfs3_size_t fragments = 0; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; for (lfs3_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfs3_bid_t bid; lfs3_stag_t tag; lfs3_bid_t weight; lfs3_data_t data; - tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv, + tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1, &bid, &weight, &data); assert(tag >= 0 || tag == LFS3_ERR_NOENT); if (tag == LFS3_ERR_NOENT) { @@ -2706,17 +2692,15 @@ code = ''' lfs3_block_t blocks = 0; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; for (lfs3_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfs3_bid_t bid; lfs3_stag_t tag; lfs3_bid_t weight; lfs3_data_t data; - tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv, + tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1, &bid, &weight, &data); assert(tag >= 0 || tag == LFS3_ERR_NOENT); if (tag == LFS3_ERR_NOENT) { @@ -2999,17 +2983,15 @@ code = ''' lfs3_size_t fragments = 0; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; for (lfs3_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfs3_bid_t bid; lfs3_stag_t tag; lfs3_bid_t weight; lfs3_data_t data; - tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv, + tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1, &bid, &weight, &data); assert(tag >= 0 || tag == LFS3_ERR_NOENT); if (tag == LFS3_ERR_NOENT) { @@ -3170,17 +3152,15 @@ code = ''' lfs3_block_t blocks = 0; lfs3_file_open(&lfs3, &file, "hello", LFS3_O_RDONLY) => 0; - lfs3_btrv_t btrv; - lfs3_btrv_init(&btrv); + lfs3_sbid_t bid = -2; for (lfs3_block_t i = 0;; i++) { // a bit hacky, but this catches infinite loops assert(i < 2*BLOCK_COUNT); - lfs3_bid_t bid; lfs3_stag_t tag; lfs3_bid_t weight; lfs3_data_t data; - tag = lfs3_bshrub_traverse(&lfs3, &file.b, &btrv, + tag = lfs3_bshrub_traverse(&lfs3, &file.b, bid+1, &bid, &weight, &data); assert(tag >= 0 || tag == LFS3_ERR_NOENT); if (tag == LFS3_ERR_NOENT) {