From fadf0cbd0edafd27af26379d11d4e5939ee13170 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Tue, 22 Jul 2025 11:50:30 -0500 Subject: [PATCH] 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. --- lfs3.c | 47 +++++++++++++++++++++++++++++++---------------- lfs3.h | 14 +++----------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/lfs3.c b/lfs3.c index 87ac2bae..6f2d7eb3 100644 --- a/lfs3.c +++ b/lfs3.c @@ -9783,6 +9783,13 @@ enum lfs3_tstate { 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) { trv->b.h.flags = lfs3_o_typeflags(LFS3_type_TRV) | 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[1] = -1; 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->u.mtortoise.blocks[0] = -1; - trv->u.mtortoise.blocks[1] = -1; - trv->u.mtortoise.step = 0; - trv->u.mtortoise.power = 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, // 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( trv->b.h.mdir.r.blocks, - trv->u.mtortoise.blocks) == 0) { + mtortoise->blocks) == 0) { LFS3_ERROR("Cycle detected during mtree traversal " "0x{%"PRIx32",%"PRIx32"}", trv->b.h.mdir.r.blocks[0], trv->b.h.mdir.r.blocks[1]); return LFS3_ERR_CORRUPT; } - if (trv->u.mtortoise.step == (1U << trv->u.mtortoise.power)) { - trv->u.mtortoise.blocks[0] = trv->b.h.mdir.r.blocks[0]; - trv->u.mtortoise.blocks[1] = trv->b.h.mdir.r.blocks[1]; - trv->u.mtortoise.step = 0; - trv->u.mtortoise.power += 1; + if (mtortoise->dist == (1U << mtortoise->power)) { + mtortoise->blocks[0] = trv->b.h.mdir.r.blocks[0]; + mtortoise->blocks[1] = trv->b.h.mdir.r.blocks[1]; + mtortoise->dist = 0; + mtortoise->power += 1; } - trv->u.mtortoise.step += 1; + mtortoise->dist += 1; bptr_->d.u.buffer = (const uint8_t*)&trv->b.h.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 - trv->u.bid = -2; + trv->bid = -2; lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_MTREE); 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 if (err != LFS3_ERR_NOENT) { // start traversing - trv->u.bid = -2; + trv->bid = -2; lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_BTREE); continue; @@ -9990,7 +10005,7 @@ static lfs3_stag_t lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_trv_t *trv, // transition to traversing the file const lfs3_file_t *file = (const lfs3_file_t*)trv->h; trv->b.shrub = file->b.shrub; - trv->u.bid = -2; + trv->bid = -2; lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_HBTREE); continue; #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_HBTREE:; // traverse through our bshrub/btree - tag = lfs3_bshrub_traverse(lfs3, &trv->b, trv->u.bid+1, - &trv->u.bid, NULL, &data); + tag = lfs3_bshrub_traverse(lfs3, &trv->b, trv->bid+1, + &trv->bid, NULL, &data); if (tag < 0) { if (tag == LFS3_ERR_NOENT) { // clear the bshrub state diff --git a/lfs3.h b/lfs3.h index 3eed2892..881309fd 100644 --- a/lfs3.h +++ b/lfs3.h @@ -771,20 +771,12 @@ typedef struct lfs3_dir { // littlefs traversal type typedef struct lfs3_trv { // mdir/bshrub/btree state, this also includes our traversal - // state machine + // state machine and cycle detection state lfs3_bshrub_t b; // opened file state lfs3_handle_t *h; - union { - // cycle detection state, only valid when traversing the mroot chain - struct { - lfs3_block_t blocks[2]; - lfs3_block_t step; - uint8_t power; - } mtortoise; - // btree traversal state - lfs3_sbid_t bid; - } u; + // bshrub/btree traversal state + lfs3_sbid_t bid; // recalculate gcksum when traversing with ckmeta uint32_t gcksum;