diff --git a/lfs.c b/lfs.c index 6cb9168b..5871d997 100644 --- a/lfs.c +++ b/lfs.c @@ -679,6 +679,9 @@ typedef struct lfsr_data { } u; } lfsr_data_t; +#define LFSR_DATA_NULL \ + ((lfsr_data_t){.u.buf=NULL, .len=0}) + #define LFSR_DATA_BUF(_buf, _len) \ ((lfsr_data_t){.u.buf=_buf, .len=_len}) @@ -1608,119 +1611,184 @@ static lfs_ssize_t lfsr_rbyd_get(lfs_t *lfs, const lfsr_rbyd_t *rbyd, return size_; } -//// TODO should we merge this into lfsr_rbyd_lookup? -//static int lfsr_rbyd_lookupattrs(lfs_t *lfs, -// const lfsr_rbyd_t *rbyd, const struct lfsr_attr *attrs, -// lfsr_tag_t tag, lfs_ssize_t id, -// lfsr_tag_t *tag_, lfs_ssize_t *id_, -// lfsr_data_t *data_, lfs_size_t *weight_) { -// // tag must be non-zero! zero tags may deceptively look like they work but -// // fail when the tree contains a deleted id0 -// LFS_ASSERT(tag != 0); -// -// lfs_ssize_t lower_id = -1; -// lfs_ssize_t upper_id = rbyd->weight; -// lfsr_tag_t upper_tag = -1; -// -// -// // TODO hmm, reverse iteration over a linked-list? this is a bad design -// // first try to find tag in the unwritten attributes -// lfs_ssize_t attr_best_id = 0; -// lfs_tag_t attr_best_tag = 0; -// lfs_ssize_t attr_weight = 0; -// unsigned attr_count = 0; -// for (const struct lfsr_attr *attr = attrs; attr; attr = attr->next) { -// attr_count += 1; -// } -// for (unsigned i = 0; i < count; i++) { -// const struct lfsr_attr *attr = attrs; -// for (unsigned j = 0; j < count-1-i; j++) { -// attr = attr->next; -// } -// -// // TODO can this be simplified a bit? -// if (lfsr_tag_ismk(attr->tag)) { -// if (attr->id < id) { -// id -= 1; -// } -// } else if (attr->tag == LFSR_TAG_RM) { -// if (attr->id < id) { -// id += attr->size; -// } -// } else if (attr->tag == LFSR_TAG_GROW) { -// if (attr->id < id) { -// id -= attr->size; -// } -// } else if (attr->tag == LFSR_TAG_SHRINK) { -// if (attr->id < id) { -// id += attr->size; -// } -// } else if (attr->tag == LFSR_TAG_FROM) { -// // TODO -// LFS_ASSERT(false); -// } else if (lfsr_tag_isrm(attr->tag)) { -// } else { -// if (attr->id == id -// && attr->tag >= tag -// && (attr->id < best_attr_id -// || (attr->id == best_attr_id -// && attr->tag < best_attr_tag))) { -// // TODO track best attr? -// best_attr_id = attr->id; -// best_attr_tag = attr->tag; -// } -// } -// } -// -// lfs_off_t off_; -// lfs_size_t size_; -// int err = lfsr_rbyd_lookup(lfs, rbyd, tag, id, -// tag_, id_, weight_, &off_, &size_); -// if (err) { -// return err; -// } -// -// if (data_) { -// *data_ = LFSR_DATA_DISK(rbyd->block, off_, size_); -// } -// -// return 0; -//} -// -//// TODO do we need this function? -//static lfs_ssize_t lfsr_rbyd_getattrs(lfs_t *lfs, -// const lfsr_rbyd_t *rbyd, const struct lfsr_attr *attrs, -// lfsr_tag_t tag, lfs_ssize_t id, void *buffer, lfs_size_t size) { -// lfsr_tag_t tag_; -// lfs_ssize_t id_; -// lfsr_data_t data_; -// int err = lfsr_rbyd_lookupattrs(lfs, rbyd, attrs, tag, id, -// &tag_, &id_, &data_, NULL); -// if (err) { -// return err; -// } -// -// // lookup finds the next-smallest tag, for get, we need to fail -// // if it's not an exact match -// if (id_ != id || tag_ != tag) { -// return LFS_ERR_NOENT; -// } -// -// // TODO should this be its own lfsr_data_ function? -// lfs_size_t delta = lfs_min(size, lfsr_data_len(data_)); -// if (!lfsr_data_ondisk(data_)) { -// memcpy(buffer, data_.u.buf, delta); -// } else { -// err = lfs_bd_read(lfs, -// &lfs->pcache, &lfs->rcache, delta, -// data_.u.disk.block, data_.u.disk.off, buffer, delta); -// if (err) { -// return err; -// } -// } -// -// return lfsr_data_len(data_); -//} +// TODO should we merge this into lfsr_rbyd_lookup? +static int lfsr_rbyd_pendinglookup(lfs_t *lfs, + const lfsr_rbyd_t *rbyd, const struct lfsr_attr *attrs, + lfsr_tag_t tag, lfs_ssize_t id, + lfsr_tag_t *tag_, lfs_ssize_t *id_, + lfsr_data_t *data_, lfs_size_t *weight_) { + printf("pendinglookup(%x, %d)\n", tag, id); + +again:; + // tag must be non-zero! zero tags may deceptively look like they work but + // fail when the tree contains a deleted id0 + LFS_ASSERT(tag != 0); + + lfsr_tag_t tag__ = 0xffff; + lfsr_data_t data__ = LFSR_DATA_NULL; + lfs_size_t weight = 0; + const struct lfsr_attr *attrs__ = attrs; + + // first search backwards through tags to find the smallest, non-deleted, + // >= id, then search through tags in-order to find the most recent update, + // two passes are required to adjust for weight changes. + // TODO hmm, reverse iteration over a linked-list? this is a bad design + unsigned attr_count = 0; + for (const struct lfsr_attr *attr = attrs; attr; attr = attr->next) { + attr_count += 1; + } + for (unsigned i = 0; i < attr_count; i++) { + const struct lfsr_attr *attr = attrs; + for (unsigned j = 0; j < attr_count-1-i; j++) { + attr = attr->next; + } + + if (attr->tag == LFSR_TAG_GROW) { + printf("g %d %d\n", attr->id, attr->size); + if (id >= attr->id) { + if (id < attr->id + attr->size) { + // TODO + id = attr->id + attr->size-1; + weight = attr->size; + attrs__ = attr->next; + goto grown; + } + id -= attr->size; + } + } else if (attr->tag == LFSR_TAG_SHRINK) { + if (id >= attr->id) { + id += attr->size; + } + } else if (attr->tag == LFSR_TAG_FROM) { + // TODO + LFS_ASSERT(false); + + } + } + + // this id can't exist in our rbyd + if (id >= (lfs_ssize_t)rbyd->weight) { + printf("? %d >= %d\n", id, rbyd->weight); + return LFS_ERR_NOENT; + } + + // if not created in attr list our id must have been created in the rbyd + lfs_off_t off__; + lfs_size_t size__; + int err = lfsr_rbyd_lookup(lfs, rbyd, tag, id, + &tag__, &id, &weight, &off__, &size__); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err != LFS_ERR_NOENT) { + data__ = LFSR_DATA_DISK(rbyd->block, off__, size__); + } + +grown:; + // TODO different way to encode weight? + lfs_ssize_t lower = id-weight+1; + printf("raw %d %d (%d)\n", id, weight, lower); + + // now replay the attr list, keeping track of changes to id, weight, tag + for (const struct lfsr_attr *attr = attrs__; attr; attr = attr->next) { + if (attr->tag == LFSR_TAG_GROW) { + if (lower >= attr->id) { + lower += attr->size; + } + if (id >= attr->id) { + id += attr->size; + } + } else if (attr->tag == LFSR_TAG_SHRINK) { + if (lower >= attr->id) { + lower -= attr->size; + } + if (id >= attr->id) { + id -= attr->size; + } + } else if (attr->tag == LFSR_TAG_FROM) { + // TODO + LFS_ASSERT(false); + + } else if (attr->id == id + && lfsr_tag_key(attr->tag) >= lfsr_tag_key(tag) + && lfsr_tag_key(attr->tag) <= lfsr_tag_key(tag__)) { + tag__ = attr->tag; + data__ = LFSR_DATA_BUF(attr->buffer, attr->size); + } + } + + // TODO if we can't get rid of this we can at least move it into the + // below condition + weight = id-lower+1; + printf("fix %d %d (%d)\n", id, weight, lower); + + // not found? increase id + if (tag__ == 0xffff) { + tag = 0x10; + id = id + 1; + goto again; + } + + // found rm? should continue + if (lfsr_tag_isrm(tag__)) { + tag = tag__ + 0x10; + goto again; + } + + // found + + // TODO how many of these should be conditional? + if (tag_) { + *tag_ = tag__; + } + if (id_) { + *id_ = id; + } + if (data_) { + *data_ = data__; + } + if (weight_) { + *weight_ = weight; + } + + return 0; +} + +// TODO do we need this function? +static lfs_ssize_t lfsr_rbyd_pendingget(lfs_t *lfs, + const lfsr_rbyd_t *rbyd, const struct lfsr_attr *attrs, + lfsr_tag_t tag, lfs_ssize_t id, void *buffer, lfs_size_t size) { + lfsr_tag_t tag_; + lfs_ssize_t id_; + lfsr_data_t data_; + int err = lfsr_rbyd_pendinglookup(lfs, rbyd, attrs, tag, id, + &tag_, &id_, &data_, NULL); + if (err) { + return err; + } + + // lookup finds the next-smallest tag, for get, we need to fail + // if it's not an exact match + if (id_ != id || tag_ != tag) { + return LFS_ERR_NOENT; + } + + // TODO should this be its own lfsr_data_ function? + lfs_size_t delta = lfs_min(size, lfsr_data_len(data_)); + if (!lfsr_data_ondisk(data_)) { + memcpy(buffer, data_.u.buf, delta); + } else { + err = lfs_bd_read(lfs, + &lfs->pcache, &lfs->rcache, delta, + data_.u.disk.block, data_.u.disk.off, buffer, delta); + if (err) { + return err; + } + } + + return lfsr_data_len(data_); +} static lfs_ssize_t lfsr_rbyd_compactedsize(lfs_t *lfs, const lfsr_rbyd_t *rbyd, lfs_ssize_t start, lfs_ssize_t stop) { diff --git a/tests/test_rbyd.toml b/tests/test_rbyd.toml index c513202e..f98bb85e 100644 --- a/tests/test_rbyd.toml +++ b/tests/test_rbyd.toml @@ -11390,3 +11390,833 @@ code = ''' } } ''' + + +## Test unwritten attributes + +[cases.test_rbyd_unwritten_permutations] +defines.N = 'range(1, 7)' +# large progs take too long for now +if = 'PROG_SIZE < 512' +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfsr_rbyd_t init_rbyd = { + .block = 0, + .rev = 1, + .off = 0, + .crc = 0, + .trunk = 0, + .weight = 0, + .erased = true, + }; + lfsr_rbyd_t rbyd; + lfsr_tag_t tag_ = 0; + lfs_ssize_t id_ = -1; + lfsr_data_t data_ = LFSR_DATA_NULL; + lfs_size_t weight_ = 0; + + // test all permutations of a given size + uint16_t perm[N]; + unsigned stack[N]; + for (uint16_t i = 0; i < N; i++) { + perm[i] = i; + stack[i] = 0; + } + + unsigned i = 1; + while (i < N) { + // test each number of written/unwritten tags, this gives us a quick + // way to test several unwritten situations + for (unsigned w = 0; w <= N; w++) { + // print permutation to help debugging + printf("--- permutation: ["); + for (unsigned j = 0; j < N; j++) { + if (j > 0) { + printf(", "); + } + printf("%d", perm[j]); + } + printf("], written: %d/%jd ---\n", w, N); + + // build the attribute lists for the current permutation + struct lfsr_attr attrs[N]; + for (unsigned j = 0; j < N; j++) { + attrs[j] = *LFSR_ATTR( + UATTR(perm[j]+1), -1, "\xaa\xaa\xaa\xaa", 4, + (j+1 < N && j+1 != w) ? &attrs[j+1] : NULL); + } + struct lfsr_attr *written = w > 0 ? &attrs[0] : NULL; + struct lfsr_attr *unwritten = w < N ? &attrs[w] : NULL; + + // create rbyd with written attr + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfsr_rbyd_commit(&lfs, &rbyd, written) => 0; + + lfsr_rbyd_fetch(&lfs, &rbyd, + rbyd.block, cfg->block_size, NULL) => 0; + + // test lookup both written/unwritten + for (unsigned j = 0; j < N; j++) { + lfsr_rbyd_pendinglookup(&lfs, &rbyd, unwritten, + LFSR_TAG_UATTR(j+1), -1, + &tag_, &id_, &data_, &weight_) => 0; + assert(tag_ == LFSR_TAG_UATTR(j+1)); + assert(id_ == -1); + assert(lfsr_data_len(data_) == 4); + assert(weight_ == 0); + } + + // test traverse both written/unwritten + tag_ = 0; + id_ = -1; + for (unsigned j = 0; j < N; j++) { + lfsr_rbyd_pendinglookup(&lfs, &rbyd, unwritten, + lfsr_tag_next(tag_), id_, + &tag_, &id_, &data_, &weight_) => 0; + assert(tag_ == LFSR_TAG_UATTR(j+1)); + assert(id_ == -1); + assert(lfsr_data_len(data_) == 4); + assert(weight_ == 0); + } + lfsr_rbyd_pendinglookup(&lfs, &rbyd, unwritten, + lfsr_tag_next(tag_), id_, + &tag_, &id_, &data_, &weight_) => LFS_ERR_NOENT; + } + + // next permutation using Heap's algorithm + if (stack[i] < i) { + if (i % 2 == 0) { + uint16_t t = perm[0]; + perm[0] = perm[i]; + perm[i] = t; + } else { + uint16_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_unwritten_random] +defines.N = 'range(1, 13)' +defines.ITER = 1000 +# large progs take too long for now +if = 'PROG_SIZE < 512' +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfsr_rbyd_t init_rbyd = { + .block = 0, + .rev = 1, + .off = 0, + .crc = 0, + .trunk = 0, + .weight = 0, + .erased = true, + }; + lfsr_rbyd_t rbyd; + const char *alpha = "abcdefghijklmnopqrstuvwxyz"; + uint8_t buffer[4]; + + // iterate through seeds so we can reproduce easily + for (uint32_t seed = 1; seed < ITER+1; seed++) { + // test each number of written/unwritten tags, this gives us a quick + // way to test several unwritten situations + for (unsigned w = 0; w <= N; w++) { + printf("--- seed: %d, written: %d/%jd ---\n", seed, w, N); + printf("perm: ["); + uint32_t prng = seed; + for (unsigned i = 0; i < N; i++) { + // choose an attr + uint8_t attr = TEST_PRNG(&prng) % N; + // choose append or remove + if (TEST_PRNG(&prng) & 1) { + printf("a0x%02x=%c", attr, alpha[i % 26]); + } else { + printf("r0x%02x", attr); + } + if (i < N-1) { + printf(", "); + } + } + printf("]\n"); + + // set up a simulation to compare against + char *sim = malloc(N); + memset(sim, 0, N); + + // set up rbyd block + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + + // set up our unwritten attr list + struct lfsr_attr *attrs = malloc(N*sizeof(struct lfsr_attr)); + unsigned j = 0; + + prng = seed; + for (unsigned i = 0; i < N; i++) { + // choose an attr + uint8_t attr = TEST_PRNG(&prng) % N; + // choose append or remove + if (TEST_PRNG(&prng) & 1) { + // update our sim + sim[attr] = alpha[i % 26]; + // update our rbyd + if (i < w) { + lfsr_rbyd_commit(&lfs, &rbyd, + LFSR_ATTR(UATTR(attr), -1, &alpha[i % 26], 1, + NULL)) => 0; + // append to our unwritten attrs + } else { + if (j > 0) { + attrs[j-1].next = &attrs[j]; + } + attrs[j] = *LFSR_ATTR( + UATTR(attr), -1, &alpha[i % 26], 1, + NULL); + j += 1; + } + } else { + // update our sim + sim[attr] = '\0'; + // update our rbyd + if (i < w) { + lfsr_rbyd_commit(&lfs, &rbyd, + LFSR_ATTR(RMUATTR(attr), -1, NULL, 0, + NULL)) => 0; + // append to our unwritten attrs + } else { + if (j > 0) { + attrs[j-1].next = &attrs[j]; + } + attrs[j] = *LFSR_ATTR( + RMUATTR(attr), -1, NULL, 0, + NULL); + j += 1; + } + } + } + + // compare rbyd vs simulation + printf("expd: ["); + bool first = true; + for (unsigned attr = 0; attr < N; attr++) { + if (sim[attr]) { + if (!first) { + printf(", "); + } + first = false; + printf("0x%02x=%c", attr, sim[attr]); + } + } + printf("]\n"); + printf("rbyd: ["); + first = true; + for (unsigned attr = 0; attr < N; attr++) { + lfs_ssize_t size = lfsr_rbyd_pendingget(&lfs, &rbyd, attrs, + LFSR_TAG_UATTR(attr), -1, buffer, 4); + if (size >= 0) { + if (!first) { + printf(", "); + } + first = false; + printf("0x%02x=%.*s", attr, size, buffer); + } + } + printf("]\n"); + + for (unsigned attr = 0; attr < N; attr++) { + lfs_ssize_t size = lfsr_rbyd_pendingget(&lfs, &rbyd, attrs, + LFSR_TAG_UATTR(attr), -1, buffer, 4); + if (sim[attr]) { + assert(size == 1); + assert(memcmp(&sim[attr], buffer, 1) == 0); + } else { + assert(size == LFS_ERR_NOENT); + } + } + + // cleanup + free(sim); + free(attrs); + } + } +''' + +[cases.test_rbyd_unwritten_create_permutations] +defines.N = 'range(1, 7)' +# large progs take too long for now +if = 'PROG_SIZE < 512' +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfsr_rbyd_t init_rbyd = { + .block = 0, + .rev = 1, + .off = 0, + .crc = 0, + .trunk = 0, + .weight = 0, + .erased = true, + }; + lfsr_rbyd_t rbyd; + lfsr_tag_t tag_ = 0; + lfs_ssize_t id_ = -1; + lfsr_data_t data_ = LFSR_DATA_NULL; + lfs_size_t weight_ = 0; + 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 + uint16_t perm[N]; + unsigned stack[N]; + for (uint16_t i = 0; i < N; i++) { + perm[i] = i; + stack[i] = 0; + } + + unsigned i = 1; + while (i < N) { + // test each number of written/unwritten tags, this gives us a quick + // way to test several unwritten situations + for (unsigned w = 0; w <= N; w++) { + // print permutation to help debugging + printf("--- permutation: ["); + for (unsigned j = 0; j < N; j++) { + if (j > 0) { + printf(", "); + } + printf("%d", perm[j]); + } + printf("], written: %d/%jd ---\n", w, N); + + // build the attribute lists for the current permutation + struct lfsr_attr attrs[2*N]; + for (unsigned j = 0; j < N; j++) { + // adjust id based on future insertions + uint16_t id = perm[j]; + for (unsigned k = j+1; k < N; k++) { + if (perm[j] > perm[k]) { + id -= 1; + } + } + + attrs[2*j+0] = *LFSR_ATTR( + GROW, id, NULL, 1, + &attrs[2*j+1]); + attrs[2*j+1] = *LFSR_ATTR( + MKREG, id, names[perm[j] % 6], 4, + (j+1 < N && j+1 != w) ? &attrs[2*j+2] : NULL); + } + struct lfsr_attr *written = w > 0 ? &attrs[0] : NULL; + struct lfsr_attr *unwritten = w < N ? &attrs[2*w] : NULL; + + // create rbyd with written attr + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfsr_rbyd_commit(&lfs, &rbyd, written) => 0; + + lfsr_rbyd_fetch(&lfs, &rbyd, + rbyd.block, cfg->block_size, NULL) => 0; + assert(rbyd.weight == w); + + // test lookup both written/unwritten + for (unsigned j = 0; j < N; j++) { + lfsr_rbyd_pendingget(&lfs, &rbyd, unwritten, + LFSR_TAG_MKREG, j, buffer, 4) => 4; + assert(memcmp(buffer, names[j % 6], 4) == 0); + } + + // test traverse both written/unwritten + tag_ = 0; + id_ = -1; + for (unsigned j = 0; j < N; j++) { + lfsr_rbyd_pendinglookup(&lfs, &rbyd, unwritten, + lfsr_tag_next(tag_), id_, + &tag_, &id_, &data_, &weight_) => 0; + assert(tag_ == LFSR_TAG_MKREG); + assert(id_ == j); + assert(lfsr_data_len(data_) == 4); + assert(weight_ == 1); + } + lfsr_rbyd_pendinglookup(&lfs, &rbyd, unwritten, + lfsr_tag_next(tag_), id_, + &tag_, &id_, &data_, NULL) => LFS_ERR_NOENT; + } + + // next permutation using Heap's algorithm + if (stack[i] < i) { + if (i % 2 == 0) { + uint16_t t = perm[0]; + perm[0] = perm[i]; + perm[i] = t; + } else { + uint16_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_unwritten_create_random] +defines.N = 'range(1, 13)' +defines.ITER = 1000 +# large progs take too long for now +if = 'PROG_SIZE < 512' +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfsr_rbyd_t init_rbyd = { + .block = 0, + .rev = 1, + .off = 0, + .crc = 0, + .trunk = 0, + .weight = 0, + .erased = true, + }; + lfsr_rbyd_t rbyd; + const char *alpha = "abcdefghijklmnopqrstuvwxyz"; + uint8_t buffer[4]; + + // iterate through seeds so we can reproduce easily + for (uint32_t seed = 1; seed < ITER+1; seed++) { + // test each number of written/unwritten tags, this gives us a quick + // way to test several unwritten situations + for (unsigned w = 0; w <= N; w++) { + printf("--- seed: %d, written: %d/%jd ---\n", seed, w, N); + printf("perm: ["); + uint32_t prng = seed; + lfs_size_t count = 0; + for (unsigned i = 0; i < N; i++) { + // choose an id + lfs_ssize_t id = TEST_PRNG(&prng) % (count+1); + // choose create or delete + if (id == (lfs_ssize_t)count || (TEST_PRNG(&prng) & 1)) { + printf("c%d=%c", id, alpha[i % 26]); + count += 1; + } else { + printf("d%d", id); + count -= 1; + } + if (i < N-1) { + printf(", "); + } + } + printf("]\n"); + + // set up a simulation to compare against, fun fact this performs + // worst than our actual rbyd block! + char *sim = malloc(N); + memset(sim, 0, N); + + // set up rbyd block + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + + // set up attr list + struct lfsr_attr *attrs = malloc(2*N*sizeof(struct lfsr_attr)); + unsigned j = 0; + + prng = seed; + count = 0; + for (unsigned i = 0; i < N; i++) { + // choose an id + lfs_ssize_t id = TEST_PRNG(&prng) % (count+1); + // choose create or delete + if (id == (lfs_ssize_t)count || (TEST_PRNG(&prng) & 1)) { + // update our sim + memmove(sim+id+1, sim+id, count-id); + sim[id] = alpha[i % 26]; + count += 1; + // update our rbyd + if (i < w) { + lfsr_rbyd_commit(&lfs, &rbyd, + LFSR_ATTR(GROW, id, NULL, 1, + LFSR_ATTR(MKREG, id, &alpha[i % 26], 1, + NULL))) => 0; + } else { + if (j > 0) { + attrs[j-1].next = &attrs[j]; + } + attrs[j] = *LFSR_ATTR( + GROW, id, NULL, 1, + &attrs[j+1]); + attrs[j+1] = *LFSR_ATTR( + MKREG, id, &alpha[i % 26], 1, + NULL); + j += 2; + } + } else { + // update our sim + memmove(sim+id, sim+id+1, count-id-1); + count -= 1; + // update our rbyd + if (i < w) { + lfsr_rbyd_commit(&lfs, &rbyd, + LFSR_ATTR(SHRINK, id, NULL, 1, + NULL)) => 0; + } else { + if (j > 0) { + attrs[j-1].next = &attrs[j]; + } + attrs[j] = *LFSR_ATTR( + SHRINK, id, NULL, 1, + NULL); + j += 1; + } + } + } + + // compare rbyd vs simulation + printf("expd: ["); + for (lfs_ssize_t id = 0; id < (lfs_ssize_t)count; id++) { + printf("%c", sim[id]); + if (id < (lfs_ssize_t)count-1) { + printf(", "); + } + } + printf("]\n"); + printf("rbyd: ["); + for (lfs_ssize_t id = 0; id < (lfs_ssize_t)count; id++) { + lfs_ssize_t size = lfsr_rbyd_pendingget(&lfs, &rbyd, attrs, + LFSR_TAG_MKREG, id, buffer, 4); + if (size >= 0) { + printf("%.*s", size, buffer); + } else { + printf("?"); + } + if (id < (lfs_ssize_t)count-1) { + printf(", "); + } + } + printf("]\n"); + + for (lfs_ssize_t id = 0; id < (lfs_ssize_t)count; id++) { + lfsr_rbyd_pendingget(&lfs, &rbyd, attrs, + LFSR_TAG_MKREG, id, buffer, 4) => 1; + assert(memcmp(&sim[id], buffer, 1) == 0); + } + + // cleanup + free(sim); + free(attrs); + } + } +''' + +[cases.test_rbyd_unwritten_mixed_permutations] +defines.N = 'range(1, 7)' +defines.M = 'range(1, 4)' +# large progs take too long for now +if = 'PROG_SIZE < 512' +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfsr_rbyd_t init_rbyd = { + .block = 0, + .rev = 1, + .off = 0, + .crc = 0, + .trunk = 0, + .weight = 0, + .erased = true, + }; + lfsr_rbyd_t rbyd; + lfsr_tag_t tag_ = 0; + lfs_ssize_t id_ = -1; + lfsr_data_t data_ = LFSR_DATA_NULL; + lfs_size_t weight_ = 0; + 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 + uint16_t perm[N]; + unsigned stack[N]; + for (uint16_t i = 0; i < N; i++) { + perm[i] = i; + stack[i] = 0; + } + + unsigned i = 1; + while (i < N) { + // test each number of written/unwritten tags, this gives us a quick + // way to test several unwritten situations + for (unsigned w = 0; w <= N; w++) { + // print permutation to help debugging + printf("--- permutation: ["); + for (unsigned j = 0; j < N; j++) { + if (j > 0) { + printf(", "); + } + printf("%d", perm[j]); + } + printf("], written: %d/%jd ---\n", w, N); + + // build the attribute lists for the current permutation + struct lfsr_attr attrs[(2+M)*N]; + for (unsigned j = 0; j < N; j++) { + // adjust id based on future insertions + uint16_t id = perm[j]; + for (unsigned k = j+1; k < N; k++) { + if (perm[j] > perm[k]) { + id -= 1; + } + } + + attrs[(2+M)*j+0] = *LFSR_ATTR( + GROW, id, NULL, 1, + &attrs[(2+M)*j+1]); + attrs[(2+M)*j+1] = *LFSR_ATTR( + MKREG, id, names[perm[j] % 6], 4, + &attrs[(2+M)*j+2]); + for (unsigned u = 0; u < M; u++) { + attrs[(2+M)*j+2+u] = *LFSR_ATTR( + UATTR(u+1), id, names[perm[j] % 6], 2, + &attrs[(2+M)*j+2+u+1]); + } + } + if (w > 0) { + attrs[(2+M)*w-1].next = NULL; + } + attrs[(2+M)*N-1].next = NULL; + struct lfsr_attr *written = w > 0 ? &attrs[0] : NULL; + struct lfsr_attr *unwritten = w < N ? &attrs[(2+M)*w] : NULL; + + // create rbyd with written attr + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfsr_rbyd_commit(&lfs, &rbyd, written) => 0; + + lfsr_rbyd_fetch(&lfs, &rbyd, + rbyd.block, cfg->block_size, NULL) => 0; + assert(rbyd.weight == w); + + // test lookup both written/unwritten + for (unsigned j = 0; j < N; j++) { + lfsr_rbyd_pendingget(&lfs, &rbyd, unwritten, + LFSR_TAG_MKREG, j, buffer, 4) => 4; + assert(memcmp(buffer, names[j % 6], 4) == 0); + + for (unsigned u = 0; u < M; u++) { + lfsr_rbyd_pendingget(&lfs, &rbyd, unwritten, + LFSR_TAG_UATTR(u+1), j, buffer, 4) => 2; + assert(memcmp(buffer, names[j % 6], 2) == 0); + } + } + + // test traverse both written/unwritten + tag_ = 0; + id_ = -1; + for (unsigned j = 0; j < N; j++) { + lfsr_rbyd_pendinglookup(&lfs, &rbyd, unwritten, + lfsr_tag_next(tag_), id_, + &tag_, &id_, &data_, &weight_) => 0; + assert(tag_ == LFSR_TAG_MKREG); + assert(id_ == j); + assert(lfsr_data_len(data_) == 4); + assert(weight_ == 1); + + for (unsigned u = 0; u < M; u++) { + lfsr_rbyd_pendinglookup(&lfs, &rbyd, unwritten, + lfsr_tag_next(tag_), id_, + &tag_, &id_, &data_, &weight_) => 0; + assert(tag_ == LFSR_TAG_UATTR(u+1)); + assert(id_ == j); + assert(lfsr_data_len(data_) == 2); + assert(weight_ == 1); + } + } + lfsr_rbyd_pendinglookup(&lfs, &rbyd, unwritten, + lfsr_tag_next(tag_), id_, + &tag_, &id_, &data_, NULL) => LFS_ERR_NOENT; + } + + // next permutation using Heap's algorithm + if (stack[i] < i) { + if (i % 2 == 0) { + uint16_t t = perm[0]; + perm[0] = perm[i]; + perm[i] = t; + } else { + uint16_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_unwritten_sparse_permutations] +defines.N = 'range(1, 7)' +defines.W = 5 +# large progs take too long for now +if = 'PROG_SIZE < 512' +in = 'lfs.c' +code = ''' + lfs_t lfs; + lfs_init(&lfs, cfg) => 0; + + lfsr_rbyd_t init_rbyd = { + .block = 0, + .rev = 1, + .off = 0, + .crc = 0, + .trunk = 0, + .weight = 0, + .erased = true, + }; + lfsr_rbyd_t rbyd; + lfsr_tag_t tag_ = 0; + lfs_ssize_t id_ = -1; + lfsr_data_t data_ = LFSR_DATA_NULL; + lfs_size_t weight_ = 0; + 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 + uint16_t perm[N]; + unsigned stack[N]; + for (uint16_t i = 0; i < N; i++) { + perm[i] = i; + stack[i] = 0; + } + + unsigned i = 1; + while (i < N) { + // test each number of written/unwritten tags, this gives us a quick + // way to test several unwritten situations + for (unsigned w = 0; w <= N; w++) { + // print permutation to help debugging + printf("--- permutation: ["); + for (unsigned j = 0; j < N; j++) { + if (j > 0) { + printf(", "); + } + printf("%d", perm[j]); + } + printf("], written: %d/%jd ---\n", w, N); + + // build the attribute lists for the current permutation + struct lfsr_attr attrs[2*N]; + for (unsigned j = 0; j < N; j++) { + // adjust id based on future insertions + uint16_t id = perm[j]; + for (unsigned k = j+1; k < N; k++) { + if (perm[j] > perm[k]) { + id -= 1; + } + } + + attrs[2*j+0] = *LFSR_ATTR( + GROW, id*W, NULL, W, + &attrs[2*j+1]); + attrs[2*j+1] = *LFSR_ATTR( + MKREG, id*W+W-1, names[perm[j] % 6], 4, + (j+1 < N && j+1 != w) ? &attrs[2*j+2] : NULL); + } + struct lfsr_attr *written = w > 0 ? &attrs[0] : NULL; + struct lfsr_attr *unwritten = w < N ? &attrs[2*w] : NULL; + + // create rbyd with written attr + rbyd = init_rbyd; + lfs_bd_erase(&lfs, rbyd.block) => 0; + lfsr_rbyd_commit(&lfs, &rbyd, written) => 0; + + lfsr_rbyd_fetch(&lfs, &rbyd, + rbyd.block, cfg->block_size, NULL) => 0; + assert(rbyd.weight == w*W); + + // test lookup both written/unwritten + for (unsigned j = 0; j < N; j++) { + lfsr_rbyd_pendingget(&lfs, &rbyd, unwritten, + LFSR_TAG_MKREG, j*W+W-1, buffer, 4) => 4; + assert(memcmp(buffer, names[j % 6], 4) == 0); + } + + // test traverse both written/unwritten + tag_ = 0; + id_ = -1; + for (unsigned j = 0; j < N; j++) { + lfsr_rbyd_pendinglookup(&lfs, &rbyd, unwritten, + lfsr_tag_next(tag_), id_, + &tag_, &id_, &data_, &weight_) => 0; + assert(tag_ == LFSR_TAG_MKREG); + assert(id_ == j*W+W-1); + assert(lfsr_data_len(data_) == 4); + assert(weight_ == W); + } + lfsr_rbyd_pendinglookup(&lfs, &rbyd, unwritten, + lfsr_tag_next(tag_), id_, + &tag_, &id_, &data_, NULL) => LFS_ERR_NOENT; + } + + // next permutation using Heap's algorithm + if (stack[i] < i) { + if (i % 2 == 0) { + uint16_t t = perm[0]; + perm[0] = perm[i]; + perm[i] = t; + } else { + uint16_t t = perm[stack[i]]; + perm[stack[i]] = perm[i]; + perm[i] = t; + } + stack[i] += 1; + i = 1; + } else { + stack[i] = 0; + i += 1; + } + } +'''