Reworked lfs3_file_truncate/fruncate to simplify crystallize

Now that we don't need to worry about losing data due to ungrafted
state, we can decide whether or not to discard leaves after
truncate/fruncate.

This simplifies lfs3_file_truncate/fruncate (and makes them much more
readable as a plus), but also lets us simplify lfs3_file_crystallize
since we no longer need to worry about implicit flushes.

lfs3_file_crystallize's call sites:

- lfs3_file_flush_ - We've already committed to flushing, so
  opportunistically clearing LFS3_o_UNFLUSH has no effect.

  lfs3_file_flush_'s logic should already take advantage of possible
  flushes anyways.

- lfs3_file_flush - We only call lfs3_file_crystallize _after_
  lfs3_file_flush_, so this has no effect.

This saves a bit more code and stack:

           code          stack          ctx
  before: 37588           2472          656
  after:  37468 (-0.3%)   2464 (-0.3%)  656 (+0.0%)
This commit is contained in:
Christopher Haster
2025-07-03 15:04:45 -05:00
parent e443af800b
commit 8365b27dea
+57 -97
View File
@@ -11969,9 +11969,6 @@ static int lfs3_file_lookupnext(lfs3_t *lfs3, const lfs3_file_t *file,
return 0;
}
// needed in lfs3_file_readnext
static int lfs3_file_crystallize(lfs3_t *lfs3, lfs3_file_t *file);
#ifndef LFS3_KVONLY
static lfs3_ssize_t lfs3_file_readnext(lfs3_t *lfs3, lfs3_file_t *file,
lfs3_off_t pos, uint8_t *buffer, lfs3_size_t size) {
@@ -12530,8 +12527,11 @@ static int lfs3_file_crystallize__(lfs3_t *lfs3, lfs3_file_t *file,
goto relocate;
}
// if we're resuming crystallization, block pointer shouldn't be
// truncated or anything
// only blocks can be uncrystallized
LFS3_ASSERT(lfs3_bptr_isbptr(&file->leaf.bptr));
LFS3_ASSERT(lfs3_bptr_iserased(&file->leaf.bptr));
// uncrystallized blocks shouldn't be truncated or anything
LFS3_ASSERT(file->leaf.pos - lfs3_bptr_off(&file->leaf.bptr)
== block_pos);
LFS3_ASSERT(lfs3_bptr_off(&file->leaf.bptr)
@@ -12792,41 +12792,26 @@ failed:;
#if !defined(LFS3_RDONLY) && !defined(LFS3_KVONLY) && !defined(LFS3_2BONLY)
static int lfs3_file_crystallize(lfs3_t *lfs3, lfs3_file_t *file) {
// TODO do we care about this possibility?
// is it possible for this to flush the cache?
bool flushing = (
file->cache.pos
>= file->leaf.pos + lfs3_bptr_size(&file->leaf.bptr));
// do nothing if our file is already crystallized
if (!lfs3_o_isuncryst(file->b.o.flags)) {
return 0;
}
// uncrystallized files must be unsynced
LFS3_ASSERT(lfs3_o_isunsync(file->b.o.flags));
// checkpoint the allocator
lfs3_alloc_ckpoint(lfs3);
// finish crystallizing
if (lfs3_o_isuncryst(file->b.o.flags)) {
// uncrystallized files must be unsynced
LFS3_ASSERT(lfs3_o_isunsync(file->b.o.flags));
// only blocks can be uncrystallized
LFS3_ASSERT(lfs3_bptr_isbptr(&file->leaf.bptr));
LFS3_ASSERT(lfs3_bptr_iserased(&file->leaf.bptr));
// checkpoint the allocator
lfs3_alloc_ckpoint(lfs3);
// finish crystallizing
int err = lfs3_file_crystallize_(lfs3, file,
file->leaf.pos - lfs3_bptr_off(&file->leaf.bptr), -1, -1,
file->cache.pos, file->cache.buffer, file->cache.size);
if (err) {
return err;
}
// we should have crystallized
LFS3_ASSERT(!lfs3_o_isuncryst(file->b.o.flags));
}
// eagerly mark as flushed if this included all of our cache
if (flushing
&& file->leaf.pos + lfs3_bptr_size(&file->leaf.bptr)
>= file->cache.pos + file->cache.size) {
file->b.o.flags &= ~LFS3_o_UNFLUSH;
int err = lfs3_file_crystallize_(lfs3, file,
file->leaf.pos - lfs3_bptr_off(&file->leaf.bptr), -1, -1,
0, NULL, 0);
if (err) {
return err;
}
// we should have crystallized
LFS3_ASSERT(!lfs3_o_isuncryst(file->b.o.flags));
return 0;
}
#endif
@@ -13974,27 +13959,6 @@ int lfs3_file_truncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
// mark as unsynced in case we fail
file->b.o.flags |= LFS3_o_UNSYNC;
// if our leaf is a fragment or will be fragmented, we need
// to go ahead and crystallize + discard it, otherwise we risk
// out-of-date fragments as btree commits move things around
//
// note this is mostly to match the behavior of fruncate, where we
// _really_ don't want to discard erased-state
if (!lfs3_bptr_isbptr(&file->leaf.bptr)
|| size_ - lfs3_min(file->leaf.pos, size_)
< lfs3_min(
lfs3->cfg->fragment_thresh,
lfs3->cfg->crystal_thresh)) {
#ifndef LFS3_2BONLY
err = lfs3_file_crystallize(lfs3, file);
if (err) {
goto failed;
}
#endif
lfs3_file_discardleaf(file);
}
// checkpoint the allocator
lfs3_alloc_ckpoint(lfs3);
// truncate our btree
@@ -14007,12 +13971,9 @@ int lfs3_file_truncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
}
// truncate our leaf
#ifndef LFS3_2BONLY
if (size_ < file->leaf.pos + lfs3_bptr_size(&file->leaf.bptr)) {
lfs3_bptr_claim(&file->leaf.bptr);
file->b.o.flags &= ~LFS3_o_UNCRYST;
}
#endif
//
// note we don't unconditionally discard to match fruncate, where we
// _really_ don't want to discard erased-state
file->leaf.bptr.data = LFS3_DATA_TRUNCATE(
file->leaf.bptr.data,
size_ - lfs3_min(file->leaf.pos, size_));
@@ -14020,6 +13981,25 @@ int lfs3_file_truncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
file->leaf.weight,
size_ - lfs3_min(file->leaf.pos, size_));
file->leaf.pos = lfs3_min(file->leaf.pos, size_);
#ifndef LFS3_2BONLY
// mark as crystallized if this truncates our erased-state
if (lfs3_bptr_off(&file->leaf.bptr)
+ lfs3_bptr_size(&file->leaf.bptr)
< lfs3_bptr_cksize(&file->leaf.bptr)) {
lfs3_bptr_claim(&file->leaf.bptr);
file->b.o.flags &= ~LFS3_o_UNCRYST;
}
#endif
// discard if our leaf is a fragment, is fragmented, or is completed
// truncated, we can't rely on any in-bshrub/btree state
if (!lfs3_bptr_isbptr(&file->leaf.bptr)
|| lfs3_bptr_size(&file->leaf.bptr)
< lfs3_min(
lfs3->cfg->fragment_thresh,
lfs3->cfg->crystal_thresh)
|| lfs3_bptr_size(&file->leaf.bptr) == 0) {
lfs3_file_discardleaf(file);
}
// truncate our cache
file->cache.size = lfs3_min(
@@ -14060,32 +14040,6 @@ int lfs3_file_fruncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
// mark as unsynced in case we fail
file->b.o.flags |= LFS3_o_UNSYNC;
// if our leaf is a fragment or will be fragmented, we need
// to go ahead and crystallize + discard it, otherwise we risk
// out-of-date fragments as btree commits move things around
//
// note that fruncate is commonly used when logging, where we
// _really_ don't want to discard erased-state, otherwise we'd just
// discard this unconditionally
if (!lfs3_bptr_isbptr(&file->leaf.bptr)
|| lfs3_bptr_size(&file->leaf.bptr) - lfs3_min(
lfs3_smax(
size - size_ - file->leaf.pos,
0),
lfs3_bptr_size(&file->leaf.bptr))
< lfs3_min(
lfs3->cfg->fragment_thresh,
lfs3->cfg->crystal_thresh)) {
#ifndef LFS3_2BONLY
err = lfs3_file_crystallize(lfs3, file);
if (err) {
goto failed;
}
#endif
lfs3_file_discardleaf(file);
}
// checkpoint the allocator
lfs3_alloc_ckpoint(lfs3);
// fruncate our btree
@@ -14098,14 +14052,10 @@ int lfs3_file_fruncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
}
// fruncate our leaf
#ifndef LFS3_2BONLY
if ((lfs3_soff_t)(size - size_)
> (lfs3_soff_t)(file->leaf.pos
+ lfs3_bptr_size(&file->leaf.bptr))) {
lfs3_bptr_claim(&file->leaf.bptr);
file->b.o.flags &= ~LFS3_o_UNCRYST;
}
#endif
//
// note we _really_ don't want to discard erased-state if possible,
// as fruncate is intended for logging operations, otherwise we'd
// just unconditionally discard the leaf and avoid this hassle
file->leaf.bptr.data = LFS3_DATA_FRUNCATE(
file->leaf.bptr.data,
lfs3_bptr_size(&file->leaf.bptr) - lfs3_min(
@@ -14121,6 +14071,16 @@ int lfs3_file_fruncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
file->leaf.pos -= lfs3_smin(
size - size_,
file->leaf.pos);
// discard if our leaf is a fragment, is fragmented, or is completed
// truncated, we can't rely on any in-bshrub/btree state
if (!lfs3_bptr_isbptr(&file->leaf.bptr)
|| lfs3_bptr_size(&file->leaf.bptr)
< lfs3_min(
lfs3->cfg->fragment_thresh,
lfs3->cfg->crystal_thresh)
|| lfs3_bptr_size(&file->leaf.bptr) == 0) {
lfs3_file_discardleaf(file);
}
// fruncate our cache
lfs3_memmove(file->cache.buffer,