New "fragmenting" write strategy

The attempt to implement in-rbyd data slicing, being lazily coalesced
during rbyd compaction, failed pretty much completely.

Slicing is a very enticing write strategy, getting both minimal overhead
post-compaction and fast random write speeds, but the idea has some
fundamental conflicts with how we play out attrs post-compaction.

This idea might work in a more powerful filesystem, but brings back the
need to simulate rbyds in RAM, which is something I really don't want to
do (complex, bug-prone, likely adds code cost, may not even be tractable).

So, third time's the charm?

---

This new write strategy writes only datas and bptrs, and avoids dagging
by completely rewriting any regions of data larger than a configurable
crystallization threshold.

This loses most of the benefits of data crystallization, random writes
will now usually need to rewrite a full block, but as a tradeoff our
data at rest is always stored with optimal overhead.

And at least data crystallization still saves space when our data isn't
block aligned, or in sparse files. From reading up on some other
filesystem designs it seems this is a desirable optimization sometimes
referred to as "tail-packing" or "block suballocation"

Some other changes from just having more time to think about the
problem:

1. Instead of scanning to figure out our current crystal size, we can
   use a simple heuristic of 1. look up left block, 2. look up right
   block, 3. assume any data between these blocks contribute to our
   current crystal.

   This is just a heuristic, so worst case you write the first and last
   byte of a block which is enough to trigger compaction into a block.
   But on the plus side this avoids issues with small holes preventing
   blocks from being formed.

   This approach brings the number of btree lookups down from
   O(crystallize_size) to 2.

2. I've gone ahead and dropped the previous scheme of coalesce_size
   + fragment_size and instead adopted a single fragment_size that
   controls the size of, well, fragments, i.e. data elements stored
   directly in trees.

   This affects both the inlined shrub as well as fragments stored in
   the inner nodes of the btree. I believe it's very similar to what is
   often called "pages" in logging filesystems, though I'm going to
   avoid that term for now because it's a bit overloaded.

   Previously, neighboring writes that, when combined, would exceed our
   coalesce_size, they just weren't combined. Now they are combined up
   to our fragment size, potentially splitting the right fragment.

   Before (fragment_size=8):

     .---+---+---+---+---+---+---+---.
     |            8 bytes            |
     '---+---+---+---+---+---+---+---'
                         +
                         .---+---+---+---+---.
                         |      5 bytes      |
                         '---+---+---+---+---'
                         =
     .---+---+---+---+---+---+---+---+---+---.
     |      5 bytes      |      5 bytes      |
     '---+---+---+---+---+---+---+---+---+---'

   After:

     .---+---+---+---+---+---+---+---.
     |            8 bytes            |
     '---+---+---+---+---+---+---+---'
                         +
                         .---+---+---+---+---.
                         |      5 bytes      |
                         '---+---+---+---+---'
                         =
     .---+---+---+---+---+---+---+---+---+---.
     |            8 bytes            |2 bytes|
     '---+---+---+---+---+---+---+---+---+---'

   This leads to better fragment alignment (much like our block
   strategy), and minimizes tree overhead.

   Any neighboring data to the right is only coalesced if it fits in the
   current fragment, or would be rewritten (carved) anyways, to avoid
   unnecessary data rewriting.

   For example (fragment_size=8):

     .---+---+---+---+---+---+---+---+---+---+---+---+---+---.
     |        6 bytes        |        6 bytes        |2 bytes|
     '---+---+---+---+---+---+---+---+---+---+---+---+---+---'
                                 +
                         .---+---+---+---+---.
                         |      5 bytes      |
                         '---+---+---+---+---'
                                 =
     .---+---+---+---+---+---+---+---+---+---+---+---+---+---.
     |            8 bytes            |    4 bytes    |2 bytes|
     '---+---+---+---+---+---+---+---+---+---+---+---+---+---'

