trv: Moved cycle detection tortoise into the shrub leaf

This forces our cycle detection tortoise (previously trv.u.mtortoise),
into the unused shrub leaf via pointer shenanigans.

This reclaims the remaining stack (and apparently code) we theoretically
gained from the btree traversal rework, up until the compiler got in the
way:

           code          stack          ctx
  before: 36876           2384          684
  after:  36852 (-0.1%)   2368 (-0.7%)  684 (+0.0%)

And it only required some _questionably_ defined behavior.

---

It's probably not well-defined behavior, but trying to understand what
the standard actually means on this is giving me a headache. I think I
have to agree C99+strict-aliasing lost the plot on this one. Note
mtortoise is only ever written/read through the same type.

What I want:

  lfs3_trv_t:          lfs3_bshrub_t:       lfs3_handle_t:
  .---+---+---+---. .. .---+---+---+---. .. .---+---+---+---.
  |     handle    |    |     handle    |    |     handle    |
  |               |    |               |    |               |
  +---+---+---+---+    +---+---+---+---+ .. '---+---+---+---'
  |   root rbyd   |    |   root rbyd   |
  |               |    |               |    lfs3_mtortoise_t:
  +---+---+---+---+    +---+---+---+---+ .. .---+---+---+---.
  |   leaf rbyd   |    |   leaf rbyd   |    |   mtortoise   |
  |               |    |               |    |               |
  +---+---+---+---+    +---+---+---+---+ .. '---+---+---+---'
  | staging rbyd  |    | staging rbyd  |
  |               |    |               |
  +---+---+---+---+ .. '---+---+---+---'
  |               |
  :               :

But I'm starting to think this is simply not possible in modern C.

