trv: Simplified MUTATED/DIRTY flags, no more swapping

A bit less simplified than I hoped, we don't _strictly_ need both
LFS3_t_DIRTY + LFS3_t_MUTATED if we're ok with either (1) making
multiple passes to confirm fixorphans succeeded or (2) clear the COMPACT
flag after one pass (which may introduce new uncompacted metadata). But
both of these have downsides, and we're not _that_ stressed for flag
space yet...

So keeping all three of:

  LFS3_t_DIRTY      0x04000000  Filesystem modified outside traversal
  LFS3_t_MUTATED    0x02000000  Filesystem modified during traversal
  LFS3_t_CKPOINTED  0x01000000  Filesystem ckpointed during traversal

But I did manage to get rid of the bit swapping by tweaking LFS3_t_DIRTY
to imply LFS3_t_MUTATED instead of being exclusive. This removes the
"failed" gotos in lfs3_mtree_gc and makes things a bit more readable.

---

I also split lfs3_fs/handle_clobber into separate lfs3_fs/handle_clobber
and lfs3_fs/handle_mutate functions. This added a bit of code, but I
think is worth it for a simpler internal API. A confusing internal API
is no good.

In total these simplifications saved a bit of code:

                 code          stack          ctx
  before:       37208           2360          684
  after:        37176 (-0.1%)   2360 (+0.0%)  684 (+0.0%)

                 code          stack          ctx
  gbmap before: 40100           2432          848
  gbmap after:  40060 (-0.1%)   2432 (+0.0%)  848 (+0.0%)
This commit is contained in:
Christopher Haster
2025-10-15 15:14:35 -05:00
parent 5a7e0c2b58
commit 06bc4dff04
3 changed files with 61 additions and 71 deletions

120
lfs3.c
View File

