diff --git a/lfs3.c b/lfs3.c index 035dedb7..5e9d2fe9 100644 --- a/lfs3.c +++ b/lfs3.c @@ -5588,9 +5588,9 @@ static int lfs3_btree_commit_(lfs3_t *lfs3, // are we root? } else if (child.blocks[0] == btree->r.blocks[0]) { - // mark btree as unerased in case of failure, our btree rbyd and + // mark btree as unfetched in case of failure, our btree rbyd and // root rbyd can diverge if there's a split, but we would have - // marked the old root as unerased earlier anyways + // marked the old root as unfetched earlier anyways lfs3_btree_claim(btree); // need to lookup child's parent @@ -6920,15 +6920,19 @@ static int lfs3_bshrub_commitroot_(lfs3_t *lfs3, lfs3_bshrub_t *bshrub, static int lfs3_bshrub_commit(lfs3_t *lfs3, lfs3_bshrub_t *bshrub, lfs3_bid_t bid, const lfs3_rattr_t *rattrs, lfs3_size_t rattr_count) { #ifndef LFS3_2BONLY + // TODO why are we marking bshrubs as unfetched here? we should either + // be marking all btrees in lfs3_btree_commit, or move this to + // lfs3_file_commit for finer control... + // // before we touch anything, we need to mark all other btree references - // as unerased + // as unfetched if (lfs3_bshrub_isbtree(bshrub)) { for (lfs3_handle_t *h = lfs3->handles; h; h = h->next) { if (lfs3_o_isbshrub(h->flags) && h != &bshrub->h && ((lfs3_bshrub_t*)h)->shrub.r.blocks[0] == bshrub->shrub.r.blocks[0]) { - // mark as unerased + // mark as unfetched lfs3_btree_claim(&((lfs3_bshrub_t*)h)->shrub); } } @@ -9282,7 +9286,7 @@ static int lfs3_mdir_commit(lfs3_t *lfs3, lfs3_mdir_t *mdir, // update our mtree } else { - // mark as unerased in case of failure + // mark as unfetched in case of failure lfs3_btree_claim(&lfs3->mtree); err = lfs3_mtree_commit(lfs3, &mtree_, @@ -9321,7 +9325,7 @@ static int lfs3_mdir_commit(lfs3_t *lfs3, lfs3_mdir_t *mdir, // how can we drop if we have no mtree? LFS3_ASSERT(lfs3->mtree.r.weight != 0); - // mark as unerased in case of failure + // mark as unfetched in case of failure lfs3_btree_claim(&lfs3->mtree); // update our mtree @@ -9358,7 +9362,7 @@ static int lfs3_mdir_commit(lfs3_t *lfs3, lfs3_mdir_t *mdir, // update our mtree } else { - // mark as unerased in case of failure + // mark as unfetched in case of failure lfs3_btree_claim(&lfs3->mtree); err = lfs3_mtree_commit(lfs3, &mtree_, @@ -10598,6 +10602,162 @@ static int lfs3_data_readgbmap(lfs3_t *lfs3, lfs3_data_t *data) { } #endif +// on-disk block map operations +// +// note these take any btree, since we sometimes update the gbmap, and +// sometimes the gbatc + +#ifdef LFS3_BMAP +static int lfs3_bmap_lookupnext(lfs3_t *lfs3, lfs3_btree_t *bmap, + lfs3_bid_t bid, + lfs3_bid_t *bid_, lfs3_bid_t *weight_) { + return lfs3_btree_lookupnext(lfs3, bmap, bid, + bid_, weight_, NULL); +} +#endif + +// this is the same as lfs3_btree_commit, but we set the inbmap flag +// for debugging reasons +#ifdef LFS3_BMAP +static int lfs3_bmap_commit(lfs3_t *lfs3, lfs3_btree_t *bmap, + lfs3_bid_t bid, const lfs3_rattr_t *rattrs, lfs3_size_t rattr_count) { + #ifdef LFS3_REVDBG + lfs3->flags |= LFS3_i_INBMAP; + #endif + + int err = lfs3_btree_commit(lfs3, bmap, bid, rattrs, rattr_count); + if (err) { + goto failed; + } + + #ifdef LFS3_REVDBG + lfs3->flags &= ~LFS3_i_INBMAP; + #endif + return 0; + +failed:; + #ifdef LFS3_REVDBG + lfs3->flags &= ~LFS3_i_INBMAP; + #endif + return err; +} +#endif + +#ifdef LFS3_BMAP +static int lfs3_bmap_set(lfs3_t *lfs3, lfs3_btree_t *bmap, + lfs3_block_t block, lfs3_tag_t tag) { + // lookup bmap range + lfs3_bid_t bid__; + lfs3_bid_t weight__; + lfs3_stag_t tag__ = lfs3_bmap_lookupnext(lfs3, bmap, block, + &bid__, &weight__); + if (tag__ < 0) { + LFS3_ASSERT(tag__ != LFS3_ERR_NOENT); + return tag__; + } + + // wait, already set to expected type? guess we're done + if (tag__ == tag) { + return 0; + } + + // temporary copy, we definitely _don't_ want to leave this in a + // weird state on error + lfs3_btree_t bmap_ = *bmap; + // TODO should we mark the gbmaps as unfetched as well? + // TODO should we just claim all matching btrees in lfs3_btree_commit? + // mark as unfetched in case of error + lfs3_btree_claim(bmap); + + // TODO should these use builder pattern? + + // first delete block from range, this may split the range into + // multiple neighbors + // + // note this is never unnecessary work, the resulting neighbors + // can't share a type or else we would've already returned + int err = lfs3_bmap_commit(lfs3, &bmap_, bid__, LFS3_RATTRS( + (bid__-(weight__-1) < block) + ? LFS3_RATTR(LFS3_TAG_GROW, -((bid__+1) - block)) + : LFS3_RATTR(LFS3_TAG_RM, -((bid__+1) - block)), + (bid__ > block) + ? LFS3_RATTR(tag__, +(bid__ - block)) + : LFS3_RATTR_NOOP())); + if (err) { + return err; + } + + // weight of new range + lfs3_bid_t weight = 1; + + // can we merge with right neighbor? + if (block < lfs3->block_count-1) { + // note the use of the old bmap to try to leverage leaf caching + tag__ = lfs3_bmap_lookupnext(lfs3, bmap, block+1, + &bid__, &weight__); + if (tag__ < 0) { + LFS3_ASSERT(tag__ != LFS3_ERR_NOENT); + return tag__; + } + + if (tag__ == tag) { + LFS3_ASSERT(weight__ == bid__ - block); + // merge + weight += weight__; + + // delete to prepare merge + // + // note the shifted bid because of the previous delete + err = lfs3_bmap_commit(lfs3, &bmap_, bid__-1, LFS3_RATTRS( + LFS3_RATTR(LFS3_TAG_RM, -weight__))); + if (err) { + return err; + } + } + } + + // can we merge with left neighbor? + if (block > 0) { + // note the use of the old bmap to try to leverage leaf caching + tag__ = lfs3_bmap_lookupnext(lfs3, bmap, block-1, + &bid__, &weight__); + if (tag__ < 0) { + LFS3_ASSERT(tag__ != LFS3_ERR_NOENT); + return tag__; + } + + if (tag__ == tag) { + LFS3_ASSERT(bid__ == block-1); + // we can merge everything in one commit here + err = lfs3_bmap_commit(lfs3, &bmap_, bid__, LFS3_RATTRS( + LFS3_RATTR(LFS3_TAG_GROW, +weight))); + if (err) { + return err; + } + + // done! + *bmap = bmap_; + return 0; + } + } + + // needs a new range + err = lfs3_bmap_commit(lfs3, &bmap_, block, LFS3_RATTRS( + LFS3_RATTR(tag, +weight))); + if (err) { + return err; + } + + // done! + *bmap = bmap_; + return 0; +} +#endif + +// TODO lfs3_bmap_relocate +// TODO lfs3_bmap_mdirdiff +// TODO lfs3_bmap_btreediff + /// Block allocator /// @@ -10878,9 +11038,9 @@ static lfs3_sblock_t lfs3_alloc(lfs3_t *lfs3, uint32_t flags) { while (lfs3->bmap.known > 0 && lfs3->lookahead.ckpoint > 0) { lfs3_bid_t bid; - lfs3_stag_t tag = lfs3_btree_lookupnext(lfs3, &lfs3->bmap.gbatc, + lfs3_stag_t tag = lfs3_bmap_lookupnext(lfs3, &lfs3->bmap.gbatc, lfs3->bmap.cursor, - &bid, NULL, NULL); + &bid, NULL); if (tag < 0) { return tag; } diff --git a/tests/test_alloc.toml b/tests/test_alloc.toml index 94f7df0a..19b8daf4 100644 --- a/tests/test_alloc.toml +++ b/tests/test_alloc.toml @@ -7,7 +7,7 @@ # since you can usually ignore allocator issues temporarily by making the test # device really big # -after = ['test_mtree', 'test_dirs', 'test_files'] +after = ['test_mtree', 'test_bmap', 'test_dirs', 'test_files'] # test that we can alloc [cases.test_alloc_alloc] diff --git a/tests/test_bmap.toml b/tests/test_bmap.toml new file mode 100644 index 00000000..c436c1d9 --- /dev/null +++ b/tests/test_bmap.toml @@ -0,0 +1,381 @@ +# Test some low-level block-map operations +after = ['test_btree', 'test_mtree'] +ifdef = 'LFS3_BMAP' + +# maximize lookahead buffer, we don't actually gc so we only get one pass +# of the disk for these tests +defines.LOOKAHEAD_SIZE = '(BLOCK_COUNT+8-1) / 8' + +# test simple set operations +[cases.test_bmap_set_split] +in = 'lfs3.c' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + lfs3_alloc_ckpoint(&lfs3); + + // create an initial bmap + lfs3_btree_t bmap; + lfs3_btree_init(&bmap); + lfs3_bmap_commit(&lfs3, &bmap, 0, LFS3_RATTRS( + LFS3_RATTR(LFS3_TAG_BMFREE, +BLOCK_COUNT))) => 0; + + // set some blocks as in-use, avoid any weird merges for now + lfs3_bmap_set(&lfs3, &bmap, 6, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 8, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 10, LFS3_TAG_BMINUSE) => 0; + printf("bmap: w%d 0x%x.%x\n", + bmap.r.weight, + bmap.r.blocks[0], + bmap.r.trunk); + + // weight should stay the same + assert(bmap.r.weight == BLOCK_COUNT); + + // check ranges + lfs3_bid_t bid_; + lfs3_bid_t weight_; + lfs3_bmap_lookupnext(&lfs3, &bmap, 0, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == 5); + assert(weight_ == 6); + lfs3_bmap_lookupnext(&lfs3, &bmap, 6, + &bid_, &weight_) => LFS3_TAG_BMINUSE; + assert(bid_ == 6); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 7, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == 7); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 8, + &bid_, &weight_) => LFS3_TAG_BMINUSE; + assert(bid_ == 8); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 9, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == 9); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 10, + &bid_, &weight_) => LFS3_TAG_BMINUSE; + assert(bid_ == 10); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 11, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == BLOCK_COUNT-1); + assert(weight_ == BLOCK_COUNT-11); + + lfs3_unmount(&lfs3) => 0; +''' + +[cases.test_bmap_set_replace] +in = 'lfs3.c' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + lfs3_alloc_ckpoint(&lfs3); + + // create an initial bmap + lfs3_btree_t bmap; + lfs3_btree_init(&bmap); + lfs3_bmap_commit(&lfs3, &bmap, 0, LFS3_RATTRS( + LFS3_RATTR(LFS3_TAG_BMFREE, +BLOCK_COUNT))) => 0; + + // set some blocks as in-use, avoid any weird merges for now + lfs3_bmap_set(&lfs3, &bmap, 6, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 8, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 10, LFS3_TAG_BMINUSE) => 0; + // replace those blocks as bad, this tests deleting ranges + lfs3_bmap_set(&lfs3, &bmap, 6, LFS3_TAG_BMINFLIGHT) => 0; + lfs3_bmap_set(&lfs3, &bmap, 8, LFS3_TAG_BMINFLIGHT) => 0; + lfs3_bmap_set(&lfs3, &bmap, 10, LFS3_TAG_BMINFLIGHT) => 0; + printf("bmap: w%d 0x%x.%x\n", + bmap.r.weight, + bmap.r.blocks[0], + bmap.r.trunk); + + // weight should stay the same + assert(bmap.r.weight == BLOCK_COUNT); + + // check ranges + lfs3_bid_t bid_; + lfs3_bid_t weight_; + lfs3_bmap_lookupnext(&lfs3, &bmap, 0, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == 5); + assert(weight_ == 6); + lfs3_bmap_lookupnext(&lfs3, &bmap, 6, + &bid_, &weight_) => LFS3_TAG_BMINFLIGHT; + assert(bid_ == 6); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 7, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == 7); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 8, + &bid_, &weight_) => LFS3_TAG_BMINFLIGHT; + assert(bid_ == 8); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 9, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == 9); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 10, + &bid_, &weight_) => LFS3_TAG_BMINFLIGHT; + assert(bid_ == 10); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 11, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == BLOCK_COUNT-1); + assert(weight_ == BLOCK_COUNT-11); + + lfs3_unmount(&lfs3) => 0; +''' + +[cases.test_bmap_set_merge] +in = 'lfs3.c' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + lfs3_alloc_ckpoint(&lfs3); + + // create an initial bmap + lfs3_btree_t bmap; + lfs3_btree_init(&bmap); + lfs3_bmap_commit(&lfs3, &bmap, 0, LFS3_RATTRS( + LFS3_RATTR(LFS3_TAG_BMFREE, +BLOCK_COUNT))) => 0; + + // set some blocks as in-use, avoid any weird merges for now + lfs3_bmap_set(&lfs3, &bmap, 6, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 8, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 10, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 12, LFS3_TAG_BMINUSE) => 0; + // set neighboring blocks as in-use, triggering merges + lfs3_bmap_set(&lfs3, &bmap, 5, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 9, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 13, LFS3_TAG_BMINUSE) => 0; + printf("bmap: w%d 0x%x.%x\n", + bmap.r.weight, + bmap.r.blocks[0], + bmap.r.trunk); + + // weight should stay the same + assert(bmap.r.weight == BLOCK_COUNT); + + // check ranges + lfs3_bid_t bid_; + lfs3_bid_t weight_; + lfs3_bmap_lookupnext(&lfs3, &bmap, 0, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == 4); + assert(weight_ == 5); + lfs3_bmap_lookupnext(&lfs3, &bmap, 5, + &bid_, &weight_) => LFS3_TAG_BMINUSE; + assert(bid_ == 6); + assert(weight_ == 2); + lfs3_bmap_lookupnext(&lfs3, &bmap, 7, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == 7); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 8, + &bid_, &weight_) => LFS3_TAG_BMINUSE; + assert(bid_ == 10); + assert(weight_ == 3); + lfs3_bmap_lookupnext(&lfs3, &bmap, 11, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == 11); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 12, + &bid_, &weight_) => LFS3_TAG_BMINUSE; + assert(bid_ == 13); + assert(weight_ == 2); + lfs3_bmap_lookupnext(&lfs3, &bmap, 14, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == BLOCK_COUNT-1); + assert(weight_ == BLOCK_COUNT-14); + + lfs3_unmount(&lfs3) => 0; +''' + +[cases.test_bmap_set_noop] +in = 'lfs3.c' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + lfs3_alloc_ckpoint(&lfs3); + + // create an initial bmap + lfs3_btree_t bmap; + lfs3_btree_init(&bmap); + lfs3_bmap_commit(&lfs3, &bmap, 0, LFS3_RATTRS( + LFS3_RATTR(LFS3_TAG_BMFREE, +BLOCK_COUNT))) => 0; + + // set some blocks as in-use, avoid any weird merges for now + lfs3_bmap_set(&lfs3, &bmap, 6, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 8, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 10, LFS3_TAG_BMINUSE) => 0; + // test a bunch of noops + lfs3_bmap_set(&lfs3, &bmap, 6, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 7, LFS3_TAG_BMFREE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 8, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 9, LFS3_TAG_BMFREE) => 0; + lfs3_bmap_set(&lfs3, &bmap, 10, LFS3_TAG_BMINUSE) => 0; + printf("bmap: w%d 0x%x.%x\n", + bmap.r.weight, + bmap.r.blocks[0], + bmap.r.trunk); + + // weight should stay the same + assert(bmap.r.weight == BLOCK_COUNT); + + // check ranges + lfs3_bid_t bid_; + lfs3_bid_t weight_; + lfs3_bmap_lookupnext(&lfs3, &bmap, 0, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == 5); + assert(weight_ == 6); + lfs3_bmap_lookupnext(&lfs3, &bmap, 6, + &bid_, &weight_) => LFS3_TAG_BMINUSE; + assert(bid_ == 6); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 7, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == 7); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 8, + &bid_, &weight_) => LFS3_TAG_BMINUSE; + assert(bid_ == 8); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 9, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == 9); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 10, + &bid_, &weight_) => LFS3_TAG_BMINUSE; + assert(bid_ == 10); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 11, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == BLOCK_COUNT-1); + assert(weight_ == BLOCK_COUNT-11); + + lfs3_unmount(&lfs3) => 0; +''' + +[cases.test_bmap_set_bounds] +in = 'lfs3.c' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + lfs3_alloc_ckpoint(&lfs3); + + // create an initial bmap + lfs3_btree_t bmap; + lfs3_btree_init(&bmap); + lfs3_bmap_commit(&lfs3, &bmap, 0, LFS3_RATTRS( + LFS3_RATTR(LFS3_TAG_BMFREE, +BLOCK_COUNT))) => 0; + + // test that setting the first and last blocks don't break anything + // + // though in theory blocks 0x{0,1} are immutable... + lfs3_bmap_set(&lfs3, &bmap, 0, LFS3_TAG_BMINUSE) => 0; + lfs3_bmap_set(&lfs3, &bmap, BLOCK_COUNT-1, LFS3_TAG_BMINUSE) => 0; + printf("bmap: w%d 0x%x.%x\n", + bmap.r.weight, + bmap.r.blocks[0], + bmap.r.trunk); + + // weight should stay the same + assert(bmap.r.weight == BLOCK_COUNT); + + // check ranges + lfs3_bid_t bid_; + lfs3_bid_t weight_; + lfs3_bmap_lookupnext(&lfs3, &bmap, 0, + &bid_, &weight_) => LFS3_TAG_BMINUSE; + assert(bid_ == 0); + assert(weight_ == 1); + lfs3_bmap_lookupnext(&lfs3, &bmap, 1, + &bid_, &weight_) => LFS3_TAG_BMFREE; + assert(bid_ == BLOCK_COUNT-2); + assert(weight_ == BLOCK_COUNT-2); + lfs3_bmap_lookupnext(&lfs3, &bmap, BLOCK_COUNT-1, + &bid_, &weight_) => LFS3_TAG_BMINUSE; + assert(bid_ == BLOCK_COUNT-1); + assert(weight_ == 1); + + lfs3_unmount(&lfs3) => 0; +''' + +[cases.test_bmap_set_fuzz] +defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] +defines.TYPES = [2, 3, 4] +defines.SEED = 'range(20)' +fuzz = 'SEED' +in = 'lfs3.c' +code = ''' + lfs3_t lfs3; + lfs3_format(&lfs3, LFS3_F_RDWR, CFG) => 0; + lfs3_mount(&lfs3, LFS3_M_RDWR, CFG) => 0; + lfs3_alloc_ckpoint(&lfs3); + + // create an initial bmap + lfs3_btree_t bmap; + lfs3_btree_init(&bmap); + lfs3_bmap_commit(&lfs3, &bmap, 0, LFS3_RATTRS( + LFS3_RATTR(LFS3_TAG_BMFREE, +BLOCK_COUNT))) => 0; + + // create a simulation to compare against + lfs3_tag_t *sim = malloc(BLOCK_COUNT*sizeof(lfs3_tag_t)); + for (lfs3_size_t i = 0; i < BLOCK_COUNT; i++) { + sim[i] = LFS3_TAG_BMFREE; + } + + uint32_t prng = SEED; + for (lfs3_size_t i = 0; i < N; i++) { + // choose a pseudo-random block + lfs3_block_t block = TEST_PRNG(&prng) % BLOCK_COUNT; + // and pseudo-random bmap type + lfs3_tag_t tag = LFS3_TAG_BMRANGE + (TEST_PRNG(&prng) % TYPES); + + // set in bmap + lfs3_bmap_set(&lfs3, &bmap, block, tag) => 0; + + // and set in sim + sim[block] = tag; + } + printf("bmap: w%d 0x%x.%x\n", + bmap.r.weight, + bmap.r.blocks[0], + bmap.r.trunk); + + // check if bmap matches sim + lfs3_size_t i = 0; + while (i < BLOCK_COUNT) { + // we need to convert our sim's raw blocks to compressed ranges, + // in theory our bmap is optimal + lfs3_tag_t tag = sim[i]; + lfs3_size_t d = 1; + while (i+d < BLOCK_COUNT && sim[i+d] == tag) { + d += 1; + } + + // does our bmap contain the optimal range? + lfs3_bid_t bid_; + lfs3_bid_t weight_; + lfs3_bmap_lookupnext(&lfs3, &bmap, i, + &bid_, &weight_) => tag; + assert(bid_ == i+(weight_-1)); + assert(weight_ == d); + + i += d; + } + + free(sim); + lfs3_unmount(&lfs3) => 0; +'''