Removed recursion from lfs_dir_traverse

lfs_dir_traverse is a bit unpleasant in that it is inherently a
recursive function, but without a strict bound of 4 calls (commit -> filter ->
move -> filter), and efforts to unroll the recursion comes at a
signification code cost.

It turns out the best solution I've found so far is to simple create an
explicit stack with an explicit bound of 4 calls (or more accurately,
3 pushed frames).

---

This actually highlights one of the bigger flaws in littlefs right now,
which is that this function, lfs_dir_traverse, takes O(n^2) disk reads
to traverse.

Note that LFS_FROM_MOVE can only occur once per commit, which is why
this code is O(n^2) and not O(n^4).
This commit is contained in:
Christopher Haster
2022-02-27 10:51:49 -06:00
parent fedf646c79
commit 8109f28266

176
lfs.c
View File

@@ -737,6 +737,7 @@ static int lfs_dir_traverse_filter(void *p,
(LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == ( (LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == (
LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) |
(LFS_MKTAG(0, 0x3ff, 0) & *filtertag))) { (LFS_MKTAG(0, 0x3ff, 0) & *filtertag))) {
*filtertag = LFS_MKTAG(LFS_FROM_NOOP, 0, 0);
return true; return true;
} }
@@ -751,17 +752,54 @@ static int lfs_dir_traverse_filter(void *p,
#endif #endif
#ifndef LFS_READONLY #ifndef LFS_READONLY
// maximum recursive depth of lfs_dir_traverse, the deepest call:
//
// traverse with commit
// '-> traverse with filter
// '-> traverse with move
// ' traverse with filter
//
#define LFS_DIR_TRAVERSE_DEPTH 4
struct lfs_dir_traverse {
const lfs_mdir_t *dir;
lfs_off_t off;
lfs_tag_t ptag;
const struct lfs_mattr *attrs;
int attrcount;
lfs_tag_t tmask;
lfs_tag_t ttag;
uint16_t begin;
uint16_t end;
int16_t diff;
int (*cb)(void *data, lfs_tag_t tag, const void *buffer);
void *data;
lfs_tag_t tag;
const void *buffer;
struct lfs_diskoff disk;
};
static int lfs_dir_traverse(lfs_t *lfs, static int lfs_dir_traverse(lfs_t *lfs,
const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag, const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag,
const struct lfs_mattr *attrs, int attrcount, const struct lfs_mattr *attrs, int attrcount,
lfs_tag_t tmask, lfs_tag_t ttag, lfs_tag_t tmask, lfs_tag_t ttag,
uint16_t begin, uint16_t end, int16_t diff, uint16_t begin, uint16_t end, int16_t diff,
int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) {
// This function in inherently recursive, but bounded. To allow tool-based
// analysis without unnecessary code-cost we use an explicit stack
struct lfs_dir_traverse stack[LFS_DIR_TRAVERSE_DEPTH-1];
unsigned sp = 0;
int res;
// iterate over directory and attrs // iterate over directory and attrs
while (true) {
lfs_tag_t tag; lfs_tag_t tag;
const void *buffer; const void *buffer;
struct lfs_diskoff disk; struct lfs_diskoff disk;
while (true) {
{
if (off+lfs_tag_dsize(ptag) < dir->off) { if (off+lfs_tag_dsize(ptag) < dir->off) {
off += lfs_tag_dsize(ptag); off += lfs_tag_dsize(ptag);
int err = lfs_bd_read(lfs, int err = lfs_bd_read(lfs,
@@ -782,67 +820,145 @@ static int lfs_dir_traverse(lfs_t *lfs,
attrs += 1; attrs += 1;
attrcount -= 1; attrcount -= 1;
} else { } else {
return 0; // finished traversal, pop from stack?
res = 0;
break;
} }
// do we need to filter?
lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0); lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0);
if ((mask & tmask & tag) != (mask & tmask & ttag)) { if ((mask & tmask & tag) != (mask & tmask & ttag)) {
continue; continue;
} }
// do we need to filter? inlining the filtering logic here allows
// for some minor optimizations
if (lfs_tag_id(tmask) != 0) { if (lfs_tag_id(tmask) != 0) {
// scan for duplicates and update tag based on creates/deletes LFS_ASSERT(sp < LFS_DIR_TRAVERSE_DEPTH);
int filter = lfs_dir_traverse(lfs, // recurse, scan for duplicates, and update tag based on
dir, off, ptag, attrs, attrcount, // creates/deletes
0, 0, 0, 0, 0, stack[sp] = (struct lfs_dir_traverse){
lfs_dir_traverse_filter, &tag); .dir = dir,
if (filter < 0) { .off = off,
return filter; .ptag = ptag,
} .attrs = attrs,
.attrcount = attrcount,
.tmask = tmask,
.ttag = ttag,
.begin = begin,
.end = end,
.diff = diff,
.cb = cb,
.data = data,
.tag = tag,
.buffer = buffer,
.disk = disk,
};
sp += 1;
if (filter) { dir = dir;
off = off;
ptag = ptag;
attrs = attrs;
attrcount = attrcount;
tmask = 0;
ttag = 0;
begin = 0;
end = 0;
diff = 0;
cb = lfs_dir_traverse_filter;
data = &stack[sp-1].tag;
continue; continue;
} }
}
popped:
// in filter range? // in filter range?
if (!(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) { if (lfs_tag_id(tmask) != 0 &&
!(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) {
continue; continue;
} }
}
// handle special cases for mcu-side operations // handle special cases for mcu-side operations
if (lfs_tag_type3(tag) == LFS_FROM_NOOP) { if (lfs_tag_type3(tag) == LFS_FROM_NOOP) {
// do nothing // do nothing
} else if (lfs_tag_type3(tag) == LFS_FROM_MOVE) { } else if (lfs_tag_type3(tag) == LFS_FROM_MOVE) {
// recurse into move
stack[sp] = (struct lfs_dir_traverse){
.dir = dir,
.off = off,
.ptag = ptag,
.attrs = attrs,
.attrcount = attrcount,
.tmask = tmask,
.ttag = ttag,
.begin = begin,
.end = end,
.diff = diff,
.cb = cb,
.data = data,
.tag = LFS_MKTAG(LFS_FROM_NOOP, 0, 0),
};
sp += 1;
uint16_t fromid = lfs_tag_size(tag); uint16_t fromid = lfs_tag_size(tag);
uint16_t toid = lfs_tag_id(tag); uint16_t toid = lfs_tag_id(tag);
int err = lfs_dir_traverse(lfs, dir = buffer;
buffer, 0, 0xffffffff, NULL, 0, off = 0;
LFS_MKTAG(0x600, 0x3ff, 0), ptag = 0xffffffff;
LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0), attrs = NULL;
fromid, fromid+1, toid-fromid+diff, attrcount = 0;
cb, data); tmask = LFS_MKTAG(0x600, 0x3ff, 0);
if (err) { ttag = LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0);
return err; begin = fromid;
} end = fromid+1;
diff = toid-fromid+diff;
} else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) { } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) {
for (unsigned i = 0; i < lfs_tag_size(tag); i++) { for (unsigned i = 0; i < lfs_tag_size(tag); i++) {
const struct lfs_attr *a = buffer; const struct lfs_attr *a = buffer;
int err = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type, res = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type,
lfs_tag_id(tag) + diff, a[i].size), a[i].buffer); lfs_tag_id(tag) + diff, a[i].size), a[i].buffer);
if (err) { if (res < 0) {
return err; return res;
}
if (res) {
break;
} }
} }
} else { } else {
int err = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer); res = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer);
if (err) { if (res < 0) {
return err; return res;
}
if (res) {
break;
} }
} }
} }
if (sp > 0) {
// pop from the stack and return, fortunately all pops share
// a destination
dir = stack[sp-1].dir;
off = stack[sp-1].off;
ptag = stack[sp-1].ptag;
attrs = stack[sp-1].attrs;
attrcount = stack[sp-1].attrcount;
tmask = stack[sp-1].tmask;
ttag = stack[sp-1].ttag;
begin = stack[sp-1].begin;
end = stack[sp-1].end;
diff = stack[sp-1].diff;
cb = stack[sp-1].cb;
data = stack[sp-1].data;
tag = stack[sp-1].tag;
buffer = stack[sp-1].buffer;
disk = stack[sp-1].disk;
sp -= 1;
goto popped;
} else {
return res;
}
} }
#endif #endif