At least this shows what is theoretically possible if we didn't have to
fight the compiler.
This commit is contained in:
Christopher Haster
2025-07-22 11:50:30 -05:00
parent 70872b5703
commit fadf0cbd0e
2 changed files with 34 additions and 27 deletions
+31 -16
View File
@@ -9783,6 +9783,13 @@ enum lfs3_tstate {
LFS3_TSTATE_DONE = 8, LFS3_TSTATE_DONE = 8,
}; };
// tortoise for cycle detection
typedef struct lfs3_mtortoise {
lfs3_block_t blocks[2];
lfs3_block_t dist;
uint8_t power;
} lfs3_mtortoise_t;
static void lfs3_trv_init(lfs3_trv_t *trv, uint32_t flags) { static void lfs3_trv_init(lfs3_trv_t *trv, uint32_t flags) {
trv->b.h.flags = lfs3_o_typeflags(LFS3_type_TRV) trv->b.h.flags = lfs3_o_typeflags(LFS3_type_TRV)
| lfs3_t_tstateflags(LFS3_TSTATE_MROOTANCHOR) | lfs3_t_tstateflags(LFS3_TSTATE_MROOTANCHOR)
@@ -9792,11 +9799,15 @@ static void lfs3_trv_init(lfs3_trv_t *trv, uint32_t flags) {
trv->b.h.mdir.r.blocks[0] = -1; trv->b.h.mdir.r.blocks[0] = -1;
trv->b.h.mdir.r.blocks[1] = -1; trv->b.h.mdir.r.blocks[1] = -1;
lfs3_bshrub_init(&trv->b); lfs3_bshrub_init(&trv->b);
// bit of a hack, but we reuse the shrub's leaf as a tortoise for
// cycle detection to save memory, see lfs3_mtree_traverse_
lfs3_mtortoise_t *mtortoise = (lfs3_mtortoise_t*)&trv->b.shrub.leaf.r;
LFS3_ASSERT(sizeof(lfs3_mtortoise_t) <= sizeof(trv->b.shrub.leaf.r));
mtortoise->blocks[0] = -1;
mtortoise->blocks[1] = -1;
mtortoise->dist = 0;
mtortoise->power = 0;
trv->h = NULL; trv->h = NULL;
trv->u.mtortoise.blocks[0] = -1;
trv->u.mtortoise.blocks[1] = -1;
trv->u.mtortoise.step = 0;
trv->u.mtortoise.power = 0;
trv->gcksum = 0; trv->gcksum = 0;
} }
@@ -9859,22 +9870,26 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_trv_t *trv,
// btree inner nodes require checksums of their pointers, // btree inner nodes require checksums of their pointers,
// so creating a valid cycle is actually quite difficult // so creating a valid cycle is actually quite difficult
// //
// a bit of a hack, but we reuse the shrub's leaf as a
// tortoise to save memory
lfs3_mtortoise_t *mtortoise
= (lfs3_mtortoise_t*)&trv->b.shrub.leaf.r;
if (lfs3_mptr_cmp( if (lfs3_mptr_cmp(
trv->b.h.mdir.r.blocks, trv->b.h.mdir.r.blocks,
trv->u.mtortoise.blocks) == 0) { mtortoise->blocks) == 0) {
LFS3_ERROR("Cycle detected during mtree traversal " LFS3_ERROR("Cycle detected during mtree traversal "
"0x{%"PRIx32",%"PRIx32"}", "0x{%"PRIx32",%"PRIx32"}",
trv->b.h.mdir.r.blocks[0], trv->b.h.mdir.r.blocks[0],
trv->b.h.mdir.r.blocks[1]); trv->b.h.mdir.r.blocks[1]);
return LFS3_ERR_CORRUPT; return LFS3_ERR_CORRUPT;
} }
if (trv->u.mtortoise.step == (1U << trv->u.mtortoise.power)) { if (mtortoise->dist == (1U << mtortoise->power)) {
trv->u.mtortoise.blocks[0] = trv->b.h.mdir.r.blocks[0]; mtortoise->blocks[0] = trv->b.h.mdir.r.blocks[0];
trv->u.mtortoise.blocks[1] = trv->b.h.mdir.r.blocks[1]; mtortoise->blocks[1] = trv->b.h.mdir.r.blocks[1];
trv->u.mtortoise.step = 0; mtortoise->dist = 0;
trv->u.mtortoise.power += 1; mtortoise->power += 1;
} }
trv->u.mtortoise.step += 1; mtortoise->dist += 1;
bptr_->d.u.buffer = (const uint8_t*)&trv->b.h.mdir; bptr_->d.u.buffer = (const uint8_t*)&trv->b.h.mdir;
return LFS3_TAG_MDIR; return LFS3_TAG_MDIR;
@@ -9889,7 +9904,7 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_trv_t *trv,
} }
// transition to traversing the mtree // transition to traversing the mtree
trv->u.bid = -2; trv->bid = -2;
lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_MTREE); lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_MTREE);
continue; continue;
@@ -9945,7 +9960,7 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_trv_t *trv,
// here, lfs3_bshrub_fetch ignores these for us // here, lfs3_bshrub_fetch ignores these for us
if (err != LFS3_ERR_NOENT) { if (err != LFS3_ERR_NOENT) {
// start traversing // start traversing
trv->u.bid = -2; trv->bid = -2;
lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_BTREE); lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_BTREE);
continue; continue;
@@ -9990,7 +10005,7 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_trv_t *trv,
// transition to traversing the file // transition to traversing the file
const lfs3_file_t *file = (const lfs3_file_t*)trv->h; const lfs3_file_t *file = (const lfs3_file_t*)trv->h;
trv->b.shrub = file->b.shrub; trv->b.shrub = file->b.shrub;
trv->u.bid = -2; trv->bid = -2;
lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_HBTREE); lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_HBTREE);
continue; continue;
#endif #endif
@@ -10002,8 +10017,8 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_trv_t *trv,
case LFS3_TSTATE_BTREE:; case LFS3_TSTATE_BTREE:;
case LFS3_TSTATE_HBTREE:; case LFS3_TSTATE_HBTREE:;
// traverse through our bshrub/btree // traverse through our bshrub/btree
tag = lfs3_bshrub_traverse(lfs3, &trv->b, trv->u.bid+1, tag = lfs3_bshrub_traverse(lfs3, &trv->b, trv->bid+1,
&trv->u.bid, NULL, &data); &trv->bid, NULL, &data);
if (tag < 0) { if (tag < 0) {
if (tag == LFS3_ERR_NOENT) { if (tag == LFS3_ERR_NOENT) {
// clear the bshrub state // clear the bshrub state
+3 -11
View File
@@ -771,20 +771,12 @@ typedef struct lfs3_dir {
// littlefs traversal type // littlefs traversal type
typedef struct lfs3_trv { typedef struct lfs3_trv {
// mdir/bshrub/btree state, this also includes our traversal // mdir/bshrub/btree state, this also includes our traversal
// state machine // state machine and cycle detection state
lfs3_bshrub_t b; lfs3_bshrub_t b;
// opened file state // opened file state
lfs3_handle_t *h; lfs3_handle_t *h;
union { // bshrub/btree traversal state
// cycle detection state, only valid when traversing the mroot chain lfs3_sbid_t bid;
struct {
lfs3_block_t blocks[2];
lfs3_block_t step;
uint8_t power;
} mtortoise;
// btree traversal state
lfs3_sbid_t bid;
} u;
// recalculate gcksum when traversing with ckmeta // recalculate gcksum when traversing with ckmeta
uint32_t gcksum; uint32_t gcksum;