@ -7460,10 +7460,6 @@ static inline void lfs3_t_setbtype(uint32_t *flags, uint8_t btype) {
*flags = (*flags & ~LFS3_t_BTYPE) | lfs3_t_btypeflags(btype);
}
static inline bool lfs3_t_isckpointed(uint32_t flags) {
return flags & LFS3_t_CKPOINTED;
}
static inline bool lfs3_t_isdirty(uint32_t flags) {
return flags & LFS3_t_DIRTY;
}
@ -7472,9 +7468,8 @@ static inline bool lfs3_t_ismutated(uint32_t flags) {
return flags & LFS3_t_MUTATED;
}
static inline uint32_t lfs3_t_swapdirty(uint32_t flags) {
uint32_t x = ((flags >> 24) ^ (flags >> 25)) & 0x1;
return flags ^ (x << 24) ^ (x << 25);
static inline bool lfs3_t_isckpointed(uint32_t flags) {
return flags & LFS3_t_CKPOINTED;
}
// mount flags
@ -7606,15 +7601,16 @@ static void lfs3_handle_open(lfs3_t *lfs3, lfs3_handle_t *h) {
}
// needed in lfs3_handle_close
static void lfs3_handle_clobber(lfs3_t *lfs3, const lfs3_handle_t *h,
uint32_t flags);
static void lfs3_handle_clobber(lfs3_t *lfs3, const lfs3_handle_t *h);
static void lfs3_handle_close(lfs3_t *lfs3, lfs3_handle_t *h) {
LFS3_ASSERT(lfs3_handle_isopen(lfs3, h));
// make sure we're not entangled in any traversals, note we don't
// set the dirty bit here
// make sure we're not entangled in any traversals
//
// this isn't necessary in rdonly mode since it's not possible for
// handles to become unsynced
#ifndef LFS3_RDONLY
lfs3_handle_clobber(lfs3, h, 0);
lfs3_handle_clobber(lfs3, h);
#endif
// remove from opened list
for (lfs3_handle_t **h_ = &lfs3->handles; *h_; h_ = &(*h_)->next) {
@ -7647,15 +7643,12 @@ static bool lfs3_mid_isopen(const lfs3_t *lfs3,
// needed in lfs3_handle_clobber
static void lfs3_trv_clobber(lfs3_t *lfs3, lfs3_trv_t *trv);
// clobber any traversals referencing our mdir
// clobber traversals referencing a handle
#ifndef LFS3_RDONLY
static void lfs3_handle_clobber(lfs3_t *lfs3, const lfs3_handle_t *h,
uint32_t flags) {
static void lfs3_handle_clobber(lfs3_t *lfs3, const lfs3_handle_t *h) {
for (lfs3_handle_t *h_ = lfs3->handles; h_; h_ = h_->next) {
if (lfs3_o_type(h_->flags) == LFS3_type_TRV) {
h_->flags |= flags;
if (h && ((lfs3_trv_t*)h_)->h == h) {
if (((lfs3_trv_t*)h_)->h == h) {
lfs3_trv_clobber(lfs3, (lfs3_trv_t*)h_);
}
}
@ -7663,13 +7656,26 @@ static void lfs3_handle_clobber(lfs3_t *lfs3, const lfs3_handle_t *h,
}
#endif
// clobber all traversals
// mark all traversals as mutated
#ifndef LFS3_RDONLY
static void lfs3_fs_clobber(lfs3_t *lfs3, uint32_t flags) {
lfs3_handle_clobber(lfs3, NULL, flags);
static void lfs3_fs_mutate(lfs3_t *lfs3) {
for (lfs3_handle_t *h_ = lfs3->handles; h_; h_ = h_->next) {
if (lfs3_o_type(h_->flags) == LFS3_type_TRV) {
// mark as dirty + mutated, self-mutating traversals should
// clear the dirty bit
h_->flags |= LFS3_t_DIRTY | LFS3_t_MUTATED;
}
}
}
#endif
// mark as mutated and clobber any traversals referencing a handle
#ifndef LFS3_RDONLY
static void lfs3_handle_mutate(lfs3_t *lfs3, const lfs3_handle_t *h) {
lfs3_handle_clobber(lfs3, h);
lfs3_fs_mutate(lfs3);
}
#endif
/// Global-state things ///
@ -9663,7 +9669,7 @@ static int lfs3_mdir_commit_(lfs3_t *lfs3, lfs3_mdir_t *mdir,
// removed?
if (h->mdir.mid < mid_ - rattrs[i].weight) {
// opened files should turn into stickynote, not
// be removed
// have their mid removed
LFS3_ASSERT(lfs3_o_type(h->flags) != LFS3_TYPE_REG);
h->flags |= LFS3_o_ZOMBIE;
h->mdir.mid = mid_;
@ -9765,8 +9771,8 @@ static int lfs3_mdir_commit_(lfs3_t *lfs3, lfs3_mdir_t *mdir,
// update any gstate changes
lfs3_fs_commitgdelta(lfs3);
// mark all traversals as dirty
lfs3_fs_clobber(lfs3, LFS3_t_DIRTY);
// mark all traversals as mutated
lfs3_fs_mutate(lfs3);
// we may have touched any number of mdirs, so assume uncompacted
// until lfs3_fs_gc can prove otherwise
@ -10633,8 +10639,8 @@ static lfs3_stag_t lfs3_mtree_gc(lfs3_t *lfs3, lfs3_trv_t *trv,
// note because we bail as soon as a ckpoint is triggered
// (lfs3_t_isckpointed), we don't need to traverse this
if (lfs3_t_isrebuildgbmap(trv->b.h.flags)
&& !lfs3_t_ismtreeonly(trv->b.h.flags)
&& lfs3_f_isgbmap(lfs3->flags)) {
&& lfs3_f_isgbmap(lfs3->flags)
&& !lfs3_t_ismtreeonly(trv->b.h.flags)) {
// at least checkpoint the lookahead buffer
lfs3_alloc_ckpoint_(lfs3);
@ -10667,16 +10673,10 @@ dropped:;
if (tag == LFS3_ERR_NOENT) {
goto eot;
}
// don't goto failed here, we haven't swapped dirty/mutated
// flags yet
return tag;
}
#ifndef LFS3_RDONLY
// swap dirty/mutated flags while in lfs3_mtree_gc
trv->b.h.flags = lfs3_t_swapdirty(trv->b.h.flags);
int err;
// mark in-use blocks in lookahead?
#ifndef LFS3_2BONLY
if (lfs3_t_islookahead(trv->b.h.flags)
@ -10689,13 +10689,13 @@ dropped:;
// mark in-use blocks in gbmap?
#ifdef LFS3_GBMAP
if (lfs3_t_isrebuildgbmap(trv->b.h.flags)
&& !lfs3_t_ismtreeonly(trv->b.h.flags)
&& lfs3_f_isgbmap(lfs3->flags)
&& !lfs3_t_ismtreeonly(trv->b.h.flags)
&& !lfs3_t_isckpointed(trv->b.h.flags)) {
err = lfs3_gbmap_markbptr(lfs3, &trv->gbmap_, tag, bptr_,
int err = lfs3_gbmap_markbptr(lfs3, &trv->gbmap_, tag, bptr_,
LFS3_TAG_BMINUSE);
if (err) {
goto failed;
return err;
}
// keep our own ckpointed flag clear
@ -10708,19 +10708,19 @@ dropped:;
&& lfs3_t_ismkconsistent(lfs3->flags)
&& tag == LFS3_TAG_MDIR) {
lfs3_mdir_t *mdir = (lfs3_mdir_t*)bptr_->d.u.buffer;
err = lfs3_mdir_mkconsistent(lfs3, mdir);
uint32_t dirty = trv->b.h.flags;
int err = lfs3_mdir_mkconsistent(lfs3, mdir);
if (err) {
goto failed;
return err;
}
// reset dirty flag
trv->b.h.flags &= ~LFS3_t_DIRTY | dirty;
// make sure we clear any zombie flags
trv->b.h.flags &= ~LFS3_o_ZOMBIE;
// did this drop our mdir?
#ifndef LFS3_2BONLY
if (mdir->mid != -1 && mdir->r.weight == 0) {
// swap back dirty/mutated flags
trv->b.h.flags = lfs3_t_swapdirty(trv->b.h.flags);
// continue traversal
lfs3_t_settstate(&trv->b.h.flags, LFS3_TSTATE_MDIRS);
goto dropped;
@ -10747,24 +10747,18 @@ dropped:;
? lfs3->cfg->gc_compact_thresh
: lfs3->cfg->block_size - lfs3->cfg->block_size/8);
// compact the mdir
err = lfs3_mdir_compact(lfs3, mdir);
uint32_t dirty = trv->b.h.flags;
int err = lfs3_mdir_compact(lfs3, mdir);
if (err) {
goto failed;
return err;
}
// reset dirty flag
trv->b.h.flags &= ~LFS3_t_DIRTY | dirty;
}
// swap back dirty/mutated flags
trv->b.h.flags = lfs3_t_swapdirty(trv->b.h.flags);
#endif
return tag;
failed:;
#ifndef LFS3_RDONLY
// swap back dirty/mutated flags
trv->b.h.flags = lfs3_t_swapdirty(trv->b.h.flags);
return err;
#endif
eot:;
#ifndef LFS3_RDONLY
// was lookahead scan successful?
@ -10779,8 +10773,8 @@ eot:;
// was gbmap rebuild successful?
#ifdef LFS3_GBMAP
if (lfs3_t_isrebuildgbmap(trv->b.h.flags)
&& !lfs3_t_ismtreeonly(trv->b.h.flags)
&& lfs3_f_isgbmap(lfs3->flags)
&& !lfs3_t_ismtreeonly(trv->b.h.flags)
&& !lfs3_t_isckpointed(trv->b.h.flags)) {
lfs3_alloc_adoptgbmap(lfs3, &trv->gbmap_, lfs3->lookahead.ckpoint);
}
@ -10795,7 +10789,6 @@ eot:;
// was compaction successful? note we may need multiple passes if
// we want to be sure everything is compacted
if (lfs3_t_iscompact(trv->b.h.flags)
&& !lfs3_t_isdirty(trv->b.h.flags)
&& !lfs3_t_ismutated(trv->b.h.flags)) {
lfs3->flags &= ~LFS3_I_COMPACT;
}
@ -14353,7 +14346,7 @@ lfs3_ssize_t lfs3_file_write(lfs3_t *lfs3, lfs3_file_t *file,
}
// clobber entangled traversals
lfs3_handle_clobber(lfs3, &file->b.h, LFS3_t_DIRTY);
lfs3_handle_mutate(lfs3, &file->b.h);
// mark as unsynced in case we fail
file->b.h.flags |= LFS3_o_UNSYNC;
@ -14497,7 +14490,7 @@ int lfs3_file_flush(lfs3_t *lfs3, lfs3_file_t *file) {
#ifndef LFS3_RDONLY
// clobber entangled traversals
lfs3_handle_clobber(lfs3, &file->b.h, LFS3_t_DIRTY);
lfs3_handle_mutate(lfs3, &file->b.h);
int err;
// flush our cache
@ -15003,7 +14996,7 @@ int lfs3_file_truncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
}
// clobber entangled traversals
lfs3_handle_clobber(lfs3, &file->b.h, LFS3_t_DIRTY);
lfs3_handle_mutate(lfs3, &file->b.h);
// mark as unsynced in case we fail
file->b.h.flags |= LFS3_o_UNSYNC;
@ -15096,7 +15089,7 @@ int lfs3_file_fruncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
}
// clobber entangled traversals
lfs3_handle_clobber(lfs3, &file->b.h, LFS3_t_DIRTY);
lfs3_handle_mutate(lfs3, &file->b.h);
// mark as unsynced in case we fail
file->b.h.flags |= LFS3_o_UNSYNC;
@ -17444,16 +17437,13 @@ int lfs3_trv_read(lfs3_t *lfs3, lfs3_trv_t *trv,
#ifndef LFS3_RDONLY
if (lfs3_t_ismkconsistent(trv->b.h.flags)
&& lfs3_grm_count(lfs3) > 0) {
// swap dirty/mutated flags while mutating
trv->b.h.flags = lfs3_t_swapdirty(trv->b.h.flags);
uint32_t dirty = trv->b.h.flags;
int err = lfs3_fs_fixgrm(lfs3);
if (err) {
trv->b.h.flags = lfs3_t_swapdirty(trv->b.h.flags);
return err;
}
trv->b.h.flags = lfs3_t_swapdirty(trv->b.h.flags);
// reset dirty flag
trv->b.h.flags &= ~LFS3_t_DIRTY | dirty;
}
#endif
@ -17552,9 +17542,9 @@ static int lfs3_trv_rewind_(lfs3_t *lfs3, lfs3_trv_t *trv) {
// reset traversal
lfs3_trv_init(trv,
trv->b.h.flags
& ~LFS3_t_CKPOINTED
& ~LFS3_t_DIRTY
& ~LFS3_t_MUTATED
& ~LFS3_t_CKPOINTED
& ~LFS3_t_TSTATE);
// and clear any pending blocks

6
lfs3.h
View File

@ -341,10 +341,10 @@ enum lfs3_btype {
#define LFS3_t_TSTATE 0x000f0000 // The current traversal state
#define LFS3_t_BTYPE 0x00f00000 // The current block type
#define LFS3_t_ZOMBIE 0x08000000 // File has been removed
#define LFS3_t_DIRTY 0x04000000 // Filesystem modified outside traversal
#define LFS3_t_MUTATED 0x02000000 // Filesystem modified during traversal
#define LFS3_t_CKPOINTED \
0x04000000 // Filesystem ckpointed during traversal
#define LFS3_t_DIRTY 0x02000000 // Filesystem modified during traversal
#define LFS3_t_MUTATED 0x01000000 // Filesystem modified by traversal
0x01000000 // Filesystem ckpointed during traversal
// GC flags
#ifndef LFS3_RDONLY

View File

@ -166,9 +166,9 @@ FLAGS = [
('^_BTREE', 0x00200000, "Btype = btree" ),
('^_DATA', 0x00300000, "Btype = data" ),
('t_ZOMBIE', 0x08000000, "File has been removed" ),
('t_CKPOINTED', 0x04000000, "Filesystem ckpointed during traversal" ),
('t_DIRTY', 0x02000000, "Filesystem modified during traversal" ),
('t_MUTATED', 0x01000000, "Filesystem modified by traversal" ),
('t_DIRTY', 0x04000000, "Filesystem modified outside traversal" ),
('t_MUTATED', 0x02000000, "Filesystem modified during traversal" ),
('t_CKPOINTED', 0x01000000, "Filesystem ckpointed during traversal" ),
# Block allocator flags
('alloc_ERASE', 0x00000001, "Please erase the block" ),