Dropped lazy grafting, but kept lazy crystallization

This merges LFS3_o_GRAFT into LFS3_o_UNCRYST, simplifying the file write
path and avoiding the mess that is ungrafted leaves.

---

This goes for a different lazy crystallization/grafting strategy that
was overlooked before. Instead of requiring all leaves to be both
crystallized and grafted, we allow leaves to be uncrystallied, but they
_must_ be grafted (in-tree) at all times.

This gets us most of the rewrite preformance of lazy-crystallization,
without needing to worry about out-of-date file leaves.

Out-of-date file leaves were a headache for both code cost and concerns
around confusing filesystem states and related bugs.

Note LFS3_o_UNCRYST gets some extra behavior here:

- LFS3_o_UNCRYST indicates when crystallization is _necessary_, and no
  longer when crystallization is _possible_.

  We already keep track of when crystallization is _possible_ via bptr's
  erased-state, and this lets us control recrystallization in
  lfs3_file_flush_ without erased-state-clearing hacks (which probably
  wouldn't work with the future ddtree).

- We opportunistically clear the UNCRYST flag if it's not possible for
  future lfs3_file_crystallize_ calls to make progress:
  - When we crystallize a full block
  - When we hit the end of the file
  - When we hit a hole
  - When we hit an unaligned block

---

Note this does impact performance!

Unlike true lazy grafting, eagerly grafting means we're always
committing to the bshrub/btree more than is strictly necessary, and this
translates to more frequent btree node erases/compactions.

Current simulated benchmarks show a ~3x increase (~20us -> ~60us) in
write times for linear file writes on NOR flash.

However:

- The moment you need unaligned progs, this performance optimization
  goes out the window, as we need to graft bptrs before any padding
  fragments.

- This only kicks in once we start crystallizing. So any writes <
  crystal_thresh (both in new files and in between blocks) are forced
  to commit to the bshrub/btree every flush.

  This risks a difficult to predict performance characteristic.

- If you sync frequently (logging), we're forced to crystallize/graft
  anyways.

- The performance hit can be alleviated with either larger writes or
  larger caches, though I realize this goes against littlefs's
  "RAM-not-required" mantra.

Worst case, we can always bring back "lazy grafting" as a
high-performance option in the future.

Though note the above concerns around in-between/pre crystallization
performance. This may only make sense when cache_size >= both prog_size
and crystal_thresh.

And of course, there's a significant code tradeoff!

           code          stack          ctx
  before: 38020           2456          656
  after:  37588 (-1.1%)   2472 (+0.7%)  656 (+0.0%)

Uh, ignore that stack cost. The simplified logic leads to more functions
being inlined, which makes a mess of our stack measurements because we
don't take shrinkwrapping into account.
This commit is contained in:
Christopher Haster
2025-07-03 13:42:54 -05:00
parent eb884011ec
commit a85f08cfe3
3 changed files with 121 additions and 223 deletions
+119 -219
View File
@@ -7151,15 +7151,6 @@ static inline bool lfs3_o_isuncryst(uint32_t flags) {
#endif
}
static inline bool lfs3_o_isungraft(uint32_t flags) {
(void)flags;
#if !defined(LFS3_KVONLY) && !defined(LFS3_2BONLY)
return flags & LFS3_o_UNGRAFT;
#else
return false;
#endif
}
static inline bool lfs3_o_isunflush(uint32_t flags) {
return flags & LFS3_o_UNFLUSH;
}
@@ -9945,18 +9936,6 @@ static int lfs3_mtree_traverse_(lfs3_t *lfs3, lfs3_traversal_t *t,
t->b.shrub = file->b.shrub;
lfs3_btraversal_init(&t->u.bt);
lfs3_t_settstate(&t->b.o.flags, LFS3_TSTATE_OBTREE);
// wait, do we have an ungrafted leaf?
#ifndef LFS3_KVONLY
if (lfs3_o_isungraft(file->b.o.flags)) {
if (tag_) {
*tag_ = LFS3_TAG_BLOCK;
}
*bptr = file->leaf.bptr;
return 0;
}
#endif
continue;
#endif
@@ -11557,7 +11536,7 @@ static inline void lfs3_file_discardcache(lfs3_file_t *file) {
#ifndef LFS3_KVONLY
static inline void lfs3_file_discardleaf(lfs3_file_t *file) {
file->b.o.flags &= ~LFS3_o_UNCRYST & ~LFS3_o_UNGRAFT;
file->b.o.flags &= ~LFS3_o_UNCRYST;
file->leaf.pos = 0;
file->leaf.weight = 0;
lfs3_bptr_discard(&file->leaf.bptr);
@@ -11575,20 +11554,10 @@ static inline lfs3_size_t lfs3_file_cachesize(lfs3_t *lfs3,
: lfs3->cfg->file_cache_size;
}
static inline lfs3_off_t lfs3_file_weight_(const lfs3_file_t *file) {
#ifndef LFS3_KVONLY
return lfs3_max(
file->leaf.pos + file->leaf.weight,
file->b.shrub.weight);
#else
return file->b.shrub.weight;
#endif
}
static inline lfs3_off_t lfs3_file_size_(const lfs3_file_t *file) {
return lfs3_max(
LFS3_IFDEF_KVONLY(0, file->cache.pos) + file->cache.size,
lfs3_file_weight_(file));
file->b.shrub.weight);
}
@@ -11974,7 +11943,7 @@ int lfs3_file_close(lfs3_t *lfs3, lfs3_file_t *file) {
// low-level file reading
static int lfs3_file_lookupnext_(lfs3_t *lfs3, const lfs3_file_t *file,
static int lfs3_file_lookupnext(lfs3_t *lfs3, const lfs3_file_t *file,
lfs3_bid_t bid,
lfs3_bid_t *bid_, lfs3_bid_t *weight_, lfs3_bptr_t *bptr_) {
lfs3_tag_t tag;
@@ -12000,77 +11969,6 @@ static int lfs3_file_lookupnext_(lfs3_t *lfs3, const lfs3_file_t *file,
return 0;
}
#ifndef LFS3_KVONLY
static int lfs3_file_lookupnext(lfs3_t *lfs3, const lfs3_file_t *file,
lfs3_bid_t bid,
lfs3_bid_t *bid_, lfs3_bid_t *weight_, lfs3_bptr_t *bptr_) {
// hits our leaf?
if (bid >= file->leaf.pos
&& bid < file->leaf.pos + file->leaf.weight) {
if (bid_) {
*bid_ = file->leaf.pos + (file->leaf.weight-1);
}
if (weight_) {
*weight_ = file->leaf.weight;
}
*bptr_ = file->leaf.bptr;
return 0;
}
// in between bshrub/btree and ungrafted leaf? pretend there's a
// hole here
if (bid >= file->b.shrub.weight && bid < file->leaf.pos) {
if (bid_) {
*bid_ = file->leaf.pos-1;
}
if (weight_) {
*weight_ = file->leaf.pos - file->b.shrub.weight;
}
lfs3_bptr_discard(bptr_);
return 0;
}
// lookup on disk
lfs3_bid_t bid__;
lfs3_bid_t weight__;
int err = lfs3_file_lookupnext_(lfs3, file, bid,
&bid__, &weight__, bptr_);
if (err) {
return err;
}
// hits our leaf? our leaf takes priority
//
// slice left leaf?
if (bid > file->leaf.pos + file->leaf.weight
&& bid__-(weight__-1) < file->leaf.pos + file->leaf.weight) {
lfs3_soff_t d = (file->leaf.pos + file->leaf.weight)
- (bid__-(weight__-1));
weight__ -= d;
bptr_->data = LFS3_DATA_SLICE(bptr_->data,
lfs3_min(d, lfs3_bptr_size(bptr_)),
-1);
// slice right leaf?
} else if (bid < file->leaf.pos
&& bid__+1 > file->leaf.pos) {
lfs3_soff_t d = bid__+1 - file->leaf.pos;
bid__ -= d;
weight__ -= d;
bptr_->data = LFS3_DATA_SLICE(bptr_->data,
-1,
lfs3_bptr_size(bptr_) - lfs3_min(d, lfs3_bptr_size(bptr_)));
}
if (bid_) {
*bid_ = bid__;
}
if (weight_) {
*weight_ = weight__;
}
return 0;
}
#endif
// needed in lfs3_file_readnext
static int lfs3_file_crystallize(lfs3_t *lfs3, lfs3_file_t *file);
@@ -12079,7 +11977,6 @@ 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) {
// the leaf must not be pinned down here
LFS3_ASSERT(!lfs3_o_isuncryst(file->b.o.flags));
LFS3_ASSERT(!lfs3_o_isungraft(file->b.o.flags));
while (true) {
// any data in our leaf?
@@ -12125,7 +12022,7 @@ static lfs3_ssize_t lfs3_file_readnext(lfs3_t *lfs3, lfs3_file_t *file,
lfs3_bid_t bid;
lfs3_bid_t weight;
lfs3_bptr_t bptr;
int err = lfs3_file_lookupnext_(lfs3, file, pos,
int err = lfs3_file_lookupnext(lfs3, file, pos,
&bid, &weight, &bptr);
if (err) {
return err;
@@ -12238,9 +12135,8 @@ lfs3_ssize_t lfs3_file_read(lfs3_t *lfs3, lfs3_file_t *file,
}
// any data in our btree?
if (pos_ < lfs3_file_weight_(file)) {
if (!lfs3_o_isuncryst(file->b.o.flags)
&& !lfs3_o_isungraft(file->b.o.flags)) {
if (pos_ < file->b.shrub.weight) {
if (!lfs3_o_isuncryst(file->b.o.flags)) {
// bypass cache?
if ((lfs3_size_t)d >= lfs3_file_cachesize(lfs3, file)) {
lfs3_ssize_t d_ = lfs3_file_readnext(lfs3, file,
@@ -12321,7 +12217,7 @@ static inline lfs3_size_t lfs3_graft_count(lfs3_size_t graft_count) {
// graft bptr/fragments into our bshrub/btree
#if !defined(LFS3_RDONLY) && !defined(LFS3_KVONLY)
static int lfs3_file_graft(lfs3_t *lfs3, lfs3_file_t *file,
static int lfs3_file_graft_(lfs3_t *lfs3, lfs3_file_t *file,
lfs3_off_t pos, lfs3_off_t weight, lfs3_soff_t delta,
const lfs3_data_t *graft, lfs3_ssize_t graft_count) {
// note! we must never allow our btree size to overflow, even
@@ -12386,7 +12282,7 @@ static int lfs3_file_graft(lfs3_t *lfs3, lfs3_file_t *file,
while (pos < file->b.shrub.weight) {
lfs3_bid_t weight_;
lfs3_bptr_t bptr_;
err = lfs3_file_lookupnext_(lfs3, file, pos,
err = lfs3_file_lookupnext(lfs3, file, pos,
&bid, &weight_, &bptr_);
if (err) {
LFS3_ASSERT(err != LFS3_ERR_NOENT);
@@ -12603,8 +12499,14 @@ failed:;
#endif
#if !defined(LFS3_RDONLY) && !defined(LFS3_KVONLY) && !defined(LFS3_2BONLY)
static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
lfs3_off_t block_pos, lfs3_soff_t crystal_min, lfs3_soff_t crystal_max,
// this LFS3_NOINLINE is to force lfs3_file_crystallize__ off the stack
// hot-path
LFS3_NOINLINE
static int lfs3_file_crystallize__(lfs3_t *lfs3, lfs3_file_t *file,
lfs3_off_t block_pos,
// TODO do we really need to keep crystal_min around? rename
// to crystal_size?
lfs3_ssize_t crystal_min, lfs3_ssize_t crystal_max,
lfs3_off_t pos, const uint8_t *buffer, lfs3_size_t size) {
// align to prog_size, limit to block_size and theoretical file size
lfs3_off_t crystal_limit = lfs3_min(
@@ -12615,11 +12517,10 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
lfs3->cfg->block_size),
lfs3_max(
pos + size,
lfs3_file_weight_(file)));
file->b.shrub.weight));
// do we need to allocate a new block?
if (!lfs3_bptr_isbptr(&file->leaf.bptr)
|| !lfs3_bptr_iserased(&file->leaf.bptr)) {
// resuming crystallization? or do we need to allocate a new block?
if (!lfs3_o_isuncryst(file->b.o.flags)) {
goto relocate;
}
@@ -12643,18 +12544,15 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
}
}
// create a temporary copy in case an error happens
lfs3_bptr_t bptr = file->leaf.bptr;
while (true) {
// crystallize data into our block
//
// i.e. eagerly merge any right neighbors unless that would put
// us over our crystal_size/block_size
lfs3_off_t pos_ = block_pos
+ lfs3_bptr_off(&bptr)
+ lfs3_bptr_size(&bptr);
uint32_t cksum_ = lfs3_bptr_cksum(&bptr);
+ lfs3_bptr_off(&file->leaf.bptr)
+ lfs3_bptr_size(&file->leaf.bptr);
uint32_t cksum_ = lfs3_bptr_cksum(&file->leaf.bptr);
while (pos_ < crystal_limit) {
// keep track of the next highest priority data offset
lfs3_ssize_t d = crystal_limit - pos_;
@@ -12665,7 +12563,8 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
lfs3_ssize_t d_ = lfs3_min(
d,
size - (pos_ - pos));
int err = lfs3_bd_prog(lfs3, lfs3_bptr_block(&bptr),
int err = lfs3_bd_prog(lfs3,
lfs3_bptr_block(&file->leaf.bptr),
pos_ - block_pos,
&buffer[pos_ - pos], d_,
&cksum_, true);
@@ -12687,7 +12586,7 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
}
// any data on disk?
if (pos_ < lfs3_file_weight_(file)) {
if (pos_ < file->b.shrub.weight) {
lfs3_bid_t bid__;
lfs3_bid_t weight__;
lfs3_bptr_t bptr__;
@@ -12712,6 +12611,10 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
// crystal if explicit, otherwise above loops
// may never terminate
&& (lfs3_soff_t)(pos_ - block_pos) >= crystal_min) {
// if we hit this condition, mark as crystallized,
// attempting resume crystallization will not make
// progress
file->b.o.flags &= ~LFS3_o_UNCRYST;
break;
}
@@ -12722,7 +12625,8 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
d,
(bid__-(weight__-1) + lfs3_bptr_size(&bptr__))
- pos_);
err = lfs3_bd_progdata(lfs3, lfs3_bptr_block(&bptr),
err = lfs3_bd_progdata(lfs3,
lfs3_bptr_block(&file->leaf.bptr),
pos_ - block_pos,
LFS3_DATA_SLICE(bptr__.data,
pos_ - (bid__-(weight__-1)),
@@ -12746,7 +12650,8 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
}
// found a hole? fill with zeros
int err = lfs3_bd_set(lfs3, lfs3_bptr_block(&bptr),
int err = lfs3_bd_set(lfs3,
lfs3_bptr_block(&file->leaf.bptr),
pos_ - block_pos,
0, d,
&cksum_, true);
@@ -12762,6 +12667,20 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
pos_ += d;
}
// if we're fully crystallized, mark as crystallized
//
// note some special conditions may also clear this flag in the
// above loop
//
// and don't worry, we can still resume crystallization if we
// write to the tracked erased state
if (pos_ - block_pos == lfs3->cfg->block_size
|| pos_ == lfs3_max(
pos + size,
file->b.shrub.weight)) {
file->b.o.flags &= ~LFS3_o_UNCRYST;
}
// a bit of a hack here, we need to truncate our block to
// prog_size alignment to avoid padding issues
//
@@ -12783,22 +12702,19 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
return err;
}
// update our block pointer
LFS3_ASSERT(pos_ - block_pos >= lfs3_bptr_off(&bptr));
// and update the leaf bptr
LFS3_ASSERT(pos_ - block_pos >= lfs3_bptr_off(&file->leaf.bptr));
LFS3_ASSERT(pos_ - block_pos <= lfs3->cfg->block_size);
file->leaf.pos = block_pos + lfs3_bptr_off(&bptr);
file->leaf.weight = pos_ - (block_pos + lfs3_bptr_off(&bptr));
file->leaf.pos = block_pos + lfs3_bptr_off(&file->leaf.bptr);
file->leaf.weight = pos_ - file->leaf.pos;
lfs3_bptr_init(&file->leaf.bptr,
LFS3_DATA_DISK(
lfs3_bptr_block(&bptr),
lfs3_bptr_off(&bptr),
pos_ - (block_pos + lfs3_bptr_off(&bptr))),
lfs3_bptr_block(&file->leaf.bptr),
lfs3_bptr_off(&file->leaf.bptr),
pos_ - file->leaf.pos),
// mark as erased
(pos_ - block_pos) | LFS3_BPTR_ISERASED,
cksum_);
// mark as uncrystallized and ungrafted
file->b.o.flags |= LFS3_o_UNCRYST | LFS3_o_UNGRAFT;
return 0;
relocate:;
@@ -12811,17 +12727,54 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
return block;
}
lfs3_bptr_init(&bptr,
lfs3_bptr_init(&file->leaf.bptr,
LFS3_DATA_DISK(block, 0, 0),
// mark as erased
LFS3_BPTR_ISERASED | 0,
0);
// mark as uncrystallized
file->b.o.flags |= LFS3_o_UNCRYST;
}
}
#endif
#if !defined(LFS3_RDONLY) && !defined(LFS3_KVONLY) && !defined(LFS3_2BONLY)
static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
lfs3_off_t block_pos, lfs3_soff_t crystal_min, lfs3_soff_t crystal_max,
lfs3_off_t pos, const uint8_t *buffer, lfs3_size_t size) {
// this is split into two functions to try to minimize stack usage
// crystallize
int err = lfs3_file_crystallize__(lfs3, file,
block_pos, crystal_min, crystal_max,
pos, buffer, size);
if (err) {
goto failed;
}
// and graft into tree
err = lfs3_file_graft_(lfs3, file,
file->leaf.pos, file->leaf.weight, 0,
&file->leaf.bptr.data, LFS3_GRAFT_ISBPTR | 1);
if (err) {
goto failed;
}
return 0;
failed:;
// if we failed to crystallize we need to discard the leaf as it no
// longer matches the btree/bshrub state, this also clears the
// LFS3_o_UNCRYST flag
lfs3_file_discardleaf(file);
return err;
}
#endif
#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
@@ -12837,7 +12790,7 @@ static int lfs3_file_crystallize(lfs3_t *lfs3, lfs3_file_t *file) {
// checkpoint the allocator
lfs3_alloc_ckpoint(lfs3);
// finish crystallizing the block
// 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);
@@ -12845,24 +12798,8 @@ static int lfs3_file_crystallize(lfs3_t *lfs3, lfs3_file_t *file) {
return err;
}
// mark as crystallized
file->b.o.flags &= ~LFS3_o_UNCRYST;
}
// and graft into tree
if (lfs3_o_isungraft(file->b.o.flags)) {
// checkpoint the allocator
lfs3_alloc_ckpoint(lfs3);
// and graft
int err = lfs3_file_graft(lfs3, file,
file->leaf.pos, file->leaf.weight, 0,
&file->leaf.bptr.data, LFS3_GRAFT_ISBPTR | 1);
if (err) {
return err;
}
// mark as grafted
file->b.o.flags &= ~LFS3_o_UNGRAFT;
// we should have crystallized
LFS3_ASSERT(!lfs3_o_isuncryst(file->b.o.flags));
}
// eagerly mark as flushed if this included all of our cache
@@ -12988,8 +12925,8 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
// mid-crystallization? can we just resume crystallizing?
//
// note that the threshold to resume crystallization (prog_size),
// is often much lower than the threshold to start crystallization
// (crystal_thresh)
// is usually much lower than the threshold to start
// crystallization (crystal_thresh)
lfs3_off_t block_start = file->leaf.pos
- lfs3_bptr_off(&file->leaf.bptr);
lfs3_off_t block_end = file->leaf.pos
@@ -13001,6 +12938,8 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
&& pos - block_end < lfs3->cfg->crystal_thresh
// need to bail if we can't meet prog alignment
&& (pos + size) - block_end >= lfs3->cfg->prog_size) {
// mark as uncrystallized
file->b.o.flags |= LFS3_o_UNCRYST;
// crystallize
int err = lfs3_file_crystallize_(lfs3, file,
block_start,
@@ -13046,7 +12985,7 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
0);
if (crystal_end - crystal_start < lfs3->cfg->crystal_thresh
&& crystal_start > 0
&& poke < lfs3_file_weight_(file)
&& poke < file->b.shrub.weight
// don't bother looking up left after the first block
&& !aligned) {
lfs3_bid_t bid;
@@ -13079,9 +13018,9 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
// find right crystal neighbor
poke = lfs3_min(
crystal_start + (lfs3->cfg->crystal_thresh-1),
lfs3_file_weight_(file)-1);
file->b.shrub.weight-1);
if (crystal_end - crystal_start < lfs3->cfg->crystal_thresh
&& crystal_end < lfs3_file_weight_(file)) {
&& crystal_end < file->b.shrub.weight) {
lfs3_bid_t bid;
lfs3_bid_t weight;
lfs3_bptr_t bptr;
@@ -13130,6 +13069,8 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
&& lfs3_bptr_iserased(&file->leaf.bptr)
&& crystal_start >= block_end
&& crystal_start < block_start + lfs3->cfg->block_size) {
// mark as uncrystallized
file->b.o.flags |= LFS3_o_UNCRYST;
// crystallize
int err = lfs3_file_crystallize_(lfs3, file,
block_start,
@@ -13160,10 +13101,6 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
return err;
}
// mark as unerased so lfs3_file_crystallize doesn't try to
// resume crystallizing this block
lfs3_bptr_claim(&file->leaf.bptr);
// before we can crystallize we need to figure out the best
// block alignment, we use the entry immediately to the left of
// our crystal for this
@@ -13227,25 +13164,6 @@ static int lfs3_file_flush_(lfs3_t *lfs3, lfs3_file_t *file,
return 0;
fragment:;
// before we write fragments, we need to make sure our crystal
// is grafted into the tree
//
// but note we're still tracking its erased state for future
// writes!
if (lfs3_o_isungraft(file->b.o.flags)) {
// graft our crystal
lfs3_alloc_ckpoint(lfs3);
int err = lfs3_file_graft(lfs3, file,
file->leaf.pos, file->leaf.weight, 0,
&file->leaf.bptr.data, LFS3_GRAFT_ISBPTR | 1);
if (err) {
return err;
}
// mark as grafted
file->b.o.flags &= ~LFS3_o_UNGRAFT;
}
// iteratively write fragments (inlined leaves)
while (size > 0) {
// checkpoint the allocator
@@ -13280,7 +13198,7 @@ fragment:;
// is already full
if (fragment_end - fragment_start < lfs3->cfg->fragment_size
&& fragment_start > 0
&& fragment_start <= lfs3_file_weight_(file)
&& fragment_start <= file->b.shrub.weight
// don't bother to lookup left after first fragment
&& !aligned) {
lfs3_bid_t bid;
@@ -13318,7 +13236,7 @@ fragment:;
//
// note this may the same as our left sibling
if (fragment_end - fragment_start < lfs3->cfg->fragment_size
&& fragment_end < lfs3_file_weight_(file)) {
&& fragment_end < file->b.shrub.weight) {
lfs3_bid_t bid;
lfs3_bid_t weight;
lfs3_bptr_t bptr;
@@ -13351,7 +13269,7 @@ fragment:;
// once we've figured out what fragment to write, graft it into
// our tree
int err = lfs3_file_graft(lfs3, file,
int err = lfs3_file_graft_(lfs3, file,
fragment_start, fragment_end - fragment_start, 0,
datas, data_count);
if (err) {
@@ -13534,8 +13452,7 @@ int lfs3_file_flush(lfs3_t *lfs3, lfs3_file_t *file) {
// do nothing if our file is already flushed, crystallized,
// and grafted
if (!lfs3_o_isunflush(file->b.o.flags)
&& !lfs3_o_isuncryst(file->b.o.flags)
&& !lfs3_o_isungraft(file->b.o.flags)) {
&& !lfs3_o_isuncryst(file->b.o.flags)) {
return 0;
}
// unflushed files must be unsynced
@@ -13587,8 +13504,8 @@ failed:;
#endif
}
// this LFS3_NOINLINE is to force lfs3_file_sync_ off the stack hot-path
#ifndef LFS3_RDONLY
// this LFS3_NOINLINE is to force lfs3_file_sync_ off the stack hot-path
LFS3_NOINLINE
static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file,
const lfs3_name_t *name) {
@@ -13608,8 +13525,6 @@ static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file,
|| lfs3_o_isunsync(file->b.o.flags));
LFS3_ASSERT(!lfs3_o_isuncryst(file->b.o.flags)
|| lfs3_o_isunsync(file->b.o.flags));
LFS3_ASSERT(!lfs3_o_isungraft(file->b.o.flags)
|| lfs3_o_isunsync(file->b.o.flags));
// pending metadata changes?
if (lfs3_o_isunsync(file->b.o.flags)) {
@@ -13638,8 +13553,7 @@ static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file,
// pending small file flush?
if (lfs3_o_isunflush(file->b.o.flags)
|| lfs3_o_isuncryst(file->b.o.flags)
|| lfs3_o_isungraft(file->b.o.flags)) {
|| lfs3_o_isuncryst(file->b.o.flags)) {
// this only works if the file is entirely in our cache
#ifndef LFS3_KVONLY
LFS3_ASSERT(file->cache.pos == 0);
@@ -13671,8 +13585,7 @@ static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file,
// make sure data is on-disk before committing metadata
if (lfs3_file_size_(file) > 0
&& !lfs3_o_isunflush(file->b.o.flags)
&& !lfs3_o_isuncryst(file->b.o.flags)
&& !lfs3_o_isungraft(file->b.o.flags)) {
&& !lfs3_o_isuncryst(file->b.o.flags)) {
int err = lfs3_bd_sync(lfs3);
if (err) {
return err;
@@ -13690,8 +13603,7 @@ static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file,
// bshrub?
} else if (lfs3_bshrub_isbshrub(&file->b)
|| lfs3_o_isunflush(file->b.o.flags)
|| lfs3_o_isuncryst(file->b.o.flags)
|| lfs3_o_isungraft(file->b.o.flags)) {
|| lfs3_o_isuncryst(file->b.o.flags)) {
rattrs[rattr_count++] = LFS3_RATTR_SHRUB(
LFS3_TAG_MASK8 | LFS3_TAG_BSHRUB, 0,
// note we use the staged trunk here
@@ -13784,8 +13696,7 @@ static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file,
// update flags
file_->b.o.flags &= ~LFS3_o_UNSYNC
& ~LFS3_o_UNFLUSH
& ~LFS3_o_UNCRYST
& ~LFS3_o_UNGRAFT;
& ~LFS3_o_UNCRYST;
// update shrubs
file_->b.shrub = file->b.shrub;
// update leaves
@@ -13850,7 +13761,6 @@ static int lfs3_file_sync_(lfs3_t *lfs3, lfs3_file_t *file,
file->b.o.flags &= ~LFS3_o_UNSYNC
& ~LFS3_o_UNFLUSH
& ~LFS3_o_UNCRYST
& ~LFS3_o_UNGRAFT
& ~LFS3_o_UNCREAT;
return 0;
}
@@ -14053,8 +13963,8 @@ int lfs3_file_truncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
file->b.o.flags |= LFS3_o_UNSYNC;
// if our leaf is a fragment or will be fragmented, we need
// to go ahead and graft + discard it, otherwise we risk out-of-date
// fragments as btree commits move things around
// 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
@@ -14076,7 +13986,7 @@ int lfs3_file_truncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
// checkpoint the allocator
lfs3_alloc_ckpoint(lfs3);
// truncate our btree
err = lfs3_file_graft(lfs3, file,
err = lfs3_file_graft_(lfs3, file,
lfs3_min(size, size_), size - lfs3_min(size, size_),
+size_ - size,
NULL, 0);
@@ -14139,8 +14049,8 @@ int lfs3_file_fruncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
file->b.o.flags |= LFS3_o_UNSYNC;
// if our leaf is a fragment or will be fragmented, we need
// to go ahead and graft + discard it, otherwise we risk out-of-date
// fragments as btree commits move things around
// 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
@@ -14167,7 +14077,7 @@ int lfs3_file_fruncate(lfs3_t *lfs3, lfs3_file_t *file, lfs3_off_t size_) {
// checkpoint the allocator
lfs3_alloc_ckpoint(lfs3);
// fruncate our btree
err = lfs3_file_graft(lfs3, file,
err = lfs3_file_graft_(lfs3, file,
0, lfs3_smax(size - size_, 0),
+size_ - size,
NULL, 0);
@@ -14241,16 +14151,6 @@ failed:;
#if !defined(LFS3_KVONLY) && !defined(LFS3_2BONLY)
static int lfs3_file_ck(lfs3_t *lfs3, const lfs3_file_t *file,
uint32_t flags) {
// validate ungrafted data block?
if (lfs3_t_isckdata(flags)
&& lfs3_o_isungraft(file->b.o.flags)) {
LFS3_ASSERT(lfs3_bptr_isbptr(&file->leaf.bptr));
int err = lfs3_bptr_ck(lfs3, &file->leaf.bptr);
if (err) {
return err;
}
}
// traverse the file's bshrub/btree
lfs3_btraversal_t bt;
lfs3_btraversal_init(&bt);
+1 -2
View File
@@ -145,8 +145,7 @@ enum lfs3_type {
#define LFS3_o_UNCREAT 0x02000000 // File does not exist yet
#define LFS3_o_UNSYNC 0x01000000 // File's metadata does not match disk
#define LFS3_o_UNCRYST 0x00800000 // File's leaf not fully crystallized
#define LFS3_o_UNGRAFT 0x00400000 // File's leaf does not match bshrub/btree
#define LFS3_o_UNFLUSH 0x00200000 // File's data does not match disk
#define LFS3_o_UNFLUSH 0x00400000 // File's cache does not match disk
// File seek flags
#define LFS3_SEEK_SET 0 // Seek relative to an absolute position
+1 -2
View File
@@ -49,8 +49,7 @@ FLAGS = [
('o', 'UNCREAT', 0x02000000, "File does not exist yet" ),
('o', 'UNSYNC', 0x01000000, "File's metadata does not match disk" ),
('o', 'UNCRYST', 0x00800000, "File's leaf not fully crystallized" ),
('o', 'UNGRAFT', 0x00400000, "File's leaf does not match bshrub/btree" ),
('o', 'UNFLUSH', 0x00200000, "File's data does not match disk" ),
('o', 'UNFLUSH', 0x00400000, "File's cache does not match disk" ),
# Custom attribute flags
('A', 'MODE', 3, "The attr's access mode" ),