Reworked lfsr_data_t based on what functionality we actually use

- Ripped out outdated file-data representation. We don't need this.

- Changed lfsr_data_add/read/cmp to just assert when data is
  concatenated data. Theoretically this is possible to implement, but
  it's complicated and we never use it, so all it is is a waste of
  code size...

- Added implicitly zero-filled hole representation, though this isn't
  adopted in the code yet.

- Added lfsr_data_truncate/fruncate, these are really useful for
  shrub/tree carving/coalescing.

---

New lfsr_data_t encoding, sign(size) indicates if the data is
on-disk/in-device, and a mode field indicates how in-device data should
be parsed:

  sign(size)=1 => on-disk:

    .---+---+---+---.          .....
    |1|   size      |      ..''     ''..
    +---+---+---+---+     :    :        :
    |     block ------+->|            ..:|
    +---+---+---+---+ |  |......( )::::::|
    |      off -------'  |:::'    :      |
    '---+---+---+---'     :'       :    :
                           ''..     :.''
                               '''''

  sign(size)=0, mode=0 => in-device buffer:

    .---+---+---+---.   .---+---+---+---.
    |0|   size      | .>| data...       |
    +---+---+---+---+ | '       .       '
    |m=0|           | | '       .       '
    +---+---+---+---+ | '               '
    |      ptr -------' '               '
    '---+---+---+---'   '---+---+---+---'

  sign(size)=0, mode=1 => hole

    .---+---+---+---.
    |0|   size      |
    +---+---+---+---+
    |m=1|           |
    +---+           +
    |               |
    '---+---+---+---'

  sign(size)=0, mode=2 => inlined

    .---+---+---+---.
    |0|   size      |
    +---+---+---+---+
    |m=2| inlined d |
    +---+           +
    | ata...        |
    '---+---+---+---'

  sign(size)=0, mode=3 => concatenated datas:

    .---+---+---+---.   .---+---+---+---.
    |0|   size      | .>|     data      |
    +---+---+---+---+ | +               +
    |m=3| c |       | | |               |
    +---+---+---+---+ | +               +
    |      ptr -------' |               |
    '---+---+---+---'   +---+---+---+---+
                        |     data      |
                        +               +
                        |               |
                        +               +
                        |               |
                        +---+---+---+---+
                        '       .       '
                        '       .       '
                        '       .       '
                        '               '
                        '               '
                        '---+---+---+---'

---

Code/RAM changes:

            code          stack
  before:  31952           2056
  after:   31396 (-1.7%)   2064 (+0.4%)

I think the increased RAM cost is due to lfsr_data_add/truncate/fruncate
passing lfsr_data_t around by value, and GCC not being able to optimize
this very well since it's 3 words.  I think most move optimizations stop
after 2-words...
This commit is contained in:
Christopher Haster
2023-10-25 23:27:10 -05:00
parent 36e8f01261
commit 13192f3f6b
2 changed files with 254 additions and 451 deletions
+218 -415
View File
@@ -988,12 +988,16 @@ static lfs_ssize_t lfsr_bd_progtag(lfs_t *lfs,
/// lfsr_data_t stuff ///
// use the sign bit to indicate on-disk vs in-device
// data uses the size's sign bit to indicate on-disk vs in-device
#define LFSR_DATA_ONDISK 0x80000000
// on-disk datas use an extra sign bit to indicate if we're actually
// a pointer to a file
#define LFSR_DATA_ISFILE 0x80000000
// in-device data modes
enum {
LFSR_DATA_BUF = 0,
LFSR_DATA_HOLE = 1,
LFSR_DATA_IMM = 2,
LFSR_DATA_CAT = 3,
};
// LFSR_DATA_DATA just provides and escape hatch to pass raw datas
// through the LFSR_ATTR macro
@@ -1001,8 +1005,26 @@ static lfs_ssize_t lfsr_bd_progtag(lfs_t *lfs,
#define LFSR_DATA_NULL \
((lfsr_data_t){ \
.u.inlined.size=0, \
.u.inlined.count=0})
.u.buf.size=0, \
.u.buf.mode=LFSR_DATA_BUF, \
.u.buf.buffer=NULL})
#define LFSR_DATA_DISK(_block, _off, _size) \
((lfsr_data_t){ \
.u.disk.size=LFSR_DATA_ONDISK | (_size), \
.u.disk.block=_block, \
.u.disk.off=_off})
#define LFSR_DATA_BUF(_buffer, _size) \
((lfsr_data_t){ \
.u.buf.size=_size, \
.u.buf.mode=LFSR_DATA_BUF, \
.u.buf.buffer=(const void*)(_buffer)})
#define LFSR_DATA_HOLE(_size) \
((lfsr_data_t){ \
.u.hole.size=_size, \
.u.hole.mode=LFSR_DATA_HOLE})
#define LFSR_DATA_IMM(_buffer, _size) \
lfsr_data_fromimm(_buffer, _size)
@@ -1010,19 +1032,7 @@ static lfs_ssize_t lfsr_bd_progtag(lfs_t *lfs,
#define LFSR_DATA_LEB128(_word) \
lfsr_data_fromleb128(_word)
#define LFSR_DATA_BUF(_buffer, _size) \
((lfsr_data_t){ \
.u.direct.size=_size, \
.u.direct.count=1, \
.u.direct.buffer=(const void*)(_buffer)})
#define LFSR_DATA_DISK(_block, _off, _size) \
((lfsr_data_t){ \
.u.disk.size=(LFSR_DATA_ONDISK | (_size)), \
.u.disk.block=_block, \
.u.disk.off=_off})
// these rely on temporary allocations which is a bit precarious...
// this relies on temporary allocations which is a bit precarious...
#define LFSR_DATA_CAT(...) \
lfsr_data_fromcat( \
(const lfsr_data_t[]){__VA_ARGS__}, \
@@ -1034,70 +1044,72 @@ static lfs_ssize_t lfsr_bd_progtag(lfs_t *lfs,
// a move of all attrs from an mdir entry
#define LFSR_DATA_MOVE(_mdir) \
((lfsr_data_t){.u.direct.buffer=(const void*)(const lfsr_mdir_t*){_mdir}})
((lfsr_data_t){.u.buf.buffer=(const void*)(const lfsr_mdir_t*){_mdir}})
// a grm update, note this is mutable! we may update the grm during
// mdir commits
#define LFSR_DATA_GRM(_grm) \
((lfsr_data_t){.u.direct.buffer=(const void*)(lfsr_grm_t*){_grm}})
((lfsr_data_t){.u.buf.buffer=(const void*)(lfsr_grm_t*){_grm}})
// writing to an unrelated trunk in the rbyd
#define LFSR_DATA_SHRUBATTRS(_file, _attrs, _attr_count) \
((lfsr_data_t){.u.direct.buffer=(const void*)&(const lfsr_shrubattrs_t){ \
((lfsr_data_t){.u.buf.buffer=(const void*)&(const lfsr_shrubattrs_t){ \
.file=_file, \
.attrs=_attrs, \
.attr_count=_attr_count}})
// the reason for lazily encoding inlined trunks is because they can change
// underneath us during mdir compaction, the horror
#define LFSR_DATA_FILE(_file, _pos, _size) \
((lfsr_data_t){ \
.u.file.size=(LFSR_DATA_ONDISK | (_size)), \
.u.file.pos=(LFSR_DATA_ISFILE | (_pos)), \
.u.file.file=_file})
#define LFSR_DATA_SHRUBTRUNK(_file) \
((lfsr_data_t){.u.buf.buffer=(const void*)(const lfsr_file_t*){_file}})
static inline bool lfsr_data_ondisk(const lfsr_data_t *data) {
return data->u.size & LFSR_DATA_ONDISK;
}
static inline bool lfsr_data_isfile(const lfsr_data_t *data) {
return lfsr_data_ondisk(data) && (data->u.file.pos & LFSR_DATA_ISFILE);
static inline bool lfsr_data_isbuf(const lfsr_data_t *data) {
return !lfsr_data_ondisk(data) && data->u.buf.mode == LFSR_DATA_BUF;
}
static inline bool lfsr_data_ishole(const lfsr_data_t *data) {
return !lfsr_data_ondisk(data) && data->u.buf.mode == LFSR_DATA_HOLE;
}
static inline bool lfsr_data_isimm(const lfsr_data_t *data) {
return !lfsr_data_ondisk(data) && data->u.buf.mode == LFSR_DATA_IMM;
}
static inline bool lfsr_data_iscat(const lfsr_data_t *data) {
return !lfsr_data_ondisk(data) && data->u.buf.mode == LFSR_DATA_CAT;
}
static inline lfs_size_t lfsr_data_size(const lfsr_data_t *data) {
return data->u.size & ~LFSR_DATA_ONDISK;
}
static inline lfs_off_t lfsr_data_pos(const lfsr_data_t *data) {
return data->u.file.pos & ~LFSR_DATA_ISFILE;
}
// some data initializers just can't be macros, we at least make these inline
// so most of the internal logic gets elided
// so most of the internal logic is hopefully elided
static inline lfsr_data_t lfsr_data_fromimm(
const void *buffer, lfs_size_t size) {
LFS_ASSERT(size <= 5);
lfsr_data_t data;
memcpy(data.u.inlined.buf, buffer, size);
data.u.inlined.size = size;
data.u.inlined.count = 0;
memcpy(data.u.imm.buf, buffer, size);
data.u.imm.size = size;
data.u.imm.mode = LFSR_DATA_IMM;
return data;
}
static inline lfsr_data_t lfsr_data_fromleb128(int32_t word) {
lfsr_data_t data;
lfs_ssize_t size = lfs_toleb128(word, data.u.inlined.buf, 5);
lfs_ssize_t size = lfs_toleb128(word, data.u.imm.buf, 5);
LFS_ASSERT(size >= 0);
LFS_ASSERT(size <= 5);
data.u.inlined.size = size;
data.u.inlined.count = 0;
data.u.imm.size = size;
data.u.imm.mode = LFSR_DATA_IMM;
return data;
}
static inline lfsr_data_t lfsr_data_fromcat(
const lfsr_data_t *datas, lfs_size_t count) {
LFS_ASSERT(count >= 2);
LFS_ASSERT(count <= 255);
// find total size
@@ -1107,47 +1119,87 @@ static inline lfsr_data_t lfsr_data_fromcat(
}
return (lfsr_data_t){
.u.indirect.size=size,
.u.indirect.count=count,
.u.indirect.datas=datas};
.u.cat.size=size,
.u.cat.mode=LFSR_DATA_CAT,
.u.cat.count=count,
.u.cat.datas=datas};
}
static void lfsr_data_add(lfsr_data_t *data, lfs_size_t off) {
lfsr_data_t data_ = *data;
// note these operations only work on "simple" (not concatenated) datas
static lfsr_data_t lfsr_data_add(lfsr_data_t data, lfs_size_t off) {
// limit our off to data range
lfs_size_t off_ = lfs_min32(off, lfsr_data_size(&data_));
lfs_size_t off_ = lfs_min32(off, lfsr_data_size(&data));
// on-disk? increment
if (lfsr_data_ondisk(&data_)) {
data_.u.disk.off += off_;
data_.u.disk.size -= off_;
if (lfsr_data_ondisk(&data)) {
data.u.disk.off += off_;
data.u.disk.size -= off_;
// buffer? increment
} else if (lfsr_data_isbuf(&data)) {
data.u.buf.buffer += off_;
data.u.buf.size -= off_;
// hole? decrement
} else if (lfsr_data_ishole(&data)) {
data.u.hole.size -= off_;
// inlined? internal memmove
} else if (data_.u.indirect.count == 0) {
memmove(data_.u.inlined.buf,
data_.u.inlined.buf + off_,
data_.u.inlined.size - off_);
data_.u.inlined.size -= off_;
} else if (lfsr_data_isimm(&data)) {
memmove(data.u.imm.buf,
data.u.imm.buf + off_,
data.u.imm.size - off_);
data.u.imm.size -= off_;
// direct? increment
} else if (data_.u.indirect.count == 1) {
data_.u.direct.buffer += off_;
data_.u.direct.size -= off_;
// indirect? just update size, more on how this works in lfsr_data_read
// concatenated? not supported
} else {
data_.u.indirect.size -= off_;
LFS_UNREACHABLE();
}
*data = data_;
return data;
}
static lfsr_data_t lfsr_data_truncate(lfsr_data_t data, lfs_size_t size) {
LFS_ASSERT(size <= lfsr_data_size(&data));
// on-disk? update size
if (lfsr_data_ondisk(&data)) {
data.u.disk.size = LFSR_DATA_ONDISK | size;
// buffer? update size
} else if (lfsr_data_isbuf(&data)) {
data.u.buf.size = size;
// hole? update size
} else if (lfsr_data_ishole(&data)) {
data.u.hole.size = size;
// inlined? update size
} else if (lfsr_data_isimm(&data)) {
data.u.imm.size = size;
// concatenated? not supported
} else {
LFS_UNREACHABLE();
}
return data;
}
static lfsr_data_t lfsr_data_fruncate(lfsr_data_t data, lfs_size_t size) {
LFS_ASSERT(size <= lfsr_data_size(&data));
// lfsr_data_fruncate and lfsr_data_add are basically the same operation
return lfsr_data_add(data, lfsr_data_size(&data) - size);
}
// data <-> bd interactions
// lfsr_data_read* operations update the lfsr_data_t, effectively
// consuming the data
static lfs_ssize_t lfsr_data_read_(lfs_t *lfs, const lfsr_data_t *data,
static lfs_ssize_t lfsr_data_read(lfs_t *lfs, lfsr_data_t *data,
void *buffer, lfs_size_t size) {
// limit our size to data range
lfs_size_t d = lfs_min32(size, lfsr_data_size(data));
@@ -1162,78 +1214,24 @@ static lfs_ssize_t lfsr_data_read_(lfs_t *lfs, const lfsr_data_t *data,
return err;
}
// buffer?
} else if (lfsr_data_isbuf(data)) {
memcpy(buffer, data->u.buf.buffer, d);
// hole?
} else if (lfsr_data_ishole(data)) {
memset(buffer, 0, d);
// inlined?
} else if (data->u.indirect.count == 0) {
memcpy(buffer, data->u.inlined.buf, d);
} else if (lfsr_data_isimm(data)) {
memcpy(buffer, data->u.imm.buf, d);
// direct?
} else if (data->u.indirect.count == 1) {
memcpy(buffer, data->u.direct.buffer, d);
// indirect? we shouldn't handle this here
// concatenated? not supported
} else {
LFS_UNREACHABLE();
}
return d;
}
static lfs_size_t lfsr_data_indirectoff(const lfsr_data_t *data) {
LFS_ASSERT(!lfsr_data_ondisk(data));
LFS_ASSERT(data->u.indirect.count >= 2);
lfs_size_t indirect_size = 0;
for (uint8_t i = 0; i < data->u.indirect.count; i++) {
indirect_size += lfsr_data_size(&data->u.indirect.datas[i]);
}
return indirect_size - data->u.indirect.size;
}
static lfs_ssize_t lfsr_data_read(lfs_t *lfs, lfsr_data_t *data,
void *buffer, lfs_size_t size) {
// handle indirect data specially to avoid recursion
lfs_ssize_t d;
if (!lfsr_data_ondisk(data) && data->u.indirect.count >= 2) {
// Indirect data is a bit complicated because we don't want to modify
// the indirect datas themselves. It's tempting to just make them
// mutable, but this breaks the common pattern of using shallow copies
// of lfsr_data_t to cheaply track progress.
//
// The solution here is to use the redundant sizes to figure out what
// we've read so far. This risks O(n^2) performance, but we usually
// only have a couple indirect datas at most
//
lfs_size_t indirect_off = lfsr_data_indirectoff(data);
uint8_t *buffer_ = buffer;
d = 0;
for (uint8_t i = 0; i < data->u.indirect.count && size > 0; i++) {
// skip consumed data
lfsr_data_t indirect_data = data->u.indirect.datas[i];
lfsr_data_add(&indirect_data, indirect_off);
indirect_off -= lfs_min32(
lfsr_data_size(&indirect_data),
indirect_off);
// read unconsumed data
lfs_ssize_t d_ = lfsr_data_read_(lfs, &indirect_data,
buffer_, size);
if (d_ < 0) {
return d_;
}
buffer_ += d_;
size -= d_;
d += d_;
}
} else {
d = lfsr_data_read_(lfs, data, buffer, size);
if (d < 0) {
return d;
}
}
lfsr_data_add(data, d);
*data = lfsr_data_add(*data, d);
return d;
}
@@ -1272,11 +1270,11 @@ static int lfsr_data_readleb128(lfs_t *lfs, lfsr_data_t *data,
return d;
}
lfsr_data_add(data, d);
*data = lfsr_data_add(*data, d);
return 0;
}
static lfs_scmp_t lfsr_data_cmp_(lfs_t *lfs, const lfsr_data_t *data,
static lfs_scmp_t lfsr_data_cmp(lfs_t *lfs, const lfsr_data_t *data,
const void *buffer, lfs_size_t size) {
// limit our size to data range
lfs_size_t d = lfs_min32(size, lfsr_data_size(data));
@@ -1289,25 +1287,41 @@ static lfs_scmp_t lfsr_data_cmp_(lfs_t *lfs, const lfsr_data_t *data,
return cmp;
}
// buffer?
} else if (lfsr_data_isbuf(data)) {
int cmp = memcmp(data->u.buf.buffer, buffer, d);
if (cmp < 0) {
return LFS_CMP_LT;
} else if (cmp > 0) {
return LFS_CMP_GT;
}
// hole?
} else if (lfsr_data_ishole(data)) {
const uint8_t *buffer_ = buffer;
int cmp = 0;
if (d > 0) {
cmp = buffer_[0] - 0;
if (cmp == 0) {
cmp = memcmp(buffer_, buffer_+1, size-1);
}
}
if (cmp < 0) {
return LFS_CMP_LT;
} else if (cmp > 0) {
return LFS_CMP_GT;
}
// inlined?
} else if (data->u.indirect.count == 0) {
int cmp = memcmp(data->u.inlined.buf, buffer, d);
} else if (lfsr_data_isimm(data)) {
int cmp = memcmp(data->u.imm.buf, buffer, d);
if (cmp < 0) {
return LFS_CMP_LT;
} else if (cmp > 0) {
return LFS_CMP_GT;
}
// direct?
} else if (data->u.indirect.count == 1) {
int cmp = memcmp(data->u.direct.buffer, buffer, d);
if (cmp < 0) {
return LFS_CMP_LT;
} else if (cmp > 0) {
return LFS_CMP_GT;
}
// indirect? we shouldn't handle this here
// concatenated? not supported
} else {
LFS_UNREACHABLE();
}
@@ -1322,52 +1336,6 @@ static lfs_scmp_t lfsr_data_cmp_(lfs_t *lfs, const lfsr_data_t *data,
}
}
static lfs_scmp_t lfsr_data_cmp(lfs_t *lfs, const lfsr_data_t *data,
const void *buffer, lfs_size_t size) {
// handle indirect data specially to avoid recursion
if (!lfsr_data_ondisk(data) && data->u.indirect.count >= 2) {
// Indirect data is a bit complicated because we don't want to modify
// the indirect datas themselves. It's tempting to just make them
// mutable, but this breaks the common pattern of using shallow copies
// of lfsr_data_t to cheaply track progress.
//
// The solution here is to use the redundant sizes to figure out what
// we've read so far. This risks O(n^2) performance, but we usually
// only have a couple indirect datas at most
//
lfs_size_t indirect_off = lfsr_data_indirectoff(data);
const uint8_t *buffer_ = buffer;
for (uint8_t i = 0; i < data->u.indirect.count && size > 0; i++) {
// skip consumed data
lfsr_data_t indirect_data = data->u.indirect.datas[i];
lfsr_data_add(&indirect_data, indirect_off);
indirect_off -= lfs_min32(
lfsr_data_size(&indirect_data),
indirect_off);
// compare against unconsumed data
lfs_size_t d = lfs_min32(size, lfsr_data_size(&indirect_data));
lfs_scmp_t cmp = lfsr_data_cmp_(lfs, &indirect_data, buffer_, d);
if (cmp != LFS_CMP_EQ) {
return cmp;
}
buffer_ += d;
size -= d;
}
// data is equal, the only remaining condition to check for is size
// remaining
if (size > 0) {
return LFS_CMP_LT;
} else {
return LFS_CMP_EQ;
}
} else {
return lfsr_data_cmp_(lfs, data, buffer, size);
}
}
static lfs_scmp_t lfsr_data_namecmp(lfs_t *lfs, const lfsr_data_t *data,
lfsr_did_t did, const char *name, lfs_size_t name_size) {
// first compare the did
@@ -1388,20 +1356,6 @@ static lfs_scmp_t lfsr_data_namecmp(lfs_t *lfs, const lfsr_data_t *data,
return lfsr_data_cmp(lfs, &data_, name, name_size);
}
// TODO if we're declaring these here, can lfsr_mdir_commit use them?
// TODO should lfsr_shrub_* be moved closer to file and just predeclared
// for lfsr_mdir_commit?
// needed for lfsr_bd_progdata
static inline bool lfsr_shrub_isnull(const lfsr_shrub_t *shrub);
static inline bool lfsr_shrub_hassprout(const lfsr_shrub_t *shrub);
static inline bool lfsr_shrub_hasshrub(const lfsr_shrub_t *shrub);
static lfs_off_t lfsr_shrub_size(const lfsr_shrub_t *shrub);
static int lfsr_rbyd_lookupnext(lfs_t *lfs, const lfsr_rbyd_t *rbyd,
lfsr_srid_t rid, lfsr_tag_t tag,
lfsr_srid_t *rid_,
lfsr_tag_t *tag_, lfsr_rid_t *weight_, lfsr_data_t *data_);
static int lfsr_bd_progdata_(lfs_t *lfs,
lfs_block_t block, lfs_size_t off, lfsr_data_t data,
uint32_t *cksum_) {
@@ -1427,25 +1381,36 @@ static int lfsr_bd_progdata_(lfs_t *lfs,
}
}
// buffer?
} else if (lfsr_data_isbuf(&data)) {
int err = lfsr_bd_prog(lfs, block, off,
data.u.buf.buffer, data.u.buf.size,
cksum_);
if (err) {
return err;
}
// hole?
} else if (lfsr_data_ishole(&data)) {
// TODO do something better than byte-level progs here
for (lfs_size_t i = 0; i < lfsr_data_size(&data); i++) {
int err = lfsr_bd_prog(lfs, block, off+i, &(uint8_t){0}, 1,
cksum_);
if (err) {
return err;
}
}
// inlined?
} else if (data.u.indirect.count == 0) {
} else if (lfsr_data_isimm(&data)) {
int err = lfsr_bd_prog(lfs, block, off,
data.u.inlined.buf, data.u.inlined.size,
data.u.imm.buf, data.u.imm.size,
cksum_);
if (err) {
return err;
}
// direct?
} else if (data.u.indirect.count == 1) {
int err = lfsr_bd_prog(lfs, block, off,
data.u.direct.buffer, data.u.direct.size,
cksum_);
if (err) {
return err;
}
// indirect? we shouldn't handle this here
// concatenated? nesting would require recursion
} else {
LFS_UNREACHABLE();
}
@@ -1456,136 +1421,24 @@ static int lfsr_bd_progdata_(lfs_t *lfs,
static int lfsr_bd_progdata(lfs_t *lfs,
lfs_block_t block, lfs_size_t off, lfsr_data_t data,
uint32_t *cksum_) {
// TODO clean this up?
// handle inlined file references specially to avoid recursion
if (lfsr_data_isfile(&data)) {
const lfsr_file_t *file = data.u.file.file;
lfs_off_t pos = lfsr_data_pos(&data);
lfs_size_t size = lfsr_data_size(&data);
while (pos - lfsr_data_pos(&data) < size) {
lfs_off_t d = size - (pos - lfsr_data_pos(&data));
// TODO deduplicate this into some sort of data-returning
// lfsr_file_iter?
// prioritize our buffer
if (pos < file->buffer_pos + file->buffer_size) {
if (pos >= file->buffer_pos) {
d = lfs_min32(
file->buffer_size - (pos - file->buffer_pos),
d);
int err = lfsr_bd_progdata_(lfs, block, off,
LFSR_DATA_BUF(
file->buffer + (pos - file->buffer_pos), d),
cksum_);
if (err) {
return err;
}
pos += d;
off += d;
continue;
}
d = lfs_min32(d, file->buffer_pos - pos);
}
// has a sprout?
if (lfsr_shrub_hassprout(&file->shrub)
&& pos < lfsr_shrub_size(&file->shrub)) {
d = lfs_min32(lfsr_shrub_size(&file->shrub) - pos, d);
int err = lfsr_bd_progdata_(lfs, block, off,
LFSR_DATA_DISK(
file->shrub.u.data.u.disk.block,
file->shrub.u.data.u.disk.off + pos,
d),
cksum_);
if (err) {
return err;
}
pos += d;
off += d;
continue;
}
// has a shrub?
if (lfsr_shrub_hasshrub(&file->shrub)
&& pos < lfsr_shrub_size(&file->shrub)) {
lfsr_srid_t rid;
lfsr_tag_t tag;
lfsr_rid_t weight;
lfsr_data_t 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(tag == LFSR_TAG_SHRUB(DATA));
LFS_ASSERT(lfsr_data_size(&data) <= weight);
if (pos < rid-(weight-1) + lfsr_data_size(&data)) {
d = lfs_min32(
lfsr_data_size(&data) - (pos - (rid-(weight-1))),
d);
int err = lfsr_bd_progdata_(lfs, block, off,
LFSR_DATA_DISK(
data.u.disk.block,
data.u.disk.off + (pos - (rid-(weight-1))),
d),
cksum_);
if (err) {
return err;
}
pos += d;
off += d;
continue;
}
d = lfs_min32(d, rid+1 - pos);
}
// found a hole, upper layers should make sure this doesn't happen
LFS_UNREACHABLE();
// simple data?
if (!lfsr_data_iscat(&data)) {
int err = lfsr_bd_progdata_(lfs, block, off, data,
cksum_);
if (err) {
return err;
}
// handle indirect data specially to avoid recursion
} else if (!lfsr_data_ondisk(&data) && data.u.indirect.count >= 2) {
// Indirect data is a bit complicated because we don't want to modify
// the indirect datas themselves. It's tempting to just make them
// mutable, but this breaks the common pattern of using shallow copies
// of lfsr_data_t to cheaply track progress.
//
// The solution here is to use the redundant sizes to figure out what
// we've read so far. This risks O(n^2) performance, but we usually
// only have a couple indirect datas at most
//
lfs_size_t indirect_off = lfsr_data_indirectoff(&data);
for (uint8_t i = 0; i < data.u.indirect.count; i++) {
// skip consumed data
lfsr_data_t indirect_data = data.u.indirect.datas[i];
lfsr_data_add(&indirect_data, indirect_off);
indirect_off -= lfs_min32(
lfsr_data_size(&indirect_data),
indirect_off);
// prog unconsumed data
int err = lfsr_bd_progdata_(lfs, block, off, indirect_data,
// concatenated data? handle specially to avoid recursion
} else {
for (uint8_t i = 0; i < data.u.cat.count; i++) {
int err = lfsr_bd_progdata_(lfs, block, off, data.u.cat.datas[i],
cksum_);
if (err) {
return err;
}
off += lfsr_data_size(&indirect_data);
}
} else {
int err = lfsr_bd_progdata_(lfs, block, off, data,
cksum_);
if (err) {
return err;
off += lfsr_data_size(&data.u.cat.datas[i]);
}
}
@@ -5457,7 +5310,7 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir,
// weighted moves are not supported
LFS_ASSERT(attrs[i].delta == 0);
const lfsr_mdir_t *mdir__
= (const lfsr_mdir_t*)attrs[i].data.u.direct.buffer;
= (const lfsr_mdir_t*)attrs[i].data.u.buf.buffer;
// skip the name tag, this is always replaced by upper layers
lfsr_tag_t tag = LFSR_TAG_STRUCT-1;
@@ -5487,7 +5340,7 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir,
} else if (attrs[i].tag == LFSR_TAG_SHRUBATTRS) {
const lfsr_shrubattrs_t *shrubattrs
= (const lfsr_shrubattrs_t*)
attrs[i].data.u.direct.buffer;
attrs[i].data.u.buf.buffer;
// swap out our trunk/weight temporarily, note we're operating
// on a copy so if this fails not _too_ many things will get
@@ -5527,7 +5380,7 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir,
// TODO should we preserve mode for all of these?
// TODO should we do the same for sprouts?
} else if (lfsr_tag_key(attrs[i].tag) == LFSR_TAG_SHRUBTRUNK) {
lfsr_file_t *file = (lfsr_file_t*)attrs[i].data.u.direct.buffer;
lfsr_file_t *file = (lfsr_file_t*)attrs[i].data.u.buf.buffer;
uint8_t trunk_buf[LFSR_TRUNK_DSIZE];
int err = lfsr_rbyd_appendattr(lfs, &mdir_.u.rbyd,
@@ -6042,7 +5895,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
for (lfs_size_t i = 0; i < attr_count; i++) {
if (attrs[i].tag == LFSR_TAG_GRM) {
// encode to disk
lfsr_grm_t *grm = (lfsr_grm_t*)attrs[i].data.u.direct.buffer;
lfsr_grm_t *grm = (lfsr_grm_t*)attrs[i].data.u.buf.buffer;
lfsr_data_fromgrm(grm, lfs->dgrm);
// xor with our current gstate to find our initial gdelta
@@ -6305,7 +6158,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
//
// gd' = gd xor (grm' xor grm)
//
lfsr_grm_t *grm = (lfsr_grm_t*)attrs[i].data.u.direct.buffer;
lfsr_grm_t *grm = (lfsr_grm_t*)attrs[i].data.u.buf.buffer;
uint8_t grm_buf[LFSR_GRM_DSIZE];
err = lfsr_grm_xor(lfs, lfs->dgrm,
lfsr_data_fromgrm(grm, grm_buf));
@@ -6480,7 +6333,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
for (lfs_size_t i = 0; i < attr_count; i++) {
// update gstate
if (attrs[i].tag == LFSR_TAG_GRM) {
lfs->grm = *(lfsr_grm_t*)attrs[i].data.u.direct.buffer;
lfs->grm = *(lfsr_grm_t*)attrs[i].data.u.buf.buffer;
// keep track of the exact encoding on-disk
lfsr_data_fromgrm(&lfs->grm, lfs->ggrm);
@@ -9544,9 +9397,7 @@ static int lfsr_shrub_carve(lfs_t *lfs, lfsr_file_t *file, lfsr_shrub_t *shrub,
lfs_off_t overlap_ = (rid_+1) - pos;
LFS_ASSERT((lfs_soff_t)overlap_ >= 0);
lfsr_data_t slice_ = LFSR_DATA_DISK(
data_.u.disk.block,
data_.u.disk.off,
lfsr_data_t slice_ = lfsr_data_truncate(data_,
lfs_min32(
weight_ - overlap_,
lfsr_data_size(&data_)));
@@ -9584,11 +9435,7 @@ static int lfsr_shrub_carve(lfs_t *lfs, lfsr_file_t *file, lfsr_shrub_t *shrub,
lfs_off_t overlap_ = (pos + weight) - (rid_-(weight_-1));
LFS_ASSERT((lfs_soff_t)overlap_ >= 0);
lfsr_data_t slice_ = LFSR_DATA_DISK(
data_.u.disk.block,
data_.u.disk.off + lfs_min32(
overlap_,
lfsr_data_size(&data_)),
lfsr_data_t slice_ = lfsr_data_fruncate(data_,
lfsr_data_size(&data_) - lfs_min32(
overlap_,
lfsr_data_size(&data_)));
@@ -9755,16 +9602,13 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) {
// coalesce, but truncate to our fragment size
// TODO this is a bit of a hacky way to prepend data...
LFS_ASSERT(data_count == 1);
datas[0] = LFSR_DATA_DISK(
data_.u.disk.block,
data_.u.disk.off,
datas[0] = lfsr_data_truncate(data_,
pos - (rid_-(weight_-1)));
datas[1] = LFSR_DATA_BUF(
data.u.direct.buffer,
datas[1] = lfsr_data_truncate(data,
lfs_min32(
lfsr_data_size(&data),
lfs->cfg->fragment_size
- lfsr_data_size(&datas[0])));
- (pos - (rid_-(weight_-1)))));
data_count = 2;
data = lfsr_data_fromcat(datas, data_count);
@@ -9797,10 +9641,7 @@ static int lfsr_file_flushbuffer(lfs_t *lfs, lfsr_file_t *file) {
&& 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))),
datas[data_count++] = lfsr_data_fruncate(data_,
lfsr_data_size(&data_)
- (pos+lfsr_data_size(&data) - (rid_-(weight_-1))));
data = lfsr_data_fromcat(datas, data_count);
@@ -9892,9 +9733,7 @@ static int lfsr_tree_carve(lfs_t *lfs, lfsr_tree_t *tree,
lfs_off_t overlap_ = (bid_+1) - pos;
LFS_ASSERT((lfs_soff_t)overlap_ >= 0);
lfsr_data_t slice_ = LFSR_DATA_DISK(
data_.u.disk.block,
data_.u.disk.off,
lfsr_data_t slice_ = lfsr_data_truncate(data_,
lfs_min32(
weight_ - overlap_,
lfsr_data_size(&data_)));
@@ -9954,11 +9793,7 @@ static int lfsr_tree_carve(lfs_t *lfs, lfsr_tree_t *tree,
lfs_off_t overlap_ = (pos + weight) - (bid_-(weight_-1));
LFS_ASSERT((lfs_soff_t)overlap_ >= 0);
lfsr_data_t slice_ = LFSR_DATA_DISK(
data_.u.disk.block,
data_.u.disk.off + lfs_min32(
overlap_,
lfsr_data_size(&data_)),
lfsr_data_t slice_ = lfsr_data_fruncate(data_,
lfsr_data_size(&data_) - lfs_min32(
overlap_,
lfsr_data_size(&data_)));
@@ -10005,11 +9840,7 @@ static int lfsr_tree_carve(lfs_t *lfs, lfsr_tree_t *tree,
lfs_off_t overlap_ = (pos + weight) - (bid_-(weight_-1));
LFS_ASSERT((lfs_soff_t)overlap_ >= 0);
lfsr_data_t slice_ = LFSR_DATA_DISK(
data_.u.disk.block,
data_.u.disk.off + lfs_min32(
overlap_,
lfsr_data_size(&data_)),
lfsr_data_t slice_ = lfsr_data_fruncate(data_,
lfsr_data_size(&data_) - lfs_min32(
overlap_,
lfsr_data_size(&data_)));
@@ -10321,16 +10152,7 @@ static int lfsr_file_flushshrub(lfs_t *lfs, lfsr_file_t *file) {
} else {
// truncate to our fragment size
d = lfs_min32(d, lfs->cfg->fragment_size);
// TODO uh oh, need a function for this?
// TODO lfsr_data_truncate? is this even possible generally?
if (!lfsr_data_ondisk(&data)) {
data = LFSR_DATA_BUF(data.u.direct.buffer, d);
} else {
data = LFSR_DATA_DISK(
data.u.disk.block,
data.u.disk.off,
d);
}
data = lfsr_data_truncate(data, d);
lfsr_data_t datas[3];
lfs_size_t data_count = 0;
@@ -10359,28 +10181,13 @@ static int lfsr_file_flushshrub(lfs_t *lfs, lfsr_file_t *file) {
// coalesce, but truncate to our fragment size
// TODO this is a bit of a hacky way to prepend data...
LFS_ASSERT(data_count == 1);
datas[0] = LFSR_DATA_DISK(
data_.u.disk.block,
data_.u.disk.off,
datas[0] = lfsr_data_truncate(data_,
pos - (bid_-(weight_-1)));
// TODO uh oh, need a function for this?
// TODO lfsr_data_truncate? is this even possible generally?
if (!lfsr_data_ondisk(&data)) {
datas[1] = LFSR_DATA_BUF(
data.u.direct.buffer,
lfs_min32(
lfsr_data_size(&data),
lfs->cfg->fragment_size
- lfsr_data_size(&datas[0])));
} else {
datas[1] = LFSR_DATA_DISK(
data.u.disk.block,
data.u.disk.off,
lfs_min32(
lfsr_data_size(&data),
lfs->cfg->fragment_size
- lfsr_data_size(&datas[0])));
}
datas[1] = lfsr_data_truncate(data,
lfs_min32(
lfsr_data_size(&data),
lfs->cfg->fragment_size
- (pos - (bid_-(weight_-1)))));
data_count = 2;
data = lfsr_data_fromcat(datas, data_count);
@@ -10415,11 +10222,7 @@ static int lfsr_file_flushshrub(lfs_t *lfs, lfsr_file_t *file) {
- (pos+lfsr_data_size(&data)
- (bid_-(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)
- (bid_-(weight_-1))),
datas[data_count++] = lfsr_data_fruncate(data_,
lfsr_data_size(&data_)
- (pos+lfsr_data_size(&data)
- (bid_-(weight_-1))));
@@ -10610,7 +10413,7 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
err = lfsr_mdir_commit(lfs, &file->m.mdir, LFSR_ATTRS(
(lfsr_shrub_hasshrub(&file->shrub)
? LFSR_ATTR(file->m.mdir.mid,
WIDE(SHRUBTRUNK), 0, FILE(file, 0, 0))
WIDE(SHRUBTRUNK), 0, SHRUBTRUNK(file))
: LFSR_ATTR(file->m.mdir.mid,
RM(WIDE(STRUCT)), 0, NULL)),
// and any btree metadata?
+36 -36
View File
@@ -446,52 +446,52 @@ typedef struct lfs_mdir {
lfs_block_t tail[2];
} lfs_mdir_t;
// either an on-disk or in-device data pointer
// Either an on-disk or in-device data pointer
//
// The sign-bit of the size field indicates if the data is
// in-device or on-disk.
//
// After removing the sign bit, the size always encodes the
// resulting size on-disk.
//
// The exact representation of in-device data also depends on the
// mode field:
// - pointer to a RAM-backed buffer
// - implicitly zero-filled holes
// - inlined data able to fit at least 1 leb128
// - an array of concatenated datas
//
// Note concatenated datas can only be 1 level deep. Concatenating
// concatenated datas would require recursion to resolve.
//
typedef struct lfsr_data {
union {
// The sign-bit of the size field indicates if the data is in-device
// or on-disk.
//
// After removing the sign bit, the size always encodes the resulting
// size on-disk.
//
// After this the count field indicates the in-device representation,
// which has a few forms:
// - count == 0 => data inlined in data struct
// - count == 1 => direct pointer to data
// - count >= 2 => indirect pointer to array of datas
//
// The indirect pointer can point to inlined/direct datas or even
// on-disk datas, but not more indirect datas as that would require
// recursion.
//
lfs_ssize_t size;
struct {
lfs_ssize_t size;
uint8_t count;
uint8_t buf[5];
} inlined;
struct {
lfs_ssize_t size;
uint8_t count;
const uint8_t *buffer;
} direct;
struct {
lfs_ssize_t size;
uint8_t count;
const struct lfsr_data *datas;
} indirect;
struct {
lfs_ssize_t size;
lfs_block_t block;
lfs_size_t off;
} disk;
// TODO doc
struct {
lfs_ssize_t size;
lfs_off_t pos;
const struct lfsr_file *file;
} file;
uint8_t mode;
const uint8_t *buffer;
} buf;
struct {
lfs_ssize_t size;
uint8_t mode;
} hole;
struct {
lfs_ssize_t size;
uint8_t mode;
uint8_t buf[5];
} imm;
struct {
lfs_ssize_t size;
uint8_t mode;
uint8_t count;
const struct lfsr_data *datas;
} cat;
} u;
} lfsr_data_t;