t: Separated mtree traversal/iteration, bshrub staging

A number of traversal changes:

- Traversal now traverses the mtree's btree (the inner btree nodes)
  separately from iterating over mdirs in the mtree.

  This makes resuming clobbered traversals more robust as there's less
  state to worry about. It also reduces all btree traversals to a single
  state which simplifies the traversal logic and _in theory_ reduces
  code/RAM costs.

  This does add a second O(n logbn) pass through the mtree, but this
  takes the fast path since we already validated btree nodes. mtree
  traversal is probably dominated by mdir fetching anyways...

- lfsr_mdir_commit no longer clobbers mid-related traversals. This was a
  bit too complicated with attrs potentially inserting new mids.

  Instead, it's up to upper layers to explicitly clobber traversals.
  Most of these already need to update dir positions, so it's not that
  much extra code, but it does add cost.

  lfsr_mdir_commit still clobbers mroot/mtree related traversals.

- We now stage bshrubs in traversals during mdir compaction, so we
  shouldn't need to clobber traversals when the mdir compacts.

  In theory as long as we clobber traversals that reference opened
  files, we should never end up being the only reference to a bshrub. So
  we should be able to stage bshrubs without cost.

  This is _not_ working at the moment, because we aren't updating the
  actual btraversal state correctly... not sure how to fix this yet...

Code/stack changes:

           code          stack
  before: 34682           2544
  after:  34716 (+0.1%)   2648 (+4.1%)

The surprise stack cost is _very_ interesting. Where is this coming
from?

It turns out when we reduce all btree traversals to a single state, and a
single function call, GCC is happy to inline lfsr_btree_traverse
directly into lfsr_fs_traverse.

This is great for code cost, but now lfs_fs_traverse contains the entire
stack frame of lfsr_btree_traverse, which is quite large. When we called
lfsr_btree_traverse twice, this stack frame was never nested with
lfsr_mtree_lookup, but now our tools think it is...

I'm not sure how to fix this. Maybe improving our tooling to understand
shrinkwrap optimizations will find this doesn't actually cost as much?
Or maybe not since this is in a complicated switch case state machine?
We could use an explicit __attribute__((noinline)), but this sort of
heavy-handed optimization guidance has been out-of-scope for littlefs up
until now...

