Files
littlefs/tests/test_bmap.toml
T
Christopher Haster e622656538 bmap: Tweaked bmap ranges, dropped in-flight tag for now
New bmap range tags:

  LFS3_TAG_BMRANGE      0x033u  v--- --11 --11 uuuu
  LFS3_TAG_BMFREE       0x0330  v--- --11 --11 ----
  LFS3_TAG_BMINUSE      0x0331  v--- --11 --11 ---1
  LFS3_TAG_BMERASED     0x0332  v--- --11 --11 --1-
  LFS3_TAG_BMBAD        0x0333  v--- --11 --11 --11

Note 0x334-0x33f are still reserved for future bmap tags, but the new
encoding fits in the surprisingly common 2-bit subfield that may
deduplicate some decoding code.

Fitting in 2-bits is the main reason for this, now that in-flight ranges
look like they won't be worth exploring further. Worst case we can
always add more bm tags in the future. And it may even make sense to use
an entire bit for in-flight tags, since in theory the concept can apply
to more than just in-use blocks.

---

Another benefit of this encoding: In-use vs free is a bit check, and I
like the implication that an in-use + erased block can only be a bad
block.

No code changes:

                code          stack          ctx
  before:      37172           2352          684
  after:       37172 (+0.0%)   2352 (+0.0%)  684 (+0.0%)

                code          stack          ctx
  bmap before: 38844           2456          800
  bmap after:  38844 (+0.0%)   2456 (+0.0%)  800 (+0.0%)
2025-10-09 14:33:24 -05:00

378 lines
12 KiB
TOML

# Test some low-level block-map operations
after = ['test_btree', 'test_mtree']
ifdef = 'LFS3_BMAP'
# 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_BMBAD) => 0;
lfs3_bmap_set(&lfs3, &bmap, 8, LFS3_TAG_BMBAD) => 0;
lfs3_bmap_set(&lfs3, &bmap, 10, LFS3_TAG_BMBAD) => 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_BMBAD;
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_BMBAD;
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_BMBAD;
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;
'''