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:
Christopher Haster
2024-05-07 16:27:55 -05:00
parent e4069ee4fc
commit 2ee6750f2e
+98 -74
View File
@@ -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();
}