I'm leaving this as-is for now, but it may be worth looking this again
in the future.
This commit is contained in:
Christopher Haster
2024-06-17 19:39:52 -05:00
parent e744106f77
commit 4cd1f84a89
3 changed files with 178 additions and 166 deletions
+149 -132
View File
@@ -5209,26 +5209,28 @@ static bool lfsr_mid_isopen(lfs_t *lfs, lfsr_smid_t mid) {
}
// needed in lfsr_opened_clobber
static void lfsr_fs_traverseclobber(lfs_t *lfs, lfsr_mtraversal_t *mt);
static void lfsr_traversal_clobber(lfs_t *lfs, lfsr_traversal_t *t);
// clobber any traversals that match our mid, or all traversals if mid=-1
static void lfsr_opened_clobber(lfs_t *lfs, lfsr_smid_t mid, bool dirty) {
for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) {
if (o->type == LFS_TYPE_TRAVERSAL) {
// mark as dirty
o->flags |= (dirty) ? LFS_F_DIRTY : 0;
// clobber if mid matches
if (mid == -1 || o->mdir.mid == mid) {
lfsr_traversal_t *t = (lfsr_traversal_t*)o;
lfsr_fs_traverseclobber(lfs, &t->mt);
// and clear any pending blocks
t->blocks[0] = -1;
t->blocks[1] = -1;
}
}
}
}
// TODO
//
//// clobber any traversals that match our mid, or all traversals if mid=-1
//static void lfsr_opened_clobber(lfs_t *lfs, lfsr_smid_t mid, bool dirty) {
// for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) {
// if (o->type == LFS_TYPE_TRAVERSAL) {
// // mark as dirty
// o->flags |= (dirty) ? LFS_F_DIRTY : 0;
//
// // clobber if mid matches
// if (mid == -1 || o->mdir.mid == mid) {
// lfsr_traversal_t *t = (lfsr_traversal_t*)o;
// lfsr_fs_traverseclobber(lfs, &t->mt);
// // and clear any pending blocks
// t->blocks[0] = -1;
// t->blocks[1] = -1;
// }
// }
// }
//}
@@ -5451,7 +5453,8 @@ static int lfsr_shrub_compact(lfs_t *lfs, lfsr_rbyd_t *rbyd_,
// this should include our current bshrub
for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) {
lfsr_file_t *file_ = (lfsr_file_t*)o;
if (file_->o.type == LFS_TYPE_REG
if ((file_->o.type == LFS_TYPE_REG
|| file_->o.type == LFS_TYPE_TRAVERSAL)
&& lfsr_bshrub_isbshrub(&file_->o.mdir, &file_->bshrub)
&& lfsr_shrub_cmp(&file_->bshrub.u.bshrub, shrub) == 0) {
file_->bshrub_.u.bshrub.blocks[0] = rbyd_->blocks[0];
@@ -5981,6 +5984,7 @@ static int lfsr_mtree_lookup(lfs_t *lfs, const lfsr_mtree_t *mtree,
mid,
&bid, &tag, NULL, &data);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
LFS_ASSERT((lfsr_sbid_t)bid == lfsr_mid_bid(lfs, mid));
@@ -6845,8 +6849,6 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
// checkpoint the allocator
lfs_alloc_ckpoint(lfs);
// clobber any related traversals
lfsr_opened_clobber(lfs, mdir->mid, true);
// play out any attrs that affect our grm _before_ committing to disk,
// keep in mind we revert to on-disk gstate if we run into an error
@@ -6893,7 +6895,8 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
}
// stage any bsprouts/bshrubs
if (o->type == LFS_TYPE_REG) {
if (o->type == LFS_TYPE_REG
|| o->type == LFS_TYPE_TRAVERSAL) {
lfsr_file_t *file = (lfsr_file_t*)o;
file->bshrub_ = file->bshrub;
}
@@ -7362,12 +7365,6 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
// update any gstate changes
lfsr_fs_commitgdelta(lfs);
// if mtree/mroot changed, clobber all traversals, too much has changed
if (lfsr_mdir_cmp(&mroot_, &lfs->mroot) != 0
|| lfsr_mtree_cmp(&mtree_, &lfs->mtree) != 0) {
lfsr_opened_clobber(lfs, -1, true);
}
// play out any attrs that affect internal state
mid_ = mdir->mid;
for (lfs_size_t i = 0; i < attr_count; i++) {
@@ -7394,7 +7391,8 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
// update any staged bsprouts/bshrubs
for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) {
if (o->type == LFS_TYPE_REG) {
if (o->type == LFS_TYPE_REG
|| o->type == LFS_TYPE_TRAVERSAL) {
lfsr_file_t *file = (lfsr_file_t*)o;
file->bshrub = file->bshrub_;
}
@@ -7409,7 +7407,6 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
// update any splits/drops
if (lfsr_mdir_cmp(&o->mdir, mdir) == 0) {
LFS_ASSERT(mdir->mid != -1 || mdir == &lfs->mroot);
if (mdelta > 0
&& lfsr_mid_rid(lfs, o->mdir.mid)
>= (lfsr_srid_t)mdir_[0].rbyd.weight) {
@@ -7434,6 +7431,20 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
mdir->rbyd = mdir_[0].rbyd;
}
// mark all traversals as dirty
for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) {
if (o->type == LFS_TYPE_TRAVERSAL) {
o->flags |= LFS_F_DIRTY;
// clobber any mroot/mtree traversals
if (o->mdir.mid == -1
&& (lfsr_mdir_cmp(&mroot_, &lfs->mroot) != 0
|| lfsr_mtree_cmp(&mtree_, &lfs->mtree) != 0)) {
lfsr_traversal_clobber(lfs, (lfsr_traversal_t*)o);
}
}
}
// update mroot and mtree
lfs->mroot = mroot_;
lfs->mtree = mtree_;
@@ -7766,7 +7777,7 @@ enum {
LFSR_TSTATE_MROOTCHAIN = 1,
LFSR_TSTATE_MTREE = 2,
LFSR_TSTATE_MDIR = 3,
LFSR_TSTATE_OMDIR = 4,
LFSR_TSTATE_OPENED = 4,
LFSR_TSTATE_BTREE = 5,
LFSR_TSTATE_DONE = 6,
};
@@ -7777,6 +7788,7 @@ enum {
.o.state=LFSR_TSTATE_MROOTANCHOR, \
.o.flags=_flags, \
.o.mdir.mid=-1, \
.ot=NULL, \
.u.mtortoise.mptr={{0, 0}}, \
.u.mtortoise.step=0, \
.u.mtortoise.power=0})
@@ -7824,6 +7836,7 @@ static void lfsr_fs_traverserewind(lfs_t *lfs, lfsr_mtraversal_t *mt) {
mt->o.flags &= ~LFS_F_DIRTY;
mt->o.state = LFSR_TSTATE_MROOTANCHOR;
mt->o.mdir.mid = -1;
mt->ot = NULL;
mt->u.mtortoise.mptr.blocks[0] = 0;
mt->u.mtortoise.mptr.blocks[1] = 0;
mt->u.mtortoise.step = 0;
@@ -7837,7 +7850,8 @@ static void lfsr_fs_traverseclobber(lfs_t *lfs, lfsr_mtraversal_t *mt) {
mt->o.mdir.mid = lfs_min(
mt->o.mdir.mid + 1,
lfsr_fs_weight(lfs));
mt->u.bt = LFSR_BTRAVERSAL(mt->o.mdir.mid);
// TODO do something different with this maybe?
mt->ot = NULL;
}
@@ -7955,116 +7969,58 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
// found an mtree?
} else if (tag == LFSR_TAG_MTREE) {
// fetch the root of the mtree
lfsr_btree_t mtree;
err = lfsr_data_readbtree(lfs, &data, &mtree);
err = lfsr_data_readbtree(lfs, &data, &mt->bshrub.u.btree);
if (err) {
return err;
}
// transition to traversing the mtree
mt->u.bt = LFSR_BTRAVERSAL(0);
mt->o.mdir.mid = 0;
mt->o.state = LFSR_TSTATE_MTREE;
// go ahead and traverse the root
//
// this avoids an annoying situation where the mtree is
// uninitialized in mountinited, but we really don't want
// to store another copy of the mtree somewhere because
// we're often traversing in extremely deep call stacks
// such as lfs_alloc
//
// after traversing the mtree root, mountinited should
// initialize lfs->mtree and we can switch to that
//
err = lfsr_btree_traverse(lfs, &mtree, &mt->u.bt,
NULL, mtinfo);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
LFS_ASSERT(mtinfo->tag == LFSR_TAG_BRANCH);
return 0;
mt->o.state = LFSR_TSTATE_BTREE;
continue;
} else {
LFS_ERROR("Weird mroot entry? 0x%"PRIx32, tag);
return LFS_ERR_CORRUPT;
}
// traverse the mtree, including both inner btree nodes and mdirs
// iterate over mdirs in the mtree
case LFSR_TSTATE_MTREE:;
// TODO should we move this into lfsr_mtree_lookup?
// end of mtree? guess we're done
if (mt->o.mdir.mid >= (lfsr_smid_t)lfsr_fs_weight(lfs)) {
mt->o.state = LFSR_TSTATE_DONE;
continue;
}
// inlined mroot? transition to mdir traversal
if (lfsr_mtree_isnull(&lfs->mtree)) {
mt->o.mdir.rbyd = lfs->mroot.rbyd;
mt->o.state = LFSR_TSTATE_MDIR;
continue;
// direct mdir? transition to mdir traversal
} else if (lfsr_mtree_ismptr(&lfs->mtree)) {
err = lfsr_mdir_fetch(lfs, &mt->o.mdir,
mt->o.mdir.mid, &lfs->mtree.u.mptr.mptr);
if (err) {
return err;
}
mt->o.state = LFSR_TSTATE_MDIR;
continue;
}
// traverse through the mtree
err = lfsr_btree_traverse(lfs, &lfs->mtree.u.btree, &mt->u.bt,
NULL, mtinfo);
// find the next mdir
err = lfsr_mtree_lookup(lfs, &lfs->mtree, mt->o.mdir.mid,
&mt->o.mdir);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
// inner btree nodes already decoded
if (mtinfo->tag == LFSR_TAG_BRANCH) {
return 0;
// fetch mdir if we're on a leaf
} else if (mtinfo->tag == LFSR_TAG_MDIR) {
lfsr_mptr_t mptr;
err = lfsr_data_readmptr(lfs, &mtinfo->u.data, &mptr);
if (err) {
return err;
}
err = lfsr_mdir_fetch(lfs, &mt->o.mdir,
mt->o.mdir.mid, &mptr);
if (err) {
return err;
}
// transition to mdir traversal next
mt->o.state = LFSR_TSTATE_MDIR;
// transition to traversing the mdir
mt->o.state = LFSR_TSTATE_MDIR;
// first time we've seen this mdir?
if (lfsr_mid_rid(lfs, mt->o.mdir.mid) == 0) {
mtinfo->tag = LFSR_TAG_MDIR;
mtinfo->u.mdir = mt->o.mdir;
return 0;
} else {
LFS_ERROR("Weird mtree entry? 0x%"PRIx32, mtinfo->tag);
return LFS_ERR_CORRUPT;
}
continue;
// scan for blocks/btrees in the current mdir
case LFSR_TSTATE_MDIR:;
// not traversing all blocks? have we exceeded our mdir's weight?
// return to mtree traversal
// return to mtree iteration
if (lfsr_t_ismtreeonly(mt->o.flags)
|| lfsr_mid_rid(lfs, mt->o.mdir.mid)
>= (lfsr_srid_t)mt->o.mdir.rbyd.weight) {
// resume from our mid
mt->o.mdir.mid = lfsr_mid_bid(lfs, mt->o.mdir.mid) + 1;
mt->u.bt = LFSR_BTRAVERSAL(mt->o.mdir.mid);
mt->o.state = LFSR_TSTATE_MTREE;
continue;
}
@@ -8102,7 +8058,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
// no? next we need to check any opened files
} else {
mt->ot = &lfs->opened;
mt->o.state = LFSR_TSTATE_OMDIR;
mt->o.state = LFSR_TSTATE_OPENED;
continue;
}
@@ -8113,7 +8069,7 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
continue;
// scan for blocks/btrees in our opened file list
case LFSR_TSTATE_OMDIR:;
case LFSR_TSTATE_OPENED:;
// reached end of opened files? return to mdir traversal
lfsr_omdir_t *o = *mt->ot;
if (!o) {
@@ -8145,16 +8101,22 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
mt->o.state = LFSR_TSTATE_BTREE;
continue;
// traverse any file btrees, including both inner btree nodes and
// block pointers
// traverse any btrees we see, this includes the mtree and any file
// btrees/bshrubs
case LFSR_TSTATE_BTREE:;
// traverse through our file
err = lfsr_bshrub_traverse(lfs, (const lfsr_file_t*)mt, &mt->u.bt,
NULL, mtinfo);
if (err) {
if (err == LFS_ERR_NOENT) {
// end of mtree? start iterating over mdirs
if (mt->o.mdir.mid == -1) {
mt->o.mdir.mid = 0;
mt->o.state = LFSR_TSTATE_MTREE;
// end of btree? go to next opened file
mt->o.state = LFSR_TSTATE_OMDIR;
} else {
mt->o.state = LFSR_TSTATE_OPENED;
}
continue;
}
return err;
@@ -8164,18 +8126,13 @@ static int lfsr_fs_traverse_(lfs_t *lfs, lfsr_mtraversal_t *mt,
if (mtinfo->tag == LFSR_TAG_BRANCH) {
return 0;
// found inlined data? ignore this
} else if (mtinfo->tag == LFSR_TAG_DATA) {
continue;
// found an indirect block?
} else if (mtinfo->tag == LFSR_TAG_BLOCK) {
return 0;
} else {
LFS_UNREACHABLE();
}
continue;
case LFSR_TSTATE_DONE:;
return LFS_ERR_NOENT;
@@ -9398,6 +9355,16 @@ failed:;
/// High-level filesystem traversal ///
// TODO keep this?
static void lfsr_traversal_clobber(lfs_t *lfs, lfsr_traversal_t *t) {
// clobber the low-level traversal
lfsr_fs_traverseclobber(lfs, &t->mt);
// and clear any pending blocks
t->blocks[0] = -1;
t->blocks[1] = -1;
}
// needed in lfsr_traversal_open
static int lfsr_traversal_rewind_(lfs_t *lfs, lfsr_traversal_t *t);
@@ -9827,6 +9794,11 @@ int lfsr_remove(lfs_t *lfs, const char *path) {
} else {
((lfsr_dir_t*)o)->pos -= 1;
}
// clobber any problematic traversals
} else if (o->type == LFS_TYPE_TRAVERSAL
&& o->mdir.mid == mdir.mid) {
lfsr_traversal_clobber(lfs, (lfsr_traversal_t*)o);
}
}
@@ -10007,6 +9979,12 @@ int lfsr_rename(lfs_t *lfs, const char *old_path, const char *new_path) {
((lfsr_dir_t*)o)->pos -= 1;
}
}
// clobber any problematic traversals
} else if (o->type == LFS_TYPE_TRAVERSAL
&& ((exists && o->mdir.mid == new_mdir.mid)
|| o->mdir.mid == lfs->grm.mids[0])) {
lfsr_traversal_clobber(lfs, (lfsr_traversal_t*)o);
}
}
@@ -10659,7 +10637,13 @@ int lfsr_file_close(lfs_t *lfs, lfsr_file_t *file) {
// if we're unsync, we need to clobber any traversals that may be
// referencing our bshrub/memory, but we don't need to mark as dirty
if (lfsr_f_isunsync(file->o.flags)) {
lfsr_opened_clobber(lfs, file->o.mdir.mid, false);
for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) {
if (o->type == LFS_TYPE_TRAVERSAL) {
if (((lfsr_traversal_t*)o)->mt.ot == &file->o.next) {
lfsr_traversal_clobber(lfs, (lfsr_traversal_t*)o);
}
}
}
}
// remove from tracked mdirs
@@ -11982,8 +11966,15 @@ lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
// checkpoint the allocator
lfs_alloc_ckpoint(lfs);
// clobber any related traversals
lfsr_opened_clobber(lfs, file->o.mdir.mid, true);
// clobber any problematic traversals
for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) {
if (o->type == LFS_TYPE_TRAVERSAL) {
o->flags |= LFS_F_DIRTY;
if (((lfsr_traversal_t*)o)->mt.ot == &file->o.next) {
lfsr_traversal_clobber(lfs, (lfsr_traversal_t*)o);
}
}
}
// mark as unsynced in case we fail
file->o.flags |= LFS_F_UNSYNC;
@@ -12142,8 +12133,15 @@ int lfsr_file_flush(lfs_t *lfs, lfsr_file_t *file) {
// checkpoint the allocator
lfs_alloc_ckpoint(lfs);
// clobber any related traversals
lfsr_opened_clobber(lfs, file->o.mdir.mid, true);
// clobber any problematic traversals
for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) {
if (o->type == LFS_TYPE_TRAVERSAL) {
o->flags |= LFS_F_DIRTY;
if (((lfsr_traversal_t*)o)->mt.ot == &file->o.next) {
lfsr_traversal_clobber(lfs, (lfsr_traversal_t*)o);
}
}
}
// flush our buffer if it contains any unwritten data
int err;
@@ -12286,13 +12284,13 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
}
}
// but do update other file handles
// update in-device state
for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) {
lfsr_file_t *file_ = (lfsr_file_t*)o;
if (file_->o.type == LFS_TYPE_REG
&& file_->o.mdir.mid == file->o.mdir.mid
if (o->type == LFS_TYPE_REG
&& o->mdir.mid == file->o.mdir.mid
// don't double update
&& file_ != file) {
&& o != &file->o) {
lfsr_file_t *file_ = (lfsr_file_t*)o;
// notify all files of creation
file_->o.flags &= ~LFS_F_ORPHAN;
@@ -12317,6 +12315,11 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
file->buffer.size);
file_->buffer.size = file->buffer.size;
}
// clobber any problematic traversals
} else if (o->type == LFS_TYPE_TRAVERSAL
&& o->mdir.mid == file->o.mdir.mid) {
lfsr_traversal_clobber(lfs, (lfsr_traversal_t*)o);
}
}
@@ -12403,8 +12406,15 @@ int lfsr_file_truncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) {
// checkpoint the allocator
lfs_alloc_ckpoint(lfs);
// clobber any related traversals
lfsr_opened_clobber(lfs, file->o.mdir.mid, true);
// clobber any problematic traversals
for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) {
if (o->type == LFS_TYPE_TRAVERSAL) {
o->flags |= LFS_F_DIRTY;
if (((lfsr_traversal_t*)o)->mt.ot == &file->o.next) {
lfsr_traversal_clobber(lfs, (lfsr_traversal_t*)o);
}
}
}
// mark as unsynced in case we fail
file->o.flags |= LFS_F_UNSYNC;
@@ -12509,8 +12519,15 @@ int lfsr_file_fruncate(lfs_t *lfs, lfsr_file_t *file, lfs_off_t size_) {
// checkpoint the allocator
lfs_alloc_ckpoint(lfs);
// clobber any related traversals
lfsr_opened_clobber(lfs, file->o.mdir.mid, true);
// clobber any problematic traversals
for (lfsr_omdir_t *o = lfs->opened; o; o = o->next) {
if (o->type == LFS_TYPE_TRAVERSAL) {
o->flags |= LFS_F_DIRTY;
if (((lfsr_traversal_t*)o)->mt.ot == &file->o.next) {
lfsr_traversal_clobber(lfs, (lfsr_traversal_t*)o);
}
}
}
// mark as unsynced in case we fail
file->o.flags |= LFS_F_UNSYNC;
+1
View File
@@ -610,6 +610,7 @@ typedef struct lfsr_mtraversal {
// so only store the relevant bits, is this a hack? yes
const struct lfs_file_config *cfg;
lfsr_bshrub_t bshrub;
lfsr_bshrub_t bshrub_;
// opened file state, we use an indirect pointer here so we
// always point to data associated with the current mid
+28 -34
View File
@@ -846,7 +846,7 @@ code = '''
[cases.test_traversal_ckmdir_dirs]
defines.N = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
code = '''
for (lfs_block_t i = 0;; i++) {
for (lfs_block_t i = 0;; i += 2) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
@@ -865,7 +865,6 @@ code = '''
lfsr_traversal_t t;
lfsr_traversal_open(&lfs, &t, 0) => 0;
lfs_block_t k = 0;
lfs_block_t c = 0;
for (lfs_block_t j = 0;; j++) {
assert(j < 2*BLOCK_COUNT);
@@ -879,7 +878,7 @@ code = '''
}
if (tinfo.btype == LFS_BTYPE_MDIR) {
if (k == i) {
if (k == i || k == i+1) {
// clobber this block
printf("clobbering 0x%x\n", tinfo.block);
uint8_t clobber_buf[BLOCK_SIZE];
@@ -887,15 +886,12 @@ code = '''
CFG->erase(CFG, tinfo.block) => 0;
CFG->prog(CFG, tinfo.block, 0,
clobber_buf, BLOCK_SIZE) => 0;
if (c == 2-1) {
if (k == i+1) {
lfsr_traversal_close(&lfs, &t) => 0;
goto clobbered;
} else {
c += 1;
}
} else {
k += 1;
}
k += 1;
}
}
@@ -935,7 +931,7 @@ defines.CKMETADATA = [false, true]
defines.CKDATA = [false, true]
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
for (lfs_block_t i = 0;; i++) {
for (lfs_block_t i = 0;; i += 2) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
@@ -965,7 +961,6 @@ code = '''
lfsr_traversal_t t;
lfsr_traversal_open(&lfs, &t, 0) => 0;
lfs_block_t k = 0;
lfs_block_t c = 0;
for (lfs_block_t j = 0;; j++) {
assert(j < 2*BLOCK_COUNT);
@@ -979,7 +974,7 @@ code = '''
}
if (tinfo.btype == LFS_BTYPE_MDIR) {
if (k == i) {
if (k == i || k == i+1) {
// clobber this block
printf("clobbering 0x%x\n", tinfo.block);
uint8_t clobber_buf[BLOCK_SIZE];
@@ -987,15 +982,12 @@ code = '''
CFG->erase(CFG, tinfo.block) => 0;
CFG->prog(CFG, tinfo.block, 0,
clobber_buf, BLOCK_SIZE) => 0;
if (c == 2-1) {
if (k == i+1) {
lfsr_traversal_close(&lfs, &t) => 0;
goto clobbered;
} else {
c += 1;
}
} else {
k += 1;
}
k += 1;
}
}
@@ -1035,7 +1027,7 @@ defines.CKMETADATA = [false, true]
defines.CKDATA = [false, true]
if = '(SIZE*N)/BLOCK_SIZE <= 32'
code = '''
for (lfs_block_t i = 0;; i++) {
for (lfs_block_t i = 0;; i += 2) {
// a bit hacky, but this catches infinite loops
assert(i < 2*BLOCK_COUNT);
@@ -1064,7 +1056,6 @@ code = '''
lfsr_traversal_t t;
lfsr_traversal_open(&lfs, &t, 0) => 0;
lfs_block_t k = 0;
lfs_block_t c = 0;
for (lfs_block_t j = 0;; j++) {
assert(j < 2*BLOCK_COUNT);
@@ -1081,7 +1072,7 @@ code = '''
}
if (tinfo.btype == LFS_BTYPE_MDIR) {
if (k == i) {
if (k == i || k == i+1) {
// clobber this block
printf("clobbering 0x%x\n", tinfo.block);
uint8_t clobber_buf[BLOCK_SIZE];
@@ -1089,15 +1080,12 @@ code = '''
CFG->erase(CFG, tinfo.block) => 0;
CFG->prog(CFG, tinfo.block, 0,
clobber_buf, BLOCK_SIZE) => 0;
if (c == 2-1) {
if (k == i+1) {
lfsr_traversal_close(&lfs, &t) => 0;
goto clobbered;
} else {
c += 1;
}
} else {
k += 1;
}
k += 1;
}
}
@@ -1174,9 +1162,8 @@ code = '''
clobber_buf, BLOCK_SIZE) => 0;
lfsr_traversal_close(&lfs, &t) => 0;
goto clobbered;
} else {
k += 1;
}
k += 1;
}
}
@@ -1269,9 +1256,8 @@ code = '''
clobber_buf, BLOCK_SIZE) => 0;
lfsr_traversal_close(&lfs, &t) => 0;
goto clobbered;
} else {
k += 1;
}
k += 1;
}
}
@@ -1366,9 +1352,8 @@ code = '''
clobber_buf, BLOCK_SIZE) => 0;
lfsr_traversal_close(&lfs, &t) => 0;
goto clobbered;
} else {
k += 1;
}
k += 1;
}
}
@@ -3004,9 +2989,6 @@ code = '''
i += 1;
}
// traverse the mtree root
lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
assert(tinfo.btype == LFS_BTYPE_BTREE);
// traverse one mdir
lfsr_traversal_read(&lfs, &t, &tinfo) => 0;
assert(tinfo.btype == LFS_BTYPE_MDIR);
@@ -3271,7 +3253,7 @@ code = '''
lfsr_unmount(&lfs) => 0;
'''
# TODO
#[cases.test_traversal_mutation_mroot_chain_split]
#[cases.test_traversal_mutation_mroot_chain_split_l]
#[cases.test_traversal_mutation_mroot_chain_split_r]
@@ -3279,6 +3261,18 @@ code = '''
#[cases.test_traversal_mutation_mtree_split_l]
#[cases.test_traversal_mutation_mtree_split_r]
# TODO also rename/remove -> mv/rm above?
#[cases.test_traversal_mutation_rm_bleaf]
#[cases.test_traversal_mutation_rm_btree]
#[cases.test_traversal_mutation_rm_bshrub]
#[cases.test_traversal_mutation_mv_src_bleaf]
#[cases.test_traversal_mutation_mv_src_btree]
#[cases.test_traversal_mutation_mv_src_bshrub]
#[cases.test_traversal_mutation_mv_dst_bleaf]
#[cases.test_traversal_mutation_mv_dst_btree]
#[cases.test_traversal_mutation_mv_dst_bshrub]
# many/fuzz tests mixed with traversals