Adopted crammed 15-bit size lfsr_attr_t representation
Another interesting observation about lfsr_cat_t: We rarely actually
need to represent the full range of data:
- lfsr_file_write:block - Block writes call lfsr_bd_prog directly, don't
need to be represented as attrs.
- lfsr_file_write:fragment - Fragments already need the full
concatenated representation because of, uh, potential concatenation.
- lfsr_file_sync:sprouts - Inlined sprouts may use the full range.
Fortunately we can union an lfsr_data_t with the btree buffer, so
no extra stack cost.
- lfsr_getuattr/sattr (planned) - User/sys attributes may use the full
range. But these will probably not be on the stack hot-path.
Most attrs that use the simple buffer lfsr_cat_t representation are used
to encode internal structs, such as leb128s, ecksum, bptr, mptr, etc.
The largest of these right now is our bptr encoding, at 21 bytes, so
these easily fit in a short or a byte.
The choice of 15-bits (reserving one bit for cat/buf representation), is
convenient as it allows us to fit the cat size next to our 16-bit
lfsr_tag_t for free in 32-bit aligned systems:
lfsr_attr_t
.---+---+---+---.
| tag |c|size |
+---+---+---+---+
| delta |
+---+---+---+---+
| ptr |
'---+---+---+---'
Unfortunately, it seems like C _really_ wants to fight us on this one.
Not really because of the struct packing, but because of how we want
lfsr_attr_t to interact with lfsr_cat_t:
lfsr_tag_t lfsr_attr_t
.---+---. .---+---+---+---.
| tag |-----------+--->| tag |c|size |
'---+---' | +---+---+---+---+
.-|--->| delta |
lfsr_srid_t | | +---+---+---+---+
.---+---+---+---. | | .->| ptr |
| delta |-' | | '---+---+---+---'
'---+---+---+---' | |
| |
lfsr_cat_t | |
.---+---+---+---. | |
|c|size |-----------' |
+---+---+---+---+ |
| ptr |-----'
'---+---+---+---'
Initializing two fields with one argument is frustratingly impossible in
C99 unless you want to duplicate the argument tree, which we
_definitely_ don't want to do because this includes the actual data
encoding steps.
The only option is to use a static inline function. You might say "oh,
but static inline costs the same as a macro". But no. Switching to a
static inline function heavily penalizes this approach (6% of stack!):
code stack
macro: 33672 2776
inline: 34512 (+2.5%) 2952 (+6.3%)
after: 33900 (+0.7%) 2736 (-1.4%)
At least this does result in net stack savings, even with the inline
function penalty.
This commit is contained in:
@@ -1416,19 +1416,19 @@ typedef struct lfsr_cat {
|
||||
// sign(size)=0 => single in-RAM buffer
|
||||
// sign(size)=1 => multiple concatenated datas
|
||||
union {
|
||||
lfs_size_t size;
|
||||
uint16_t size;
|
||||
struct {
|
||||
lfs_size_t size;
|
||||
uint16_t size;
|
||||
const uint8_t *buffer;
|
||||
} buf;
|
||||
struct {
|
||||
lfs_size_t size;
|
||||
uint16_t size;
|
||||
const lfsr_data_t *datas;
|
||||
} cat;
|
||||
} u;
|
||||
} lfsr_cat_t;
|
||||
|
||||
#define LFSR_CAT_ISCAT 0x80000000
|
||||
#define LFSR_CAT_ISCAT 0x8000
|
||||
|
||||
#define LFSR_CAT_NULL() \
|
||||
((lfsr_cat_t){ \
|
||||
@@ -1441,10 +1441,14 @@ typedef struct lfsr_cat {
|
||||
.u.buf.buffer=(const void*)(_buffer)})
|
||||
|
||||
#define LFSR_CAT_DATA(_data) \
|
||||
lfsr_cat_fromdatas(_data, 1)
|
||||
((lfsr_cat_t){ \
|
||||
.u.cat.size=LFSR_CAT_ISCAT | 1, \
|
||||
.u.cat.datas=_data})
|
||||
|
||||
#define LFSR_CAT_DATAS(_datas, _count) \
|
||||
lfsr_cat_fromdatas(_datas, _count)
|
||||
((lfsr_cat_t){ \
|
||||
.u.cat.size=LFSR_CAT_ISCAT | (_count), \
|
||||
.u.cat.datas=_datas})
|
||||
|
||||
// cat helpers
|
||||
static inline bool lfsr_cat_isbuf(lfsr_cat_t cat) {
|
||||
@@ -1455,29 +1459,29 @@ static inline bool lfsr_cat_iscat(lfsr_cat_t cat) {
|
||||
return cat.u.size & LFSR_CAT_ISCAT;
|
||||
}
|
||||
|
||||
static inline lfs_size_t lfsr_cat_size(lfsr_cat_t cat) {
|
||||
return cat.u.size & ~LFSR_CAT_ISCAT;
|
||||
}
|
||||
|
||||
static inline lfsr_data_t lfsr_cat_data(lfsr_cat_t cat) {
|
||||
// this only works if cat is a simple buffer
|
||||
LFS_ASSERT(lfsr_cat_isbuf(cat));
|
||||
return LFSR_DATA_BUF(cat.u.buf.buffer, cat.u.buf.size);
|
||||
}
|
||||
|
||||
// some initializers just can't be macros, we at least make these inline
|
||||
// so most of the internal logic is hopefully elided
|
||||
static inline lfsr_cat_t lfsr_cat_fromdatas(
|
||||
const lfsr_data_t *datas, lfs_size_t count) {
|
||||
// find total size, we store this instead of count
|
||||
lfs_size_t size = 0;
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
size += lfsr_data_size(datas[i]);
|
||||
}
|
||||
static inline uint16_t lfsr_cat_count(lfsr_cat_t cat) {
|
||||
LFS_ASSERT(lfsr_cat_iscat(cat));
|
||||
return cat.u.size & ~LFSR_CAT_ISCAT;
|
||||
}
|
||||
|
||||
return (lfsr_cat_t){
|
||||
.u.cat.size=LFSR_CAT_ISCAT | size,
|
||||
.u.cat.datas=datas};
|
||||
static inline lfs_size_t lfsr_cat_size(lfsr_cat_t cat) {
|
||||
// this gets a bit complicated for concatenated data
|
||||
if (lfsr_cat_isbuf(cat)) {
|
||||
return cat.u.buf.size;
|
||||
} else {
|
||||
uint16_t count = lfsr_cat_count(cat);
|
||||
lfs_size_t size = 0;
|
||||
for (uint16_t i = 0; i < count; i++) {
|
||||
size += lfsr_data_size(cat.u.cat.datas[i]);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
// some more cats
|
||||
@@ -1514,14 +1518,16 @@ static inline lfsr_cat_t lfsr_cat_fromlleb128(uint32_t word,
|
||||
// lfsr_data_t
|
||||
typedef struct lfsr_data_name {
|
||||
lfsr_data_t did_data;
|
||||
lfsr_cat_t name_data;
|
||||
lfs_size_t name_size;
|
||||
const uint8_t *name;
|
||||
} lfsr_data_name_t;
|
||||
|
||||
#define LFSR_CAT_NAME(_did, _name, _name_size) \
|
||||
LFSR_CAT_DATAS( \
|
||||
(lfsr_data_t*)(&(lfsr_data_name_t){ \
|
||||
lfsr_cat_data(LFSR_CAT_LEB128(_did)), \
|
||||
LFSR_CAT_BUF(_name, _name_size)}), \
|
||||
((lfsr_data_t*)&(lfsr_data_name_t){ \
|
||||
.did_data=lfsr_cat_data(LFSR_CAT_LEB128(_did)), \
|
||||
.name_size=_name_size, \
|
||||
.name=(const void*)(_name)}), \
|
||||
2)
|
||||
|
||||
// cat <-> bd interactions
|
||||
@@ -1539,20 +1545,16 @@ static int lfsr_bd_progcat(lfs_t *lfs,
|
||||
|
||||
// indirect concatenated data?
|
||||
} else {
|
||||
lfs_size_t size = lfsr_cat_size(cat);
|
||||
const lfsr_data_t *data = cat.u.cat.datas;
|
||||
while (size > 0) {
|
||||
int err = lfsr_bd_progdata(lfs, block, off, *data,
|
||||
uint16_t count = lfsr_cat_count(cat);
|
||||
for (uint16_t i = 0; i < count; i++) {
|
||||
int err = lfsr_bd_progdata(lfs, block, off, cat.u.cat.datas[i],
|
||||
cksum_);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
off += lfsr_data_size(*data);
|
||||
size -= lfsr_data_size(*data);
|
||||
data += 1;
|
||||
off += lfsr_data_size(cat.u.cat.datas[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1560,10 +1562,26 @@ static int lfsr_bd_progcat(lfs_t *lfs,
|
||||
|
||||
// operations on attribute lists
|
||||
|
||||
// needed for lfsr_attr
|
||||
typedef struct lfsr_shrubcommit lfsr_shrubcommit_t;
|
||||
|
||||
typedef struct lfsr_attr {
|
||||
lfsr_tag_t tag;
|
||||
uint16_t size;
|
||||
lfsr_srid_t delta;
|
||||
lfsr_cat_t cat;
|
||||
|
||||
// sign(size)=0 => single in-RAM buffer
|
||||
// sign(size)=1 => multiple concatenated datas
|
||||
// special tags => other types
|
||||
union {
|
||||
const uint8_t *buffer;
|
||||
const lfsr_data_t *datas;
|
||||
|
||||
lfsr_grm_t *grm;
|
||||
const lfsr_mdir_t *mdir;
|
||||
const lfsr_shrubcommit_t *shrubcommit;
|
||||
const lfsr_shrub_t *shrub;
|
||||
} u;
|
||||
} lfsr_attr_t;
|
||||
|
||||
#define LFSR_ATTR(_tag, _delta, _cat) \
|
||||
@@ -1571,10 +1589,19 @@ typedef struct lfsr_attr {
|
||||
|
||||
static inline lfsr_attr_t lfsr_attr(
|
||||
lfsr_tag_t tag, lfsr_srid_t delta, lfsr_cat_t cat) {
|
||||
return (lfsr_attr_t){tag, delta, cat};
|
||||
return (lfsr_attr_t){
|
||||
.tag=tag,
|
||||
.size=cat.u.cat.size,
|
||||
.delta=delta,
|
||||
.u.datas=cat.u.cat.datas};
|
||||
}
|
||||
|
||||
#define LFSR_ATTR_NOOP() LFSR_ATTR(LFSR_TAG_NULL, 0, LFSR_CAT_NULL())
|
||||
#define LFSR_ATTR_NOOP() \
|
||||
((lfsr_attr_t){ \
|
||||
.tag=LFSR_TAG_NULL, \
|
||||
.size=0, \
|
||||
.delta=0, \
|
||||
.u.datas=NULL})
|
||||
|
||||
// create an attribute list
|
||||
#define LFSR_ATTRS(...) \
|
||||
@@ -1586,24 +1613,25 @@ static inline lfsr_attr_t lfsr_attr(
|
||||
// a move of all attrs from an mdir entry
|
||||
#define LFSR_ATTR_MOVE(_tag, _delta, _mdir) \
|
||||
((const lfsr_attr_t){ \
|
||||
_tag, _delta, \
|
||||
.cat.u.buf.buffer=(const void*)(const lfsr_mdir_t*){_mdir}})
|
||||
.tag=_tag, \
|
||||
.delta=_delta, \
|
||||
.u.mdir=_mdir})
|
||||
|
||||
// a grm update, note this is mutable! we may update the grm during
|
||||
// mdir commits
|
||||
#define LFSR_ATTR_GRM(_tag, _delta, _grm) \
|
||||
((const lfsr_attr_t){ \
|
||||
_tag, _delta, \
|
||||
.cat.u.buf.buffer=(const void*)(lfsr_grm_t*){_grm}})
|
||||
.tag=_tag, \
|
||||
.delta=_delta, \
|
||||
.u.grm=_grm})
|
||||
|
||||
// writing to an unrelated trunk in the rbyd
|
||||
typedef struct lfsr_shrubcommit lfsr_shrubcommit_t;
|
||||
|
||||
#define LFSR_ATTR_SHRUBCOMMIT(_tag, _delta, \
|
||||
_shrub, _rid, _attrs, _attr_count) \
|
||||
((const lfsr_attr_t){ \
|
||||
_tag, _delta, \
|
||||
.cat.u.buf.buffer=(const void*)&(const lfsr_shrubcommit_t){ \
|
||||
.tag=_tag, \
|
||||
.delta=_delta, \
|
||||
.u.shrubcommit=&(const lfsr_shrubcommit_t){ \
|
||||
.shrub=_shrub, \
|
||||
.rid=_rid, \
|
||||
.attrs=_attrs, \
|
||||
@@ -1611,8 +1639,9 @@ typedef struct lfsr_shrubcommit lfsr_shrubcommit_t;
|
||||
|
||||
#define LFSR_ATTR_SHRUBTRUNK(_tag, _delta, _shrub) \
|
||||
((const lfsr_attr_t){ \
|
||||
_tag, _delta, \
|
||||
.cat.u.buf.buffer=(const void*)(const lfsr_shrub_t*){_shrub}})
|
||||
.tag=_tag, \
|
||||
.delta=_delta, \
|
||||
.u.shrub=_shrub})
|
||||
|
||||
// some helpers
|
||||
static inline bool lfsr_attr_isnoop(const lfsr_attr_t *attr) {
|
||||
@@ -1623,21 +1652,10 @@ static inline bool lfsr_attr_isinsert(const lfsr_attr_t *attr) {
|
||||
return !lfsr_tag_isgrow(attr->tag) && attr->delta > 0;
|
||||
}
|
||||
|
||||
static inline lfsr_grm_t *lfsr_attr_grm(const lfsr_attr_t *attr) {
|
||||
return (lfsr_grm_t*)attr->cat.u.buf.buffer;
|
||||
}
|
||||
|
||||
static inline lfsr_mdir_t *lfsr_attr_mdir(const lfsr_attr_t *attr) {
|
||||
return (lfsr_mdir_t*)attr->cat.u.buf.buffer;
|
||||
}
|
||||
|
||||
static inline const lfsr_shrubcommit_t *lfsr_attr_shrubcommit(
|
||||
const lfsr_attr_t *attr) {
|
||||
return (const lfsr_shrubcommit_t*)attr->cat.u.buf.buffer;
|
||||
}
|
||||
|
||||
static inline lfsr_shrub_t *lfsr_attr_shrubtrunk(const lfsr_attr_t *attr) {
|
||||
return (lfsr_shrub_t*)attr->cat.u.buf.buffer;
|
||||
static inline lfsr_cat_t lfsr_attr_cat(const lfsr_attr_t *attr) {
|
||||
return (lfsr_cat_t){
|
||||
.u.cat.size=attr->size,
|
||||
.u.cat.datas=attr->u.datas};
|
||||
}
|
||||
|
||||
|
||||
@@ -3583,7 +3601,7 @@ static int lfsr_rbyd_appendattrs(lfs_t *lfs, lfsr_rbyd_t *rbyd,
|
||||
&& (lfs_size_t)(rid + 1) <= (lfs_size_t)end_rid) {
|
||||
int err = lfsr_rbyd_appendattr(lfs, rbyd,
|
||||
rid - lfs_smax32(start_rid, 0),
|
||||
attrs[i].tag, attrs[i].delta, attrs[i].cat);
|
||||
attrs[i].tag, attrs[i].delta, lfsr_attr_cat(&attrs[i]));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
@@ -5975,7 +5993,7 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
} else if (attrs[i].tag == LFSR_TAG_MOVE) {
|
||||
// weighted moves are not supported
|
||||
LFS_ASSERT(attrs[i].delta == 0);
|
||||
const lfsr_mdir_t *mdir__ = lfsr_attr_mdir(&attrs[i]);
|
||||
const lfsr_mdir_t *mdir__ = attrs[i].u.mdir;
|
||||
|
||||
// skip the name tag, this is always replaced by upper layers
|
||||
lfsr_tag_t tag = LFSR_TAG_STRUCT-1;
|
||||
@@ -6097,7 +6115,7 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
} else if (attrs[i].tag == LFSR_TAG_SHRUBALLOC
|
||||
|| attrs[i].tag == LFSR_TAG_SHRUBCOMMIT) {
|
||||
const lfsr_shrubcommit_t *bshrubcommit
|
||||
= lfsr_attr_shrubcommit(&attrs[i]);
|
||||
= attrs[i].u.shrubcommit;
|
||||
|
||||
// SHRUBALLOC is roughly the same as SHRUBCOMMIT but also
|
||||
// resets the shrub, we need to do this here so bshrub root
|
||||
@@ -6123,7 +6141,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_shrub_t *shrub = lfsr_attr_shrubtrunk(&attrs[i]);
|
||||
const lfsr_shrub_t *shrub = attrs[i].u.shrub;
|
||||
|
||||
int err = lfsr_rbyd_appendattr(lfs, &rbyd_,
|
||||
rid - lfs_smax32(start_rid, 0),
|
||||
@@ -6141,7 +6159,9 @@ static int lfsr_mdir_commit__(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
|
||||
int err = lfsr_rbyd_appendattr(lfs, &rbyd_,
|
||||
rid - lfs_smax32(start_rid, 0),
|
||||
attrs[i].tag, attrs[i].delta, attrs[i].cat);
|
||||
attrs[i].tag,
|
||||
attrs[i].delta,
|
||||
lfsr_attr_cat(&attrs[i]));
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
@@ -6597,7 +6617,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_attr_grm(&attrs[i]);
|
||||
lfsr_grm_t *grm = attrs[i].u.grm;
|
||||
lfsr_gdelta_xorgrm(lfs, lfs->grm_d, LFSR_GRM_DSIZE, grm);
|
||||
|
||||
// xor with our current gstate to find our initial gdelta
|
||||
@@ -6880,7 +6900,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
//
|
||||
// gd' = gd xor (grm' xor grm)
|
||||
//
|
||||
lfsr_grm_t *grm = lfsr_attr_grm(&attrs[i]);
|
||||
lfsr_grm_t *grm = attrs[i].u.grm;
|
||||
lfsr_gdelta_xorgrm(lfs, lfs->grm_d, LFSR_GRM_DSIZE, grm);
|
||||
|
||||
// patch our grm
|
||||
@@ -7015,7 +7035,7 @@ static int lfsr_mdir_commit(lfs_t *lfs, lfsr_mdir_t *mdir,
|
||||
for (lfs_size_t i = 0; i < attr_count; i++) {
|
||||
// update any gstate changes
|
||||
if (attrs[i].tag == LFSR_TAG_GRM) {
|
||||
lfs->grm = *lfsr_attr_grm(&attrs[i]);
|
||||
lfs->grm = *attrs[i].u.grm;
|
||||
|
||||
// keep track of the exact encoding on-disk
|
||||
lfsr_cat_fromgrm(&lfs->grm, lfs->grm_g);
|
||||
@@ -10189,7 +10209,7 @@ static int lfsr_bshrub_commit(lfs_t *lfs, lfsr_file_t *file,
|
||||
&& !lfsr_tag_isrm(attrs[i].tag)) {
|
||||
commit_estimate += lfs->attr_estimate;
|
||||
}
|
||||
commit_estimate += lfsr_cat_size(attrs[i].cat);
|
||||
commit_estimate += lfsr_cat_size(lfsr_attr_cat(&attrs[i]));
|
||||
}
|
||||
|
||||
// does our estimate exceed our shrub_size? need to recalculate an
|
||||
@@ -11387,7 +11407,10 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
lfsr_attr_t attrs[2];
|
||||
lfs_size_t attr_count = 0;
|
||||
lfsr_data_t name_data;
|
||||
uint8_t buf[LFSR_BTREE_DSIZE];
|
||||
union {
|
||||
lfsr_data_t data;
|
||||
uint8_t buf[LFSR_BTREE_DSIZE];
|
||||
} data;
|
||||
|
||||
// not created yet? need to convert orphan to normal file
|
||||
if (lfsr_f_isorphan(file->m.flags)) {
|
||||
@@ -11413,9 +11436,10 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
LFSR_CAT_NULL());
|
||||
// small file inlined in mdir?
|
||||
} else if (lfsr_f_isunflush(file->m.flags)) {
|
||||
data.data = LFSR_DATA_BUF(file->buffer, file->buffer_size);
|
||||
attrs[attr_count++] = LFSR_ATTR(
|
||||
LFSR_TAG_SUB | LFSR_TAG_DATA, 0,
|
||||
LFSR_CAT_BUF(file->buffer, file->buffer_size));
|
||||
LFSR_CAT_DATA(&data.data));
|
||||
// bshrub?
|
||||
} else if (lfsr_bshrub_isbshrub(&file->m.mdir, &file->bshrub)) {
|
||||
attrs[attr_count++] = LFSR_ATTR_SHRUBTRUNK(
|
||||
@@ -11425,7 +11449,7 @@ int lfsr_file_sync(lfs_t *lfs, lfsr_file_t *file) {
|
||||
} else if (lfsr_bshrub_isbtree(&file->m.mdir, &file->bshrub)) {
|
||||
attrs[attr_count++] = LFSR_ATTR(
|
||||
LFSR_TAG_SUB | LFSR_TAG_BTREE, 0,
|
||||
lfsr_cat_frombtree(&file->bshrub.u.btree, buf));
|
||||
lfsr_cat_frombtree(&file->bshrub.u.btree, data.buf));
|
||||
} else {
|
||||
LFS_UNREACHABLE();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user