Files
littlefs/tests
Christopher Haster d1d69c0a52 trv: Greatly simplified filesystem traversal
The main idea here is to drop the flag-encoded tstate state machine, and
replace it with a matrix controlled by special mid + bid values:

                    -- mid ->
             -5   -4   -3   -2 >=-1
  bid   -2    x    x              x  --> mdir
   v  >=-1         x  gbm  gbm    x  --> bshrub/btree

              '----|----|----|----|----> mroot anchor
                   '----|----|----|----> mroot chain + mtree
                        '----|----|----> gbmap   (in-ram gbmap)
                             '----|----> gbmap_p (on-disk gbmap)
                                  '----> file bshrubs/btrees

This was motivated by the observation that everything in our filesystem
can be modeled as mdir + bshrub/btree tuples, as long as some states are
noops. And we can cleanly encode these tuples in the unused negative
mid + bid ranges without needing an explicit state machine.

Well, that and the previous tstate state machine approach being an ugly
pile of switch cases and messy logic.

Note though that some mids may need to traverse multiple mdirs/bshrub/
btrees:

- The mroot chain + mtree (mid=-4) needs to traverse all mroots in the
  mroot chain, and detect any cycles.

- File mdirs (mid>=-1) need to traverse both the on-disk bshrub/btree
  and any opened file handles' bshrubs/btrees before moving onto the
  next mid.

  This grows O(n^2) because all file handles are in one big unsorted
  linked-list, but as usual we don't care.

In addition to the greatly simplified traversal logic, the new state
matrix simplifies traversal clobbering: Setting bid=-2 always forces a
bshrub/btree refetch.

This comes at the cost of traversal _precision_, i.e. we can now revisit
previously visited bshrub/btree nodes. But I think this is well worth it
for more robust traversal clobbering. Traversal clobbering is delicate
and difficult to get right.

Besides, we can already revisit blocks due to CoW references, so what's
the harm in revisiting blocks when under mutation?

---

The simpler traversal logic leads to a nice amount of code savings
across the board:

                 code          stack          ctx
  before:       36476           2304          660
  after:        35940 (-1.5%)   2280 (-1.0%)  660 (+0.0%)

                 code          stack          ctx
  gbmap before: 39524           2320          772
  gbmap after:  38916 (-1.5%)   2296 (-1.0%)  772 (+0.0%)

                 code          stack          ctx
  gc before:    36548           2304          804
  gc after:     36012 (-1.5%)   2280 (-1.0%)  776 (-3.5%)

Note the ctx savings in LFS3_GC mode. Most of the stack/ctx savings
comes from the smaller lfs3_mtrv_t struct, which no longer needs to
stage bshrubs (we no longer care about bshrubs across mdir commit as a
part of the above clobbering simplifications):

                before  after
  lfs3_mtrv_t:     128    100 (-21.9%)
  lfs3_mgc_t:      128    100 (-21.9%)
  lfs3_trv_t:      136    108 (-20.6%)

Unfortunately, the simpler clobbering means now any gc work needs the
block queue (i.e. lfs3_trv_t), solely so clobbering the block queue
doesn't clobber unallocated memory. Not great but hopefully fixable.

---

Some other notes:

- As a part of simplifying traversal clobbering, everything is triggered
  by lfs3_alloc_ckpoint (via lfs3_trv_ckpoint_).

  This may clobber traversals more than is strictly necessary, but
  that's kinda the idea. Better safe than sorry.

  And no more need to explicit lfs3_handle_clobber calls is nice.

- Opened file handle iteration is now tracked by the traversal handle's
  position in the handle linked-list, instead of a separate handle
  pointer. This means one less thing to disentangle and makes traversals
  no longer a special case for things like lfs3_handle_close.

  You may think this bumps traversals up to O(n^3) in-ram, but because
  we only ever visit each unique handle + mid once, we can keep the
  total O(n^2) if we're smart about linked-list updates!

- lfs3_mdir_commit needed to be tweaked to accept mids<=-1, instead of
  just mid=-1 for the mroot. Unfortunately I don't know how much this
  costs on its own.

- The reorganization of lfs3_mtrv_t means lfs3_mtortoise_t gets its own
  struct again!

- No more tstate state machine also frees up a big chunk of the
  traversal flag space, which was getting pretty cramped.
2025-11-08 19:46:22 -06:00
..
2025-07-18 18:29:41 -05:00