Other than these changes this commit is mostly a bunch of carveshrub
rewriting again, which continues to be nuanced and annoying to get
bug free.
This commit is contained in:
Christopher Haster
2023-10-20 01:22:44 -05:00
parent 907c24beeb
commit c815c19c20
5 changed files with 608 additions and 276 deletions
+598 -266
View File
@@ -9297,7 +9297,7 @@ static int lfsr_file_readnext(lfs_t *lfs, const lfsr_file_t *file,
// data get a strict read hint
d = lfs_min32(
d,
lfsr_data_size(&file->shrub.u.data));
lfsr_data_size(&file->shrub.u.data) - pos);
if (weight_) {
*weight_ = d;
@@ -9454,317 +9454,510 @@ lfs_ssize_t lfsr_file_read(lfs_t *lfs, lfsr_file_t *file,
? LFSR_SLICE_DSIZE \
: LFSR_BPTR_DSIZE))
static lfs_ssize_t lfsr_tree_buildcarve(lfs_t *lfs, const lfsr_tree_t *tree,
//static lfs_ssize_t lfsr_tree_buildcarve(lfs_t *lfs, const lfsr_tree_t *tree,
// lfs_off_t pos, lfs_off_t weight, lfs_soff_t delta,
// lfsr_tag_t tag, lfsr_data_t data,
// lfs_soff_t *estimate_,
// lfsr_attr_t attrs[static LFSR_CARVE_ATTRS],
// uint8_t buffer[static LFSR_CARVE_DSIZE]) {
// // TODO need size?
// // we should never try to shove more data than can fit in a given weight
// LFS_ASSERT(lfsr_data_size(&data) <= weight + delta);
//
// // allocate attrs/buffer as necessary
// lfs_size_t attr_count = 0;
// lfs_size_t buffer_off = 0;
//
// // keeep track of removed data, we combined this into a single attr,
// // and keep track of how our changes will affect our size estimate
// lfs_off_t rm = 0;
// lfs_soff_t estimate = 0;
//
// // try to carve any existing data
// lfs_off_t pos_ = pos;
// while (pos_ < lfs_min32(pos+weight, lfsr_tree_size(tree))) {
// lfsr_bid_t bid_;
// lfsr_tag_t tag_;
// lfsr_bid_t weight_;
// lfsr_data_t data_;
// // the tree may be aliased to either a sprout or block pointer if
// // we're creating a new btree, assume sprout if we're a shrub,
// // otherwise a block pointer
// if (!lfsr_tree_hasbtree(tree) && lfsr_tag_isshrub(tag)) {
// const lfsr_data_t *slice = (const lfsr_data_t*)tree;
// bid_ = lfsr_data_size(slice)-1;
// tag_ = tag;
// weight_ = lfsr_data_size(slice);
// data_ = *slice;
// } else if (!lfsr_tree_hasbtree(tree)) {
// // TODO
// LFS_ASSERT(false);
// } else {
// int err = lfsr_btree_lookupnext(lfs, &tree->u.btree, pos_,
// &bid_, &tag_, &weight_, &data_);
// if (err) {
// LFS_ASSERT(err != LFS_ERR_NOENT);
// return err;
// }
// }
// LFS_ASSERT(lfsr_tag_key(tag_) == LFSR_TAG_DATA
// || lfsr_tag_key(tag_) == LFSR_TAG_SLICE
// || lfsr_tag_key(tag_) == LFSR_TAG_BLOCK);
//
// // go ahead and dereference to get to the actual data
// lfsr_data_t slice_;
// lfsr_bptr_t bptr_;
// int err = lfsr_data_deref(lfs, tag_, data_, &slice_, &bptr_);
// if (err) {
// return err;
// }
// LFS_ASSERT(lfsr_data_size(&slice_) <= weight_);
//
//// // get the physical data size
//// lfs_size_t size_;
//// err = lfsr_data_derefsize(lfs, tag_, data_, &size_);
//// if (err) {
//// return err;
//// }
//// LFS_ASSERT(size_ <= weight_);
//
// // note an entry can be both a left and right sibling!
//
// // found left sibling?
// if (pos > bid_-(weight_-1)) {
// lfs_off_t overlap_ = (bid_+1) - pos;
// LFS_ASSERT((lfs_soff_t)overlap_ >= 0);
//
// // carve in-rbyd data?
// if (lfsr_tag_key(tag_) == LFSR_TAG_DATA
// || lfsr_tag_key(tag_) == LFSR_TAG_SLICE) {
// lfsr_data_t slice__ = LFSR_DATA_DISK(
// slice_.u.disk.block,
// slice_.u.disk.off,
// lfs_min32(
// weight_ - overlap_,
// lfsr_data_size(&slice_)));
//
// // some special cases require a new tag: if we're creating a
// // new btree
// if (!lfsr_tree_hasbtree(tree)) {
// attrs[attr_count++] = LFSR_ATTR(0,
// TAG(lfsr_tag_mode(tag)
// | LFSR_TAG_SLICE), +(weight_ - overlap_),
// FROMSLICE(&slice__, &buffer[buffer_off]));
// buffer += LFSR_SLICE_DSIZE;
//
// // update our estimate
// estimate += lfsr_data_size(&slice__);
//
// } else {
// attrs[attr_count++] = LFSR_ATTR(bid_,
// TAG(lfsr_tag_mode(tag)
// | LFSR_TAG_GROW(WIDE(SLICE))), -overlap_,
// FROMSLICE(&slice__, &buffer[buffer_off]));
// buffer += LFSR_SLICE_DSIZE;
//
// // update our estimate
// estimate -= (lfsr_data_size(&slice_)
// - lfsr_data_size(&slice__));
// // we may remove a hole here
// if (lfsr_data_size(&slice_) < weight_
// && lfsr_data_size(&slice__) == weight_ - overlap_) {
// estimate -= LFSR_ATTR_ESTIMATE;
// }
// }
//
// // carve block pointer?
// } else if (lfsr_tag_key(tag_) == LFSR_TAG_BLOCK) {
// // TODO
// LFS_ASSERT(false);
//// lfsr_bptr_t bptr_;
//// int err = lfsr_data_readbptr(lfs, &data_, &bptr_);
//// if (err) {
//// return err;
//// }
//// LFS_ASSERT(bptr_.size <= weight_);
////
//// // are we carving the block too small?
//// if (bptr_.size <= lfs->cfg->crystallize_size) {
//// attrs[attr_count++] = LFSR_ATTR(bid_,
//// GROW(), -overlap_, DATA(
//// data_.u.disk.block,
//// data_.u.disk.off,
//// weight_ - overlap_}));
//// } else {
//// attrs[attr_count++] = LFSR_ATTR(bid_,
//// GROW(), -overlap_, FROMBPTR(
//// (&(lfsr_bptr_t){
//// .block=data_.u.disk.block,
//// .off=data_.u.disk.off,
//// .size=weight_ - overlap_}),
//// &buffer[buffer_off]));
//// buffer += LFSR_BPTR_DSIZE;
//// }
// }
//
// // TODO wait is this the same on all branches?
// pos_ = bid_+1;
// }
//
// // found right sibling?
// if (pos + weight < bid_+1) {
// lfs_off_t overlap_ = (pos + weight) - (bid_-(weight_-1));
// LFS_ASSERT((lfs_soff_t)overlap_ >= 0);
//
// // carve in-rbyd data?
// if (lfsr_tag_key(tag_) == LFSR_TAG_DATA
// || lfsr_tag_key(tag_) == LFSR_TAG_SLICE) {
// lfsr_data_t slice__ = LFSR_DATA_DISK(
// slice_.u.disk.block,
// slice_.u.disk.off + lfs_min32(
// overlap_,
// lfsr_data_size(&slice_)),
// lfsr_data_size(&slice_) - lfs_min32(
// overlap_,
// lfsr_data_size(&slice_)));
//
// // some special cases require a new tag: if we're creating a
// // new btree, or if left/right siblings are the same
// if (!lfsr_tree_hasbtree(tree) || overlap_ > weight) {
// attrs[attr_count++] = LFSR_ATTR(pos,
// TAG(lfsr_tag_mode(tag)
// | LFSR_TAG_SLICE), +(weight_ - overlap_),
// FROMSLICE(&slice__, &buffer[buffer_off]));
// buffer += LFSR_SLICE_DSIZE;
//
// // update our estimate
// estimate += lfsr_data_size(&slice__);
// // if we split, we may actually create a hole
// if (lfsr_data_size(&slice__) < weight_ - overlap_) {
// estimate += LFSR_ATTR_ESTIMATE;
// }
//
// } else {
// // we need to account for changes to left sibling here
// attrs[attr_count++] = LFSR_ATTR(pos+rm+weight_-1,
// TAG(lfsr_tag_mode(tag)
// | LFSR_TAG_GROW(WIDE(SLICE))), -overlap_,
// FROMSLICE(&slice__, &buffer[buffer_off]));
// buffer += LFSR_SLICE_DSIZE;
//
// // update our estimate
// estimate -= (lfsr_data_size(&slice_)
// - lfsr_data_size(&slice__));
// }
//
// // carve block pointer?
// } else if (lfsr_tag_key(tag_) == LFSR_TAG_BLOCK) {
// // TODO
// // TODO note this needs to handle both siblings being
// // larger than crystallize size (force one to crystallize)
// LFS_ASSERT(false);
// }
//
// pos_ = bid_+1;
// }
//
// // found fully overwritten data?
// if (pos <= bid_-(weight_-1) && pos + weight >= bid_+1) {
// // combine all rms into a single attr
// rm += weight_;
//
// // update estimate
// estimate -= lfsr_data_size(&slice_);
// // include any removed holes
// if (lfsr_data_size(&slice_) < weight_) {
// estimate -= LFSR_ATTR_ESTIMATE;
// }
//
// pos_ = bid_+1;
// }
// }
//
// // write the combined rm attribute
// if (rm > 0) {
// attrs[attr_count++] = LFSR_ATTR(pos+rm-1,
// TAG(lfsr_tag_mode(tag)
// | LFSR_TAG_RM), -rm,
// NULL);
// }
//
// // TODO make sure data-less holes get coalesced, though not we don't
// // accurately track coalesced holes with our estimate because it simplifies
// // things... document?
//
// // there's a few cases we need to append an extra tag: we're appended data
// // past the end of our btree and need a hole, or we're creating a new
// // btree and didn't carve the existing data
// if (!lfsr_tree_hasbtree(tree)
// && lfsr_tree_size(tree) > 0
// && (pos >= lfsr_tree_size(tree)
// || pos + weight == 0)) {
// // note we never grow and rm a btree at the same time
// LFS_ASSERT(rm == 0);
//
// attrs[attr_count++] = LFSR_ATTR(0,
// TAG(lfsr_tag_mode(tag)
// | LFSR_TAG_DATA), +lfs_max32(pos, lfsr_tree_size(tree)),
// DATA(*(const lfsr_data_t*)tree));
//
// // update our estimate
// estimate += lfsr_data_size((const lfsr_data_t*)tree);
// // we may be making a hole here
// if (pos > lfsr_tree_size(tree)) {
// estimate += LFSR_ATTR_ESTIMATE;
// }
//
// // need a hole?
// } else if (pos > lfsr_tree_size(tree)) {
// // note we never grow and rm a btree at the same time
// LFS_ASSERT(rm == 0);
//
// attrs[attr_count++] = LFSR_ATTR(lfsr_tree_size(tree),
// TAG(lfsr_tag_mode(tag)
// | LFSR_TAG_DATA), +(pos - lfsr_tree_size(tree)),
// NULL);
//
// // turns out making a hole, makes a hole
// estimate += LFSR_ATTR_ESTIMATE;
// }
//
// // finally append our data
// if (weight + delta > 0) {
// attrs[attr_count++] = LFSR_ATTR(pos,
// TAG(tag), +(weight + delta), DATA(data));
//
// // update our estimate
// estimate += lfsr_data_size(&data);
// // include the overhead of any new holes
// if (lfsr_data_size(&data) < weight + delta) {
// estimate += LFSR_ATTR_ESTIMATE;
// }
// }
//
// LFS_ASSERT(attr_count <= LFSR_CARVE_ATTRS);
// LFS_ASSERT(buffer_off <= LFSR_CARVE_DSIZE);
// if (estimate_) {
// *estimate_ = estimate;
// }
// return attr_count;
//}
// TODO can lfsr_file_carveshrub and lfsr_file_carvetree be combined somehow?
static int lfsr_file_carveshrub(lfs_t *lfs, lfsr_file_t *file,
lfs_off_t pos, lfs_off_t weight, lfs_soff_t delta,
lfsr_tag_t tag, lfsr_data_t data,
lfs_soff_t *estimate_,
lfsr_attr_t attrs[static LFSR_CARVE_ATTRS],
uint8_t buffer[static LFSR_CARVE_DSIZE]) {
// TODO need size?
lfsr_tag_t tag, lfsr_data_t data) {
// only inlined data is supported in shrubs
LFS_ASSERT(tag == LFSR_TAG_SHRUB(DATA));
// we should never try to shove more data than can fit in a given weight
LFS_ASSERT(lfsr_data_size(&data) <= weight + delta);
// allocate attrs/buffer as necessary
lfsr_attr_t attrs[4];
lfs_size_t attr_count = 0;
lfs_size_t buffer_off = 0;
// keeep track of removed data, we combined this into a single attr,
// and keep track of how our changes will affect our size estimate
lfs_off_t rm = 0;
lfs_soff_t estimate = 0;
// TODO just use another variable in file for shrub estimate? I don't
// think unionizing this variable gets us anything
lfs_off_t estimate = (lfsr_shrub_hasshrub(&file->shrub))
? file->shrub.u.shrub.estimate
: 0;
// do we need to add sprout data to our shrub?
//
// we only do this here if we aren't carving
if (lfsr_shrub_hassprout(&file->shrub)
&& (pos >= lfsr_shrub_size(&file->shrub)
|| pos + weight == 0)) {
attrs[attr_count++] = LFSR_ATTR(0,
SHRUB(DATA), +lfsr_shrub_size(&file->shrub),
DATA(file->shrub.u.data));
// update our estimate
estimate += LFSR_ATTR_ESTIMATE + lfsr_shrub_size(&file->shrub);
}
// try to carve any existing data
lfs_off_t pos_ = pos;
while (pos_ < lfs_min32(pos+weight, lfsr_tree_size(tree))) {
lfsr_bid_t bid_;
while (pos_ < lfs_min32(pos+weight, lfsr_shrub_size(&file->shrub))) {
lfsr_srid_t rid_;
lfsr_tag_t tag_;
lfsr_bid_t weight_;
lfsr_rid_t weight_;
lfsr_data_t data_;
// the tree may be aliased to either a sprout or block pointer if
// we're creating a new btree, assume sprout if we're a shrub,
// otherwise a block pointer
if (!lfsr_tree_hasbtree(tree) && lfsr_tag_isshrub(tag)) {
const lfsr_data_t *slice = (const lfsr_data_t*)tree;
bid_ = lfsr_data_size(slice)-1;
tag_ = tag;
weight_ = lfsr_data_size(slice);
data_ = *slice;
} else if (!lfsr_tree_hasbtree(tree)) {
// TODO
LFS_ASSERT(false);
// sprout or shrub?
if (!lfsr_shrub_hasshrub(&file->shrub)) {
rid_ = lfsr_data_size(&file->shrub.u.data)-1;
tag_ = LFSR_TAG_DATA;
weight_ = lfsr_data_size(&file->shrub.u.data);
data_ = file->shrub.u.data;
} else {
int err = lfsr_btree_lookupnext(lfs, &tree->u.btree, pos_,
&bid_, &tag_, &weight_, &data_);
int err = lfsr_rbyd_lookupnext(lfs, &file->shrub.u.rbyd,
pos_, 0,
&rid_, &tag_, &weight_, &data_);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
}
LFS_ASSERT(lfsr_tag_key(tag_) == LFSR_TAG_DATA
|| lfsr_tag_key(tag_) == LFSR_TAG_SLICE
|| lfsr_tag_key(tag_) == LFSR_TAG_BLOCK);
// go ahead and dereference to get to the actual data
lfsr_data_t slice_;
lfsr_bptr_t bptr_;
int err = lfsr_data_deref(lfs, tag_, data_, &slice_, &bptr_);
if (err) {
return err;
}
LFS_ASSERT(lfsr_data_size(&slice_) <= weight_);
// // get the physical data size
// lfs_size_t size_;
// err = lfsr_data_derefsize(lfs, tag_, data_, &size_);
// if (err) {
// return err;
// }
// LFS_ASSERT(size_ <= weight_);
LFS_ASSERT(lfsr_tag_key(tag_) == LFSR_TAG_DATA);
// note an entry can be both a left and right sibling!
// TODO! are we coalescing holes?
// found left sibling?
if (pos > bid_-(weight_-1)) {
lfs_off_t overlap_ = (bid_+1) - pos;
if (pos > rid_-(weight_-1)) {
lfs_off_t overlap_ = (rid_+1) - pos;
LFS_ASSERT((lfs_soff_t)overlap_ >= 0);
// carve in-rbyd data?
if (lfsr_tag_key(tag_) == LFSR_TAG_DATA
|| lfsr_tag_key(tag_) == LFSR_TAG_SLICE) {
lfsr_data_t slice__ = LFSR_DATA_DISK(
slice_.u.disk.block,
slice_.u.disk.off,
lfs_min32(
weight_ - overlap_,
lfsr_data_size(&slice_)));
// carve
lfsr_data_t data__ = LFSR_DATA_DISK(
data_.u.disk.block,
data_.u.disk.off,
lfs_min32(
weight_ - overlap_,
lfsr_data_size(&data_)));
// some special cases require a new tag: if we're creating a
// new btree
if (!lfsr_tree_hasbtree(tree)) {
attrs[attr_count++] = LFSR_ATTR(0,
TAG(lfsr_tag_mode(tag)
| LFSR_TAG_SLICE), +(weight_ - overlap_),
FROMSLICE(&slice__, &buffer[buffer_off]));
buffer += LFSR_SLICE_DSIZE;
// some special cases require a new tag: if we're creating a
// new shrub
if (!lfsr_shrub_hasshrub(&file->shrub)) {
attrs[attr_count++] = LFSR_ATTR(0,
SHRUB(DATA), +(weight_ - overlap_),
DATA(data__));
// update our estimate
estimate += lfsr_data_size(&slice__);
// update our estimate
estimate += LFSR_ATTR_ESTIMATE + lfsr_data_size(&data__);
} else {
attrs[attr_count++] = LFSR_ATTR(bid_,
TAG(lfsr_tag_mode(tag)
| LFSR_TAG_GROW(WIDE(SLICE))), -overlap_,
FROMSLICE(&slice__, &buffer[buffer_off]));
buffer += LFSR_SLICE_DSIZE;
// we can get away with a grow attribute in some cases, avoiding
// a data copy
} else if (lfsr_data_size(&data_) == lfsr_data_size(&data__)) {
attrs[attr_count++] = LFSR_ATTR(rid_,
SHRUB(GROW), -overlap_,
NULL);
// update our estimate
estimate -= (lfsr_data_size(&slice_)
- lfsr_data_size(&slice__));
// we may remove a hole here
if (lfsr_data_size(&slice_) < weight_
&& lfsr_data_size(&slice__) == weight_ - overlap_) {
estimate -= LFSR_ATTR_ESTIMATE;
}
}
} else {
attrs[attr_count++] = LFSR_ATTR(rid_,
SHRUB(GROW(WIDE(DATA))), -overlap_,
DATA(data__));
// carve block pointer?
} else if (lfsr_tag_key(tag_) == LFSR_TAG_BLOCK) {
// TODO
LFS_ASSERT(false);
// lfsr_bptr_t bptr_;
// int err = lfsr_data_readbptr(lfs, &data_, &bptr_);
// if (err) {
// return err;
// }
// LFS_ASSERT(bptr_.size <= weight_);
//
// // are we carving the block too small?
// if (bptr_.size <= lfs->cfg->crystallize_size) {
// attrs[attr_count++] = LFSR_ATTR(bid_,
// GROW(), -overlap_, DATA(
// data_.u.disk.block,
// data_.u.disk.off,
// weight_ - overlap_}));
// } else {
// attrs[attr_count++] = LFSR_ATTR(bid_,
// GROW(), -overlap_, FROMBPTR(
// (&(lfsr_bptr_t){
// .block=data_.u.disk.block,
// .off=data_.u.disk.off,
// .size=weight_ - overlap_}),
// &buffer[buffer_off]));
// buffer += LFSR_BPTR_DSIZE;
// }
// update our estimate
estimate -= lfsr_data_size(&data_) - lfsr_data_size(&data__);
}
// TODO wait is this the same on all branches?
pos_ = bid_+1;
}
// found right sibling?
if (pos + weight < bid_+1) {
lfs_off_t overlap_ = (pos + weight) - (bid_-(weight_-1));
// found right sibling? carve
if (pos + weight < (lfs_off_t)rid_+1) {
lfs_off_t overlap_ = (pos + weight) - (rid_-(weight_-1));
LFS_ASSERT((lfs_soff_t)overlap_ >= 0);
// carve in-rbyd data?
if (lfsr_tag_key(tag_) == LFSR_TAG_DATA
|| lfsr_tag_key(tag_) == LFSR_TAG_SLICE) {
lfsr_data_t slice__ = LFSR_DATA_DISK(
slice_.u.disk.block,
slice_.u.disk.off + lfs_min32(
overlap_,
lfsr_data_size(&slice_)),
lfsr_data_size(&slice_) - lfs_min32(
overlap_,
lfsr_data_size(&slice_)));
lfsr_data_t data__ = LFSR_DATA_DISK(
data_.u.disk.block,
data_.u.disk.off + lfs_min32(
overlap_,
lfsr_data_size(&data_)),
lfsr_data_size(&data_) - lfs_min32(
overlap_,
lfsr_data_size(&data_)));
// some special cases require a new tag: if we're creating a
// new btree, or if left/right siblings are the same
if (!lfsr_tree_hasbtree(tree) || overlap_ > weight) {
attrs[attr_count++] = LFSR_ATTR(pos,
TAG(lfsr_tag_mode(tag)
| LFSR_TAG_SLICE), +(weight_ - overlap_),
FROMSLICE(&slice__, &buffer[buffer_off]));
buffer += LFSR_SLICE_DSIZE;
// can we coalesce a hole?
if (lfsr_data_size(&data__) == 0) {
delta += rid_+1 - (pos + weight);
// add to rm if left/right siblings aren't the same
if (overlap_ <= weight) {
rm += weight_;
// update our estimate
estimate += lfsr_data_size(&slice__);
// if we split, we may actually create a hole
if (lfsr_data_size(&slice__) < weight_ - overlap_) {
estimate += LFSR_ATTR_ESTIMATE;
}
} else {
// we need to account for changes to left sibling here
attrs[attr_count++] = LFSR_ATTR(pos+rm+weight_-1,
TAG(lfsr_tag_mode(tag)
| LFSR_TAG_GROW(WIDE(SLICE))), -overlap_,
FROMSLICE(&slice__, &buffer[buffer_off]));
buffer += LFSR_SLICE_DSIZE;
// update our estimate
estimate -= (lfsr_data_size(&slice_)
- lfsr_data_size(&slice__));
estimate -= LFSR_ATTR_ESTIMATE + lfsr_data_size(&data_);
}
// carve block pointer?
} else if (lfsr_tag_key(tag_) == LFSR_TAG_BLOCK) {
// TODO
// TODO note this needs to handle both siblings being
// larger than crystallize size (force one to crystallize)
LFS_ASSERT(false);
}
// some special cases require a new tag: if we're creating a
// new btree, or if left/right siblings are the same
} else if (!lfsr_shrub_hasshrub(&file->shrub)
|| overlap_ > weight) {
attrs[attr_count++] = LFSR_ATTR(pos,
SHRUB(DATA), +(weight_ - overlap_),
DATA(data__));
pos_ = bid_+1;
// update our estimate
estimate += LFSR_ATTR_ESTIMATE + lfsr_data_size(&data__);
} else {
// we need to account for changes to left sibling here
attrs[attr_count++] = LFSR_ATTR(pos+rm+weight_-1,
SHRUB(GROW(WIDE(DATA))), -overlap_,
DATA(data__));
// update our estimate
estimate -= lfsr_data_size(&data_) - lfsr_data_size(&data__);
}
}
// found fully overwritten data?
if (pos <= bid_-(weight_-1) && pos + weight >= bid_+1) {
if (pos <= rid_-(weight_-1)
&& pos + weight >= (lfs_off_t)rid_+1
&& lfsr_shrub_hasshrub(&file->shrub)) {
// combine all rms into a single attr
rm += weight_;
// update estimate
estimate -= lfsr_data_size(&slice_);
// include any removed holes
if (lfsr_data_size(&slice_) < weight_) {
estimate -= LFSR_ATTR_ESTIMATE;
}
pos_ = bid_+1;
estimate -= LFSR_ATTR_ESTIMATE + lfsr_data_size(&data_);
}
pos_ = rid_+1;
}
// TODO can grow tags span multiple rids? if so can we merge
// the rm attribute with the data tag? maybe also use this for
// cheaper hole coalescing?
//
// write the combined rm attribute
if (rm > 0) {
attrs[attr_count++] = LFSR_ATTR(pos+rm-1,
TAG(lfsr_tag_mode(tag)
| LFSR_TAG_RM), -rm,
NULL);
SHRUB(RM), -rm, NULL);
}
// TODO make sure data-less holes get coalesced, though not we don't
// accurately track coalesced holes with our estimate because it simplifies
// things... document?
// need a hole?
if (pos > lfsr_shrub_size(&file->shrub)
// if we have no data we can coalesce our hole here
|| (weight + delta > 0 && lfsr_data_size(&data) == 0)) {
lfs_off_t pos_ = lfs_min32(pos, lfsr_shrub_size(&file->shrub));
lfs_off_t hole = pos - pos_
+ ((lfsr_data_size(&data) == 0) ? weight + delta : 0);
// there's a few cases we need to append an extra tag: we're appended data
// past the end of our btree and need a hole, or we're creating a new
// btree and didn't carve the existing data
if (!lfsr_tree_hasbtree(tree)
&& lfsr_tree_size(tree) > 0
&& (pos >= lfsr_tree_size(tree)
|| pos + weight == 0)) {
// note we never grow and rm a btree at the same time
LFS_ASSERT(rm == 0);
// we can usually get away with a simple grow attribute
if (pos_ > 0) {
attrs[attr_count++] = LFSR_ATTR(pos_-1,
SHRUB(GROW), +hole, NULL);
} else {
attrs[attr_count++] = LFSR_ATTR(pos_,
SHRUB(DATA), +hole, NULL);
attrs[attr_count++] = LFSR_ATTR(0,
TAG(lfsr_tag_mode(tag)
| LFSR_TAG_DATA), +lfs_max32(pos, lfsr_tree_size(tree)),
DATA(*(const lfsr_data_t*)tree));
// update our estimate
estimate += lfsr_data_size((const lfsr_data_t*)tree);
// we may be making a hole here
if (pos > lfsr_tree_size(tree)) {
// update our estimate
estimate += LFSR_ATTR_ESTIMATE;
}
// need a hole?
} else if (pos > lfsr_tree_size(tree)) {
// note we never grow and rm a btree at the same time
LFS_ASSERT(rm == 0);
attrs[attr_count++] = LFSR_ATTR(lfsr_tree_size(tree),
TAG(lfsr_tag_mode(tag)
| LFSR_TAG_DATA), +(pos - lfsr_tree_size(tree)),
NULL);
// turns out making a hole, makes a hole
estimate += LFSR_ATTR_ESTIMATE;
}
// finally append our data
if (weight + delta > 0) {
if (weight + delta > 0 && lfsr_data_size(&data) != 0) {
attrs[attr_count++] = LFSR_ATTR(pos,
TAG(tag), +(weight + delta), DATA(data));
// update our estimate
estimate += lfsr_data_size(&data);
// include the overhead of any new holes
if (lfsr_data_size(&data) < weight + delta) {
estimate += LFSR_ATTR_ESTIMATE;
}
estimate += LFSR_ATTR_ESTIMATE + lfsr_data_size(&data);
}
LFS_ASSERT(attr_count <= LFSR_CARVE_ATTRS);
LFS_ASSERT(buffer_off <= LFSR_CARVE_DSIZE);
if (estimate_) {
*estimate_ = estimate;
}
return attr_count;
}
static int lfsr_file_carveshrub(lfs_t *lfs, lfsr_file_t *file,
lfs_off_t pos, lfs_off_t weight, lfs_soff_t delta,
lfsr_tag_t tag, lfsr_data_t data) {
// we can pretend our shrub is a small btree to save a bit of code, we
// just need to commit the generated attributes through lfsr_mdir_commit
lfsr_attr_t attrs[LFSR_CARVE_ATTRS];
uint8_t attr_buf[LFSR_CARVE_DSIZE];
lfs_soff_t estimate;
lfs_ssize_t attr_count = lfsr_tree_buildcarve(lfs,
(const lfsr_tree_t*)&file->shrub.u.rbyd,
pos, weight, delta,
tag, data,
&estimate,
attrs, attr_buf);
if (attr_count < 0) {
return attr_count;
}
// we didn't overflow our attr buffer did we?
LFS_ASSERT(attr_count <= 4);
// do our attributes push us past our inline_size? we need to flush
// our shrub then
LFS_ASSERT(estimate >= -(lfs_soff_t)file->shrub.u.shrub.estimate);
if (file->shrub.u.shrub.estimate + estimate > lfs->cfg->inline_size) {
LFS_ASSERT((lfs_soff_t)estimate >= 0);
if (estimate > lfs->cfg->inline_size) {
// TODO
// return LFS_ERR_RANGE;
}
@@ -9778,7 +9971,146 @@ static int lfsr_file_carveshrub(lfs_t *lfs, lfsr_file_t *file,
}
// update our estimate
file->shrub.u.shrub.estimate += estimate;
file->shrub.u.shrub.estimate = estimate;
return 0;
}
// TODO this should also take in extra data during writes
static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) {
// Two things we need to do:
//
// 1. Try to coalesce with neighboring data
// if it fits in our fragment size.
//
// 2. Fragment writes into our fragment size.
//
lfs_size_t i = 0;
while (i < file->buffer_size) {
// truncate to our fragment size
lfs_off_t pos = file->buffer_pos + i;
lfsr_data_t data = LFSR_DATA_BUF(
file->buffer + i,
lfs_min32(
file->buffer_size - i,
lfs->cfg->fragment_size));
lfs_off_t d = lfsr_data_size(&data);
lfsr_data_t datas[3];
lfs_size_t data_count = 0;
datas[data_count++] = data;
// do we have a left neighbor?
if (pos > 0 && lfsr_shrub_size(&file->shrub) >= pos
// don't bother to lookup left after first fragment
&& i == 0) {
// TODO should these shrub lookups be deduplicated?
// lfsr_shrub_lookupnext?
lfsr_srid_t rid_;
lfsr_tag_t tag_;
lfsr_rid_t weight_;
lfsr_data_t data_;
// sprout or shrub?
if (!lfsr_shrub_hasshrub(&file->shrub)) {
rid_ = lfsr_data_size(&file->shrub.u.data)-1;
tag_ = LFSR_TAG_DATA;
weight_ = lfsr_data_size(&file->shrub.u.data);
data_ = file->shrub.u.data;
} else {
int err = lfsr_rbyd_lookupnext(lfs, &file->shrub.u.rbyd,
pos-1, 0,
&rid_, &tag_, &weight_, &data_);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
}
LFS_ASSERT(lfsr_tag_key(tag_) == LFSR_TAG_DATA);
// can we coalesce?
if (rid_-(weight_-1) + lfsr_data_size(&data_) >= pos
&& lfsr_data_size(&data_) < lfs->cfg->fragment_size) {
// TODO this is a bit of a hacky way to prepend data...
// coalesce, but truncate to our fragment size
LFS_ASSERT(data_count == 1);
datas[0] = LFSR_DATA_DISK(
data_.u.disk.block,
data_.u.disk.off,
pos - (rid_-(weight_-1)));
datas[1] = LFSR_DATA_BUF(
file->buffer + i,
lfs_min32(
file->buffer_size - i,
lfs->cfg->fragment_size - lfsr_data_size(&data_)));
data_count = 2;
data = lfsr_data_fromcat(datas, data_count);
pos = rid_-(weight_-1);
d = lfsr_data_size(&datas[1]);
}
}
// do we have a right neighbor?
//
// note this may the same as our left neighbor
if (pos + lfsr_data_size(&data) < lfsr_shrub_size(&file->shrub)
// don't bother to lookup right if fragment is already full
&& lfsr_data_size(&data) < lfs->cfg->fragment_size) {
// TODO should these shrub lookups be deduplicated?
// lfsr_shrub_lookupnext?
lfsr_srid_t rid_;
lfsr_tag_t tag_;
lfsr_rid_t weight_;
lfsr_data_t data_;
// sprout or shrub?
if (!lfsr_shrub_hasshrub(&file->shrub)) {
rid_ = lfsr_data_size(&file->shrub.u.data)-1;
tag_ = LFSR_TAG_DATA;
weight_ = lfsr_data_size(&file->shrub.u.data);
data_ = file->shrub.u.data;
} else {
int err = lfsr_rbyd_lookupnext(lfs, &file->shrub.u.rbyd,
pos+lfsr_data_size(&data), 0,
&rid_, &tag_, &weight_, &data_);
if (err) {
LFS_ASSERT(err != LFS_ERR_NOENT);
return err;
}
}
LFS_ASSERT(lfsr_tag_key(tag_) == LFSR_TAG_DATA);
// can we coalesce?
if (pos+lfsr_data_size(&data)
< rid_-(weight_-1) + lfsr_data_size(&data_)
&& lfsr_data_size(&data) + lfsr_data_size(&data_)
- (pos+lfsr_data_size(&data) - (rid_-(weight_-1)))
<= lfs->cfg->fragment_size) {
datas[data_count++] = LFSR_DATA_DISK(
data_.u.disk.block,
data_.u.disk.off
+ (pos+lfsr_data_size(&data) - (rid_-(weight_-1))),
lfsr_data_size(&data_)
- (pos+lfsr_data_size(&data) - (rid_-(weight_-1))));
data = lfsr_data_fromcat(datas, data_count);
}
}
// make sure we didn't overflow our data buffer
LFS_ASSERT(data_count <= 3);
// once we've figured out what fragment to write, carve it into
// our shrub
int err = lfsr_file_carveshrub(lfs, file,
pos, lfsr_data_size(&data), 0,
LFSR_TAG_SHRUB(DATA), data);
if (err) {
return err;
}
// and update our buffer
i += d;
}
file->buffer_size = 0;
return 0;
}
@@ -10708,34 +11040,34 @@ static int lfsr_file_flushshrub(lfs_t *lfs, lfsr_file_t *file) {
return 0;
}
static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) {
// TODO restructure this based on how flushshrub interacts with
// our buffer?
while (file->buffer_size > 0) {
// try to flush our buffer into our shrub
int err = lfsr_file_carveshrub(lfs, file,
file->buffer_pos, file->buffer_size, 0,
LFSR_TAG_SHRUB(DATA),
LFSR_DATA_BUF(file->buffer, file->buffer_size));
if (err) {
// uh oh, doesn't fit? flush inlined data and try again
if (err == LFS_ERR_RANGE) {
err = lfsr_file_flushshrub(lfs, file);
if (err) {
return err;
}
continue;
}
return err;
}
// we've flushed our buffer
file->buffer_size = 0;
}
return 0;
}
//static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) {
// // TODO restructure this based on how flushshrub interacts with
// // our buffer?
// while (file->buffer_size > 0) {
// // try to flush our buffer into our shrub
// int err = lfsr_file_carveshrub(lfs, file,
// file->buffer_pos, file->buffer_size, 0,
// LFSR_TAG_SHRUB(DATA),
// LFSR_DATA_BUF(file->buffer, file->buffer_size));
// if (err) {
// // uh oh, doesn't fit? flush inlined data and try again
// if (err == LFS_ERR_RANGE) {
// err = lfsr_file_flushshrub(lfs, file);
// if (err) {
// return err;
// }
//
// continue;
// }
// return err;
// }
//
// // we've flushed our buffer
// file->buffer_size = 0;
// }
//
// return 0;
//}
lfs_ssize_t lfsr_file_write(lfs_t *lfs, lfsr_file_t *file,
const void *buffer, lfs_size_t size) {