From 2979e6273a10a9f219927d90a5e9646ff7558665 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 29 Dec 2022 00:39:41 -0600 Subject: [PATCH] Added weight-adjusting insert operations, though it is currently very hacky --- lfs.c | 266 +++++++++++++++----- tests/test_rbyd.toml | 576 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 782 insertions(+), 60 deletions(-) diff --git a/lfs.c b/lfs.c index 638f72c7..48bf274b 100644 --- a/lfs.c +++ b/lfs.c @@ -412,24 +412,26 @@ typedef uint32_t lfs_rtag_t; typedef int32_t lfs_srtag_t; enum lfs_rtag_type1 { - LFS_TYPE1_CREATE = 0x40, - LFS_TYPE1_DELETE = 0x48, - LFS_TYPE1_STRUCT = 0x50, - LFS_TYPE1_UATTR = 0x60, + LFS_TYPE1_CREATE = 0x0040, + LFS_TYPE1_CREATEREG = 0x00c0, + LFS_TYPE1_CREATEDIR = 0x0140, + LFS_TYPE1_DELETE = 0x0048, + LFS_TYPE1_STRUCT = 0x0050, + LFS_TYPE1_UATTR = 0x0060, - LFS_TYPE1_TAIL = 0x08, - LFS_TYPE1_GSTATE = 0x10, + LFS_TYPE1_TAIL = 0x0008, + LFS_TYPE1_GSTATE = 0x0010, - LFS_TYPE1_CRC0 = 0x02, - LFS_TYPE1_CRC1 = 0x03, - LFS_TYPE1_FCRC = 0x0a, + LFS_TYPE1_CRC = 0x0002, + LFS_TYPE1_FCRC = 0x000a, - LFS_TYPE1_RM = 0x01, + LFS_TYPE1_RM = 0x0001, + LFS_TYPE1_ALT = 0x0004, +}; - LFS_TYPE1_ALTBLT = 0x04, - LFS_TYPE1_ALTRLT = 0x05, - LFS_TYPE1_ALTBGT = 0x06, - LFS_TYPE1_ALTRGT = 0x07, +enum lfs_rtag_pat { + LFS_PAT_GET = 0x0000, + LFS_PAT_FIND = 0x0001, }; #define LFS_ALT_B false @@ -452,8 +454,14 @@ enum lfs_rtag_type1 { #define LFS_MKRRMTAG(type1, type2, id) \ LFS_MKRRMTAG_(LFS_TYPE1_##type1, type2, id) +#define LFS_MKRPATTAG_(pat, type1, type2, id) \ + LFS_MKRTAG_((pat) | (type1), type2, id) + +#define LFS_MKRPATTAG(pat, type1, type2, id) \ + LFS_MKRPATTAG_(LFS_PAT_##pat, type1, type2, id) + #define LFS_MKRALT_(color, dir, weight) \ - (LFS_TYPE1_ALTBLT \ + (LFS_TYPE1_ALT \ | ((0x1 & (lfs_rtag_t)(color)) << 0) \ | ((0x1 & (lfs_rtag_t)(dir)) << 1) \ | ((0xfffffff & (lfs_rtag_t)(weight)) << 3)) @@ -466,6 +474,14 @@ static inline bool lfs_rtag_isvalid(lfs_rtag_t tag) { return !(tag & 0x80000000); } +static inline lfs_rtag_t lfs_rtag_valid(lfs_rtag_t tag) { + return tag & ~0x80000000; +} + +static inline lfs_rtag_t lfs_rtag_invalid(lfs_rtag_t tag) { + return tag | 0x80000000; +} + static inline bool lfs_rtag_isalt(lfs_rtag_t tag) { return tag & 0x4; } @@ -498,10 +514,22 @@ static inline lfs_rtag_t lfs_rtag_setid(lfs_rtag_t tag, uint16_t id) { return (tag & 0x7fff) | ((lfs_rtag_t)id << 15); } +static inline lfs_rtag_t lfs_rtag_incid(lfs_rtag_t tag) { + return tag + (1 << 15); +} + +static inline lfs_rtag_t lfs_rtag_decid(lfs_rtag_t tag) { + return tag - (1 << 15); +} + static inline lfs_rtag_t lfs_rtag_inc(lfs_rtag_t tag) { return tag + 0x8; } +static inline uint8_t lfs_rtag_pat(lfs_rtag_t tag) { + return tag & 0x7; +} + // alt operations static inline bool lfs_rtag_isblack(lfs_rtag_t tag) { return !(tag & 0x1); @@ -545,7 +573,7 @@ static inline lfs_srtag_t lfs_rtag_weight_lt(lfs_rtag_t tag, uint16_t count) { } static inline lfs_srtag_t lfs_rtag_weight_gt(lfs_rtag_t tag, uint16_t count) { - return (((lfs_srtag_t)count+1) << 12)-1 - lfs_rtag_weight(tag); + return (((lfs_srtag_t)count) << 12)-1 - lfs_rtag_weight(tag); } static inline bool lfs_rtag_follow(lfs_rtag_t alt, @@ -606,40 +634,55 @@ struct lfs_rattr { }; #define LFS_MKRATTR_(...) \ - (&(struct lfs_rattr){__VA_ARGS__}) + (&(const struct lfs_rattr){__VA_ARGS__}) #define LFS_MKRATTR(type1, type2, id, buffer, size, next) \ - (&(struct lfs_rattr){LFS_MKRTAG(type1, type2, id), buffer, size, next}) + (&(const struct lfs_rattr){ \ + LFS_MKRTAG(type1, type2, id), \ + buffer, size, next}) #define LFS_MKRRMATTR(type1, type2, id, next) \ - (&(struct lfs_rattr){LFS_MKRRMTAG(type1, type2, id), NULL, 0, next}) + (&(const struct lfs_rattr){ \ + LFS_MKRRMTAG(type1, type2, id), \ + NULL, 0, next}) // operations on pattern lists -enum lfs_fetchpattern_type { - LFS_FETCHPATTERN_NAME = 0, - LFS_FETCHPATTERN_PAIR = 1, -}; - -struct lfs_fetchpattern { +struct lfs_rpat { lfs_rtag_t tag; - const void *buffer; - lfs_size_t size; - struct lfs_fetchpattern *next; + union { + struct { + void *buffer; + lfs_size_t size; + } get; + struct { + const void *buffer; + lfs_size_t size; + } find; + } u; + struct lfs_rpat *next; }; -#define LFS_MKRPATTERN_(...) \ - (&(struct lfs_fetchpattern){__VA_ARGS__}) +#define LFS_MKRGETPAT_(tag, buffer, size, next) \ + (&(struct lfs_rpat){ \ + .tag = LFS_PAT_GET | (tag), \ + .u.get.buffer = buffer, \ + .u.get.size = size, \ + .next = next}) -#define LFS_MKRPATTERN(type, type1, type2, buffer, size, next) \ - (&(struct lfs_fetchpattern){ \ - LFS_MKRTAG(LFS_FETCHPATTERN_##type | type1, type2, 0), \ - buffer, size, next}) +#define LFS_MKRGETPAT(type1, type2, buffer, size, next) \ + LFS_MKRGET_(LFS_MKRTAG(type1, type2, 0), buffer, size, next) + +#define LFS_MKRFINDPAT_(tag, buffer, size, next) \ + (&(struct lfs_rpat){ \ + .tag = LFS_PAT_FIND | (tag), \ + .u.find.buffer = buffer, \ + .u.find.size = size, \ + .next = next}) + +#define LFS_MKRFINDPAT(type1, type2, buffer, size, next) \ + LFS_MKRFIND_(LFS_MKRTAG(type1, type2, 0), buffer, size, next) -static inline uint8_t lfs_fetchpattern_type( - const struct lfs_fetchpattern *pattern) { - return pattern->tag & 0x1; -} // operations on global state static inline void lfs_gstate_xor(lfs_gstate_t *a, const lfs_gstate_t *b) { @@ -989,9 +1032,14 @@ static lfs_ssize_t lfs_rbyd_readtag(lfs_t *lfs, static int lfs_rbyd_fetch(lfs_t *lfs, lfs_rbyd_t *rbyd, lfs_block_t block, - struct lfs_fetchpattern *patterns) { - // TODO rm this - (void)patterns; + struct lfs_rpat *patterns) { + // TODO this + // first mark patterns as invalid until we find matches + for (struct lfs_rpat *pattern = patterns; + pattern; + pattern = pattern->next) { + pattern->tag = lfs_rtag_invalid(pattern->tag); + } // read the revision count and get the crc started uint32_t rev; @@ -1007,6 +1055,7 @@ static int lfs_rbyd_fetch(lfs_t *lfs, lfs_off_t off = sizeof(uint32_t); lfs_off_t trunk = 0; bool wastrunk = false; + uint16_t count = 0; rbyd->block = block; rbyd->rev = lfs_fromle32_(&rev); @@ -1057,7 +1106,7 @@ static int lfs_rbyd_fetch(lfs_t *lfs, } // not an end-of-commit crc - if ((lfs_rtag_type1(tag) & ~0x1) != LFS_TYPE1_CRC0) { + if ((lfs_rtag_type1(tag) & ~0x1) != LFS_TYPE1_CRC) { // fcrc is only valid if the last tag was a crc hasfcrc = false; @@ -1072,8 +1121,11 @@ static int lfs_rbyd_fetch(lfs_t *lfs, return err; } - // found an fcrc? - if (lfs_rtag_type1(tag) == LFS_TYPE1_FCRC) { + // found a create? increase count of ids + if (lfs_rtag_type1(tag) == LFS_TYPE1_CREATE) { + count += 1; + // found an fcrc? save for later + } else if (lfs_rtag_type1(tag) == LFS_TYPE1_FCRC) { err = lfs_bd_read(lfs, NULL, &lfs->rcache, lfs->cfg->block_size, block, off, &fcrc, @@ -1121,6 +1173,7 @@ static int lfs_rbyd_fetch(lfs_t *lfs, rbyd->trunk = trunk; rbyd->off = off; rbyd->crc = crc; + rbyd->count = count; } off += size; @@ -1163,8 +1216,8 @@ tryagain:; } // weights for pruning - lfs_srtag_t lt = lfs_rtag_weight_lt(tag, rbyd->count); - lfs_srtag_t gt = lfs_rtag_weight_gt(tag, rbyd->count); + lfs_srtag_t lt = lfs_rtag_weight_lt(tag, rbyd->count+1); + lfs_srtag_t gt = lfs_rtag_weight_gt(tag, rbyd->count+1); printf("lt, gt = (%x, %x)\n", lt, gt); // descend down tree @@ -1417,9 +1470,9 @@ static int lfs_rbyd_commit(lfs_t *lfs, lfs_rbyd_t *rbyd, // setup commit state const lfs_block_t block = rbyd->block; lfs_off_t trunk = rbyd->trunk; - uint16_t count = rbyd->count; lfs_off_t off = rbyd->off; uint32_t crc = rbyd->crc; + uint16_t count = rbyd->count; bool erased = false; // mark as unerased in case we fail @@ -1442,6 +1495,8 @@ static int lfs_rbyd_commit(lfs_t *lfs, lfs_rbyd_t *rbyd, // append each tag to the tree for (const struct lfs_rattr *attr = attrs; attr; attr = attr->next) { printf("append()\n"); + LFS_ASSERT(lfs_rtag_id(attr->tag) <= count+1); + // assume we'll update our trunk lfs_off_t branch = trunk; trunk = off; @@ -1452,8 +1507,25 @@ static int lfs_rbyd_commit(lfs_t *lfs, lfs_rbyd_t *rbyd, } // weights for pruning - lfs_srtag_t lt = lfs_rtag_weight_lt(attr->tag, count); - lfs_srtag_t gt = lfs_rtag_weight_gt(attr->tag, count); + lfs_srtag_t lt; + lfs_srtag_t gt; + if (lfs_rtag_type1(attr->tag) == LFS_TYPE1_CREATE) { + // inserting a new id? align down + // TODO special function for this? +// if (lfs_rtag_id(attr->tag) == count+1) { +// lt = (count+1) << 12; +// gt = 0; +// } else { +// lt = lfs_rtag_weight_lt(attr->tag & ~0x7fff, count+1); +// gt = lfs_rtag_weight_gt(attr->tag & ~0x7fff, count+1); +// } + lt = lfs_rtag_weight_lt(attr->tag & ~0x7fff, count+1); + gt = lfs_rtag_weight_gt(attr->tag & ~0x7fff, count+1) + 1; + } else { + lt = lfs_rtag_weight_lt(attr->tag, count+1); + gt = lfs_rtag_weight_gt(attr->tag, count+1); + } + printf("lt, gt = (%x, %x)\n", lt, gt); // queue of pending alts we can emulate rotations with @@ -1533,7 +1605,8 @@ static int lfs_rbyd_commit(lfs_t *lfs, lfs_rbyd_t *rbyd, // should've taken red alt? needs a flip if (lt < 0 || gt < 0) { LFS_ASSERT(lfs_rtag_isblack(alt)); - LFS_ASSERT(p_alts[0] && lfs_rtag_isred(p_alts[0])); + LFS_ASSERT(p_alts[0]); + LFS_ASSERT(lfs_rtag_isred(p_alts[0])); printf("rflip %s (%x,%x)\n", lfs_rtag_isparallel(alt, p_alts[0]) ? "parallel" : "perpendicular", @@ -1588,18 +1661,83 @@ static int lfs_rbyd_commit(lfs_t *lfs, lfs_rbyd_t *rbyd, // update the tag id // TODO should this be a function? maybe see what create/delete need // TODO alternatively can we do this a bit more directly here +// lfs_rtag_t tag_ = lfs_rtag_setid(alt, +// lfs_min(lfs_rtag_id(attr->tag), count)); lfs_rtag_t tag_ = lfs_rtag_setid(alt, lfs_rtag_id(attr->tag)); + // TODO I don't really know why this works + if (lfs_rtag_type1(attr->tag) == LFS_TYPE1_CREATE && gt == 0) { + tag_ = lfs_rtag_decid(tag_); + } + //gt == 0 ? lfs_rtag_id(attr->tag)-1 : lfs_rtag_id(attr->tag)); + printf("found %x (%d, %d)\n", tag_, lt >> 12, gt >> 12); // split leaf? - if (tag_ != attr->tag) { - // bias the weights so that lookups always find the - // next biggest tag - if (lfs_rtag_weight(tag_) < lfs_rtag_weight(attr->tag)) { - alt = LFS_MKRALT(B, LT, lt+1 - + lfs_rtag_weight(tag_) - - lfs_rtag_weight(attr->tag)); + // TODO we might be able to rearrange this a bit better + if (lfs_rtag_type1(attr->tag) == LFS_TYPE1_CREATE + || tag_ != attr->tag) { + // inserting a new id? + if (lfs_rtag_type1(attr->tag) == LFS_TYPE1_CREATE) { + // bias the weights so that lookups always find the + // next biggest tag + if (lfs_rtag_weight(tag_) + < lfs_rtag_weight(attr->tag & ~0x7fff)) { + printf("lt insert\n"); + // + // lt gt + // .----'---. .-'. + // alt + // .--'--. + // <---+------+---|--+--|---+------+---> + // a old new d e + // + alt = LFS_MKRALT(B, LT, lt + - (lfs_rtag_weight(attr->tag & ~0x7fff)-1 + - lfs_rtag_weight(tag_)) + + 1); + } else { + printf("gt insert\n"); + printf("hmmm? %08x %08x\n", lfs_rtag_weight(tag_), lfs_rtag_weight(attr->tag & ~0x7fff)); + // + // lt gt + // .-----'--. .-'. + // alt + // .--'--. + // <---+------+---|--+--|---+------+---> + // a b new old e + // + alt = LFS_MKRALT(B, GT, + // TODO hm, can this be done differently? + lfs_rtag_weight(lfs_rtag_incid(tag_)) + - lfs_rtag_weight(attr->tag)); + } } else { - alt = LFS_MKRALT(B, GT, gt); + // bias the weights so that lookups always find the + // next biggest tag + if (lfs_rtag_weight(tag_) + < lfs_rtag_weight(attr->tag)) { + // + // lt gt + // .-----'-----. .--'--. + // alt + // .--'--. + // <---+------+------+------+------+---> + // a old new d e + // + alt = LFS_MKRALT(B, LT, lt + + 1 + - (lfs_rtag_weight(attr->tag) + - lfs_rtag_weight(tag_))); + } else { + // + // lt gt + // .-----'-----. .--'--. + // alt + // .--'--. + // <---+------+------+------+------+---> + // a b new old e + // + alt = LFS_MKRALT(B, GT, gt); + } } lfs_ssize_t delta = lfs_rbyd_p_push(lfs, @@ -1651,6 +1789,15 @@ static int lfs_rbyd_commit(lfs_t *lfs, lfs_rbyd_t *rbyd, } off += attr->size; + // if we're inserting, increase the id count, indirectly shifting + // all ids >= over one + // + // note we do this here since it is possible to insert into an + // empty tree + if (lfs_rtag_type1(attr->tag) == LFS_TYPE1_CREATE) { + count += 1; + } + continue; } @@ -1726,7 +1873,7 @@ static int lfs_rbyd_commit(lfs_t *lfs, lfs_rbyd_t *rbyd, // get around this catch-22 we just always write a fully-expanded leb128 // encoding uint8_t buffer[1+4+4]; - buffer[0] = (LFS_MKRTAG(CRC0, 0, 0) << 1) | (lfs_popc(crc) & 1); + buffer[0] = (LFS_MKRTAG(CRC, 0, 0) << 1) | (lfs_popc(crc) & 1); lfs_off_t padding = aligned - (off + 1+4); buffer[1] = 0x80 | (0x7f & (padding >> 0)); @@ -1778,6 +1925,7 @@ static int lfs_rbyd_commit(lfs_t *lfs, lfs_rbyd_t *rbyd, rbyd->trunk = trunk; rbyd->off = aligned; rbyd->crc = crc; + rbyd->count = count; rbyd->erased = erased; return 0; diff --git a/tests/test_rbyd.toml b/tests/test_rbyd.toml index ae24eb9b..c1d1e317 100644 --- a/tests/test_rbyd.toml +++ b/tests/test_rbyd.toml @@ -87,6 +87,8 @@ code = ''' # [cases.test_rbyd_fetchmatch] # [cases.test_rbyd_multi_fetchmatch] +# TODO we really need to test dense keys... + [cases.test_rbyd_lookup] in = 'lfs.c' code = ''' @@ -1737,6 +1739,7 @@ code = ''' // check that we can still lookup all the tags prng = 42; + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; for (lfs_size_t i = 0; i < count; i++) { uint8_t x = (ORDER == 0) ? (uint8_t)i @@ -1749,7 +1752,7 @@ code = ''' ''' -### removal testing ### +### Removal testing ### [cases.test_rbyd_remove] in = 'lfs.c' @@ -2146,3 +2149,574 @@ code = ''' } } ''' + +### Insertion testing ### + +[cases.test_rbyd_insert] +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfs_rbyd_t init_rbyd = { + .block = 0, + .trunk = 0, + .off = 0, + .rev = 1, + .crc = 0, + .count = 0, + .erased = true, + }; + lfs_rbyd_t rbyd; + uint8_t buffer[4]; + + // try to insert one id + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; + + assert(rbyd.count == 1); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 1); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + + // try to insert two ids + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, NULL))) => 0; + + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + + // try to insert two in the other direction + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xbb\xbb\xbb\xbb", 4, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, NULL))) => 0; + + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + + // insert a third to the right + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, + LFS_MKRATTR(CREATEREG, 0, 3, "\xcc\xcc\xcc\xcc", 4, NULL)))) => 0; + + assert(rbyd.count == 3); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 3); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); + + // insert a third to the left + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xbb\xbb\xbb\xbb", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xcc\xcc\xcc\xcc", 4, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, NULL)))) => 0; + + assert(rbyd.count == 3); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 3); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); + + // insert a third in the middle + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xcc\xcc\xcc\xcc", 4, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, NULL)))) => 0; + + assert(rbyd.count == 3); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 3); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); +''' + +[cases.test_rbyd_multi_insert] +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfs_rbyd_t init_rbyd = { + .block = 0, + .trunk = 0, + .off = 0, + .rev = 1, + .crc = 0, + .count = 0, + .erased = true, + }; + lfs_rbyd_t rbyd; + uint8_t buffer[4]; + + // try to insert one id + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; + + assert(rbyd.count == 1); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 1); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) + => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + + // try to insert two ids + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; + + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + + // try to insert two in the other direction + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; + + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 2); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + + // insert a third to the right + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 3, "\xcc\xcc\xcc\xcc", 4, NULL)) => 0; + + assert(rbyd.count == 3); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 3); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); + + // insert a third to the left + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 2, "\xcc\xcc\xcc\xcc", 4, NULL)) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; + + assert(rbyd.count == 3); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 3); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); + + // insert a third in the middle + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 1, "\xaa\xaa\xaa\xaa", 4, NULL)) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 2, "\xcc\xcc\xcc\xcc", 4, NULL)) => 0; + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, 2, "\xbb\xbb\xbb\xbb", 4, NULL)) => 0; + + assert(rbyd.count == 3); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == 3); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 1), buffer, 4) => 4; + assert(memcmp(buffer, "\xaa\xaa\xaa\xaa", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 2), buffer, 4) => 4; + assert(memcmp(buffer, "\xbb\xbb\xbb\xbb", 4) == 0); + lfs_rbyd_get(&lfs, &rbyd, LFS_MKRTAG(CREATEREG, 0, 3), buffer, 4) => 4; + assert(memcmp(buffer, "\xcc\xcc\xcc\xcc", 4) == 0); +''' + +[cases.test_rbyd_insert_permutations] +defines.N = 'range(1, 8)' +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfs_rbyd_t init_rbyd = { + .block = 0, + .trunk = 0, + .off = 0, + .rev = 1, + .crc = 0, + .count = 0, + .erased = true, + }; + lfs_rbyd_t rbyd; + const uint8_t names[6][4] = { + "\xaa\xaa\xaa\xaa", + "\xbb\xbb\xbb\xbb", + "\xcc\xcc\xcc\xcc", + "\xdd\xdd\xdd\xdd", + "\xee\xee\xee\xee", + "\xff\xff\xff\xff", + }; + uint8_t buffer[4]; + + // test all permutations of a given size + uint8_t perm[N]; + uint8_t stack[N]; + for (uint8_t i = 0; i < N; i++) { + perm[i] = i; + stack[i] = 0; + } + + uint8_t i = 1; + while (i < N) { + // print permutation to help debugging + printf("--- permutation: ["); + for (int j = 0; j < N; j++) { + if (j > 0) { + printf(", "); + } + printf("%d", perm[j]+1); + } + printf("] ---\n"); + + // build the attribute list for the current permutation + struct lfs_rattr attrs[N]; + for (int j = 0; j < N; j++) { + // adjust id based on future insertions + uint16_t id = perm[j]; + for (int k = j+1; k < N; k++) { + if (perm[j] > perm[k]) { + id -= 1; + } + } + + attrs[j] = *LFS_MKRATTR( + CREATEREG, 0, id+1, + names[perm[j] % 6], 4, + (j+1 < N) ? &attrs[j+1] : NULL); + } + + // test the given permutation + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfs_rbyd_commit(&lfs, &rbyd, attrs) => 0; + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == N); + for (int j = 0; j < N; j++) { + lfs_rbyd_get(&lfs, &rbyd, + LFS_MKRTAG(CREATEREG, 0, j+1), buffer, 4) => 4; + assert(memcmp(buffer, names[j % 6], 4) == 0); + } + + // next permutation using Heap's algorithm + if (stack[i] < i) { + if (i % 2 == 0) { + uint8_t t = perm[0]; + perm[0] = perm[i]; + perm[i] = t; + } else { + uint8_t t = perm[stack[i]]; + perm[stack[i]] = perm[i]; + perm[i] = t; + } + stack[i] += 1; + i = 1; + } else { + stack[i] = 0; + i += 1; + } + } +''' + +[cases.test_rbyd_multi_insert_permutations] +defines.N = 'range(1, 8)' +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfs_rbyd_t init_rbyd = { + .block = 0, + .trunk = 0, + .off = 0, + .rev = 1, + .crc = 0, + .count = 0, + .erased = true, + }; + lfs_rbyd_t rbyd; + const uint8_t names[6][4] = { + "\xaa\xaa\xaa\xaa", + "\xbb\xbb\xbb\xbb", + "\xcc\xcc\xcc\xcc", + "\xdd\xdd\xdd\xdd", + "\xee\xee\xee\xee", + "\xff\xff\xff\xff", + }; + uint8_t buffer[4]; + + // test all permutations of a given size + uint8_t perm[N]; + uint8_t stack[N]; + for (uint8_t i = 0; i < N; i++) { + perm[i] = i; + stack[i] = 0; + } + + uint8_t i = 1; + while (i < N) { + // print permutation to help debugging + printf("--- permutation: ["); + for (int j = 0; j < N; j++) { + if (j > 0) { + printf(", "); + } + printf("%d", perm[j]+1); + } + printf("] ---\n"); + + // test the given permutation with multiple commits + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + + for (int j = 0; j < N; j++) { + // adjust id based on future insertions + uint16_t id = perm[j]; + for (int k = j+1; k < N; k++) { + if (perm[j] > perm[k]) { + id -= 1; + } + } + + lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, id+1, names[perm[j] % 6], 4, + NULL)) => 0; + } + + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + assert(rbyd.count == N); + for (int j = 0; j < N; j++) { + lfs_rbyd_get(&lfs, &rbyd, + LFS_MKRTAG(CREATEREG, 0, j+1), buffer, 4) => 4; + assert(memcmp(buffer, names[j % 6], 4) == 0); + } + + // next permutation using Heap's algorithm + if (stack[i] < i) { + if (i % 2 == 0) { + uint8_t t = perm[0]; + perm[0] = perm[i]; + perm[i] = t; + } else { + uint8_t t = perm[stack[i]]; + perm[stack[i]] = perm[i]; + perm[i] = t; + } + stack[i] += 1; + i = 1; + } else { + stack[i] = 0; + i += 1; + } + } +''' + +[cases.test_rbyd_insert_large] +in = 'lfs.c' +# ORDER: +# 0 = in-order +# 1 = reverse-order +# 2 = random-order +defines.ORDER = [0, 1, 2] +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfs_rbyd_t init_rbyd = { + .block = 0, + .trunk = 0, + .off = 0, + .rev = 1, + .crc = 0, + .count = 0, + .erased = true, + }; + lfs_rbyd_t rbyd; + const uint8_t names[6][4] = { + "\xaa\xaa\xaa\xaa", + "\xbb\xbb\xbb\xbb", + "\xcc\xcc\xcc\xcc", + "\xdd\xdd\xdd\xdd", + "\xee\xee\xee\xee", + "\xff\xff\xff\xff", + }; + + // create the rbyd tree + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + + // keep inserting tags until we run out of space + // + // note, the ids we create this way are both sparse and sometimes + // repeated, so we need to mod our current rbyd size to avoid invalid + // insertions + // + uint32_t prng = 42; + for (lfs_size_t i = 0;; i++) { + uint16_t x + = (ORDER == 0) ? (uint16_t)i + : (ORDER == 1) ? (uint16_t)(((lfs_size_t)-1) - i) + : (uint16_t)TEST_PRNG(&prng); + x = x % (rbyd.count+1); + + int err = lfs_rbyd_commit(&lfs, &rbyd, + LFS_MKRATTR(CREATEREG, 0, x+1, names[x % 6], 4, NULL)); + // if we can't fit an fcrc, erased is set to false, but if we can, + // lfs_rbyd_commit may error later with LFS_ERR_RANGE + if (!rbyd.erased || err == LFS_ERR_RANGE) { + break; + } + assert(err == 0); + } + + // check that we can at least lookup all the tags + lfs_off_t off; + lfs_size_t size; + lfs_rbyd_fetch(&lfs, &rbyd, rbyd.block, NULL) => 0; + for (uint16_t x = 0; x < rbyd.count; x++) { + lfs_rbyd_lookup(&lfs, &rbyd, + LFS_MKRTAG(CREATEREG, 0, x+1), &off, &size) + => LFS_MKRTAG(CREATEREG, 0, x+1); + } +'''