From f9324d14439fb5a2e78c3c0891a45804efc4df2d Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 26 Nov 2020 10:26:51 -0600 Subject: [PATCH] Exploring inlined mutable configuration Currently littlefs uses a separate mutable state struct and immutable config struct. This lets users place the config struct in ROM where possible. However the recent addition of LFS_STATICCFG raises the question of if this split is still valuable. If the config is copied into the mutable struct at runtime, this allows a couple things: 1. Easier user interface, config can be stack allocated, no need to store the config struct for the lifetime of littlefs in OSs. 2. Avoids duplication when littlefs would need to change config based on observed superblocks, such as LFS_NAME_MAX limits 3. In theory, access to a single struct is faster/smaller as it avoids an additional load instruction. Unfortunately, inlining the dynamic config runs into several issues: 1. The code size actually increases with this change! From digging into this it's for a couple reasons: - Copying the config over takes code. - C has notorious problems with pointer aliasing, accessing constants from a const struct actually allows C to assume the values aren't going to change in more situations. This suggests it may be possible to reduce the code size more by loading the config pointer into a variable, but I haven't explored this probably not-worth-it optimization. - Even assuming deduplication of superblock-dependent configuration, the config struct is significantly larger than the mutable struct, and it turns out combining these two exceeds the limits of immediate-relative-loads, discarding the possible code size improvement from avoiding a second dereference. 2. The implementation of dynamic configuration differs significantly from the static configuration. This adds mess into the compile-time #ifdefs needed to support both options. --- lfs.c | 168 +++++++++++++++++++++++---------------- lfs.h | 7 +- tests/test_evil.toml | 24 ++---- tests/test_truncate.toml | 2 +- 4 files changed, 112 insertions(+), 89 deletions(-) diff --git a/lfs.c b/lfs.c index 3e5dfcc..a276e6d 100644 --- a/lfs.c +++ b/lfs.c @@ -34,26 +34,26 @@ #else // direct config towards dynamic lfs_cfg struct #define LFS_CFG_READ(lfs, block, off, buffer, size) \ - lfs->cfg->read(lfs->cfg->ctx, block, off, buffer, size) + lfs->cfg.read(lfs->cfg.ctx, block, off, buffer, size) #define LFS_CFG_PROG(lfs, block, off, buffer, size) \ - lfs->cfg->prog(lfs->cfg->ctx, block, off, buffer, size) + lfs->cfg.prog(lfs->cfg.ctx, block, off, buffer, size) #define LFS_CFG_ERASE(lfs, block) \ - lfs->cfg->erase(lfs->cfg->ctx, block) + lfs->cfg.erase(lfs->cfg.ctx, block) #define LFS_CFG_SYNC(lfs) \ - lfs->cfg->sync(lfs->cfg->ctx) -#define LFS_CFG_READ_SIZE(lfs) lfs->cfg->read_size -#define LFS_CFG_PROG_SIZE(lfs) lfs->cfg->prog_size -#define LFS_CFG_BLOCK_SIZE(lfs) lfs->cfg->block_size -#define LFS_CFG_BLOCK_COUNT(lfs) lfs->cfg->block_count -#define LFS_CFG_BLOCK_CYCLES(lfs) lfs->cfg->block_cycles -#define LFS_CFG_CACHE_SIZE(lfs) lfs->cfg->cache_size -#define LFS_CFG_LOOKAHEAD_SIZE(lfs) lfs->cfg->lookahead_size -#define LFS_CFG_READ_BUFFER(lfs) lfs->cfg->read_buffer -#define LFS_CFG_PROG_BUFFER(lfs) lfs->cfg->prog_buffer -#define LFS_CFG_LOOKAHEAD_BUFFER(lfs) lfs->cfg->lookahead_buffer -#define LFS_CFG_NAME_MAX(lfs) lfs->cfg->name_max -#define LFS_CFG_FILE_MAX(lfs) lfs->cfg->file_max -#define LFS_CFG_ATTR_MAX(lfs) lfs->cfg->attr_max + lfs->cfg.sync(lfs->cfg.ctx) +#define LFS_CFG_READ_SIZE(lfs) lfs->cfg.read_size +#define LFS_CFG_PROG_SIZE(lfs) lfs->cfg.prog_size +#define LFS_CFG_BLOCK_SIZE(lfs) lfs->cfg.block_size +#define LFS_CFG_BLOCK_COUNT(lfs) lfs->cfg.block_count +#define LFS_CFG_BLOCK_CYCLES(lfs) lfs->cfg.block_cycles +#define LFS_CFG_CACHE_SIZE(lfs) lfs->cfg.cache_size +#define LFS_CFG_LOOKAHEAD_SIZE(lfs) lfs->cfg.lookahead_size +#define LFS_CFG_READ_BUFFER(lfs) lfs->cfg.read_buffer +#define LFS_CFG_PROG_BUFFER(lfs) lfs->cfg.prog_buffer +#define LFS_CFG_LOOKAHEAD_BUFFER(lfs) lfs->cfg.lookahead_buffer +#define LFS_CFG_NAME_MAX(lfs) lfs->cfg.name_max +#define LFS_CFG_FILE_MAX(lfs) lfs->cfg.file_max +#define LFS_CFG_ATTR_MAX(lfs) lfs->cfg.attr_max #endif #ifdef LFS_FILE_STATICCFG @@ -63,9 +63,9 @@ #define LFS_FILE_CFG_ATTR_COUNT(file) LFS_FILE_ATTR_COUNT #else // direct config towards dynamic lfs_cfg struct -#define LFS_FILE_CFG_BUFFER(file) file->cfg->buffer -#define LFS_FILE_CFG_ATTRS(file) file->cfg->attrs -#define LFS_FILE_CFG_ATTR_COUNT(file) file->cfg->attr_count +#define LFS_FILE_CFG_BUFFER(file) file->cfg.buffer +#define LFS_FILE_CFG_ATTRS(file) file->cfg.attrs +#define LFS_FILE_CFG_ATTR_COUNT(file) file->cfg.attr_count #endif @@ -1099,7 +1099,7 @@ static int lfs_dir_getinfo(lfs_t *lfs, lfs_mdir_t *dir, } lfs_stag_t tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x780, 0x3ff, 0), - LFS_MKTAG(LFS_TYPE_NAME, id, lfs->name_max+1), info->name); + LFS_MKTAG(LFS_TYPE_NAME, id, LFS_CFG_NAME_MAX(lfs)+1), info->name); if (tag < 0) { return (int)tag; } @@ -1990,7 +1990,7 @@ int lfs_mkdir(lfs_t *lfs, const char *path) { // check that name fits lfs_size_t nlen = strlen(path); - if (nlen > lfs->name_max) { + if (nlen > LFS_CFG_NAME_MAX(lfs)) { LFS_TRACE("lfs_mkdir -> %d", LFS_ERR_NAMETOOLONG); return LFS_ERR_NAMETOOLONG; } @@ -2445,7 +2445,11 @@ static int lfs_ctz_traverse(lfs_t *lfs, /// Top level file operations /// static int lfs_file_opencommon(lfs_t *lfs, lfs_file_t *file, - const char *path, int flags) { + const char *path, int flags +#ifndef LFS_FILE_STATICCFG + , const struct lfs_file_cfg *cfg +#endif + ) { // deorphan if we haven't yet, needed at most once after poweron if ((flags & 3) != LFS_O_RDONLY) { int err = lfs_fs_forceconsistency(lfs); @@ -2454,6 +2458,9 @@ static int lfs_file_opencommon(lfs_t *lfs, lfs_file_t *file, } } + // copy over config + file->cfg = *cfg; + // setup simple file details int err; file->flags = flags | LFS_F_OPENED; @@ -2481,7 +2488,7 @@ static int lfs_file_opencommon(lfs_t *lfs, lfs_file_t *file, // check that name fits lfs_size_t nlen = strlen(path); - if (nlen > lfs->name_max) { + if (nlen > LFS_CFG_NAME_MAX(lfs)) { err = LFS_ERR_NAMETOOLONG; goto cleanup; } @@ -2535,7 +2542,7 @@ static int lfs_file_opencommon(lfs_t *lfs, lfs_file_t *file, } if ((file->flags & 3) != LFS_O_RDONLY) { - if (LFS_FILE_CFG_ATTRS(file)[i].size > lfs->attr_max) { + if (LFS_FILE_CFG_ATTRS(file)[i].size > LFS_CFG_ATTR_MAX(lfs)) { err = LFS_ERR_NOSPC; goto cleanup; } @@ -2595,11 +2602,12 @@ int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) { LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)", (void*)lfs, (void*)file, path, flags); -#ifndef LFS_FILE_STATICCFG - static const struct lfs_file_cfg defaults = {0}; - file->cfg = &defaults; -#endif +#ifdef LFS_FILE_STATICCFG int err = lfs_file_opencommon(lfs, file, path, flags); +#else + int err = lfs_file_opencommon(lfs, file, path, flags, + &(struct lfs_file_cfg){0}); +#endif LFS_TRACE("lfs_file_open -> %d", err); return err; } @@ -2612,8 +2620,7 @@ int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", (void*)lfs, (void*)file, path, flags, (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); - file->cfg = cfg; - int err = lfs_file_opencommon(lfs, file, path, flags); + int err = lfs_file_opencommon(lfs, file, path, flags, cfg); LFS_TRACE("lfs_file_opencfg -> %d", err); return err; } @@ -2966,7 +2973,7 @@ lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, file->pos = file->ctz.size; } - if (file->pos + size > lfs->file_max) { + if (file->pos + size > LFS_CFG_FILE_MAX(lfs)) { // Larger than file limit? LFS_TRACE("lfs_file_write -> %d", LFS_ERR_FBIG); return LFS_ERR_FBIG; @@ -3097,7 +3104,7 @@ lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, npos = file->ctz.size + off; } - if (npos > lfs->file_max) { + if (npos > LFS_CFG_FILE_MAX(lfs)) { // file position out of range LFS_TRACE("lfs_file_seek -> %d", LFS_ERR_INVAL); return LFS_ERR_INVAL; @@ -3343,7 +3350,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { if (prevtag == LFS_ERR_NOENT) { // check that name fits lfs_size_t nlen = strlen(newpath); - if (nlen > lfs->name_max) { + if (nlen > LFS_CFG_NAME_MAX(lfs)) { LFS_TRACE("lfs_rename -> %d", LFS_ERR_NAMETOOLONG); return LFS_ERR_NAMETOOLONG; } @@ -3473,7 +3480,7 @@ lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, tag = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x7ff, 0x3ff, 0), LFS_MKTAG(LFS_TYPE_USERATTR + type, - id, lfs_min(size, lfs->attr_max)), + id, lfs_min(size, LFS_CFG_ATTR_MAX(lfs))), buffer); if (tag < 0) { if (tag == LFS_ERR_NOENT) { @@ -3516,7 +3523,7 @@ int lfs_setattr(lfs_t *lfs, const char *path, uint8_t type, const void *buffer, lfs_size_t size) { LFS_TRACE("lfs_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", (void*)lfs, path, type, buffer, size); - if (size > lfs->attr_max) { + if (size > LFS_CFG_ATTR_MAX(lfs)) { LFS_TRACE("lfs_setattr -> %d", LFS_ERR_NOSPC); return LFS_ERR_NOSPC; } @@ -3535,9 +3542,16 @@ int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) { /// Filesystem operations /// -static int lfs_initcommon(lfs_t *lfs) { +static int lfs_initcommon(lfs_t *lfs +#ifndef LFS_STATICCFG + , const struct lfs_cfg *cfg +#endif + ) { int err = 0; + // copy over config + lfs->cfg = *cfg; + // validate that the lfs-cfg sizes were initiated properly before // performing any arithmetic logics with them LFS_ASSERT(LFS_CFG_READ_SIZE(lfs) != 0); @@ -3605,22 +3619,28 @@ static int lfs_initcommon(lfs_t *lfs) { // check that the size limits are sane LFS_ASSERT(LFS_CFG_NAME_MAX(lfs) <= LFS_NAME_MAX); - lfs->name_max = LFS_CFG_NAME_MAX(lfs); - if (!lfs->name_max) { - lfs->name_max = LFS_NAME_MAX; + //lfs->name_max = LFS_CFG_NAME_MAX(lfs); +#ifndef LFS_STATICCFG + if (!lfs->cfg.name_max) { + lfs->cfg.name_max = LFS_NAME_MAX; } +#endif LFS_ASSERT(LFS_CFG_FILE_MAX(lfs) <= LFS_FILE_MAX); - lfs->file_max = LFS_CFG_FILE_MAX(lfs); - if (!lfs->file_max) { - lfs->file_max = LFS_FILE_MAX; + //lfs->file_max = LFS_CFG_FILE_MAX(lfs); +#ifndef LFS_STATICCFG + if (!lfs->cfg.file_max) { + lfs->cfg.file_max = LFS_FILE_MAX; } +#endif LFS_ASSERT(LFS_CFG_ATTR_MAX(lfs) <= LFS_ATTR_MAX); - lfs->attr_max = LFS_CFG_ATTR_MAX(lfs); - if (!lfs->attr_max) { - lfs->attr_max = LFS_ATTR_MAX; + //lfs->attr_max = LFS_CFG_ATTR_MAX(lfs); +#ifndef LFS_STATICCFG + if (!lfs->cfg.attr_max) { + lfs->cfg.attr_max = LFS_ATTR_MAX; } +#endif // setup default state lfs->root[0] = LFS_BLOCK_NULL; @@ -3658,10 +3678,18 @@ static int lfs_deinit(lfs_t *lfs) { return 0; } -static int lfs_formatcommon(lfs_t *lfs) { +static int lfs_formatcommon(lfs_t *lfs +#ifndef LFS_STATICCFG + , const struct lfs_cfg *cfg +#endif + ) { int err = 0; { +#ifdef LFS_STATICCFG err = lfs_initcommon(lfs); +#else + err = lfs_initcommon(lfs, cfg); +#endif if (err) { return err; } @@ -3686,9 +3714,9 @@ static int lfs_formatcommon(lfs_t *lfs) { .version = LFS_DISK_VERSION, .block_size = LFS_CFG_BLOCK_SIZE(lfs), .block_count = LFS_CFG_BLOCK_COUNT(lfs), - .name_max = lfs->name_max, - .file_max = lfs->file_max, - .attr_max = lfs->attr_max, + .name_max = LFS_CFG_NAME_MAX(lfs), + .file_max = LFS_CFG_FILE_MAX(lfs), + .attr_max = LFS_CFG_ATTR_MAX(lfs), }; lfs_superblock_tole32(&superblock); @@ -3748,15 +3776,22 @@ int lfs_formatcfg(lfs_t *lfs, const struct lfs_cfg *cfg) { cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); - lfs->cfg = cfg; - int err = lfs_formatcommon(lfs); + int err = lfs_formatcommon(lfs, cfg); LFS_TRACE("lfs_formatcfg -> %d", err); return err; } #endif -static int lfs_mountcommon(lfs_t *lfs) { +static int lfs_mountcommon(lfs_t *lfs +#ifndef LFS_STATICCFG + , const struct lfs_cfg *cfg +#endif + ) { +#ifdef LFS_STATICCFG int err = lfs_initcommon(lfs); +#else + int err = lfs_initcommon(lfs, cfg); +#endif if (err) { return err; } @@ -3814,36 +3849,36 @@ static int lfs_mountcommon(lfs_t *lfs) { // check superblock configuration if (superblock.name_max) { - if (superblock.name_max > lfs->name_max) { + if (superblock.name_max > LFS_CFG_NAME_MAX(lfs)) { LFS_ERROR("Unsupported name_max (%"PRIu32" > %"PRIu32")", - superblock.name_max, lfs->name_max); + superblock.name_max, LFS_CFG_NAME_MAX(lfs)); err = LFS_ERR_INVAL; goto cleanup; } - lfs->name_max = superblock.name_max; + lfs->cfg.name_max = superblock.name_max; } if (superblock.file_max) { - if (superblock.file_max > lfs->file_max) { + if (superblock.file_max > LFS_CFG_FILE_MAX(lfs)) { LFS_ERROR("Unsupported file_max (%"PRIu32" > %"PRIu32")", - superblock.file_max, lfs->file_max); + superblock.file_max, LFS_CFG_FILE_MAX(lfs)); err = LFS_ERR_INVAL; goto cleanup; } - lfs->file_max = superblock.file_max; + lfs->cfg.file_max = superblock.file_max; } if (superblock.attr_max) { - if (superblock.attr_max > lfs->attr_max) { + if (superblock.attr_max > LFS_CFG_ATTR_MAX(lfs)) { LFS_ERROR("Unsupported attr_max (%"PRIu32" > %"PRIu32")", - superblock.attr_max, lfs->attr_max); + superblock.attr_max, LFS_CFG_ATTR_MAX(lfs)); err = LFS_ERR_INVAL; goto cleanup; } - lfs->attr_max = superblock.attr_max; + lfs->cfg.attr_max = superblock.attr_max; } } @@ -3909,8 +3944,7 @@ int lfs_mountcfg(lfs_t *lfs, const struct lfs_cfg *cfg) { cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, cfg->name_max, cfg->file_max, cfg->attr_max); - lfs->cfg = cfg; - int err = lfs_mountcommon(lfs); + int err = lfs_mountcommon(lfs, cfg); LFS_TRACE("lfs_mountcfg -> %d", err); return err; } @@ -4964,9 +4998,9 @@ static int lfs_migratecommon(lfs_t *lfs) { .version = LFS_DISK_VERSION, .block_size = LFS_CFG_BLOCK_SIZE(lfs), .block_count = LFS_CFG_BLOCK_COUNT(lfs), - .name_max = lfs->name_max, - .file_max = lfs->file_max, - .attr_max = lfs->attr_max, + .name_max = LFS_CFG_NAME_MAX(lfs), + .file_max = LFS_CFG_FILE_MAX(lfs), + .attr_max = LFS_CFG_ATTR_MAX(lfs), }; lfs_superblock_tole32(&superblock); diff --git a/lfs.h b/lfs.h index 23e7b69..a9b5c2f 100644 --- a/lfs.h +++ b/lfs.h @@ -384,7 +384,7 @@ typedef struct lfs_file { lfs_cache_t cache; #ifndef LFS_FILE_STATICCFG - const struct lfs_file_cfg *cfg; + struct lfs_file_cfg cfg; #endif } lfs_file_t; @@ -429,11 +429,8 @@ typedef struct lfs { } free; #ifndef LFS_STATICCFG - const struct lfs_cfg *cfg; + struct lfs_cfg cfg; #endif - lfs_size_t name_max; - lfs_size_t file_max; - lfs_size_t attr_max; #ifdef LFS_MIGRATE struct lfs1 *lfs1; diff --git a/tests/test_evil.toml b/tests/test_evil.toml index 3c7015c..fdc203d 100644 --- a/tests/test_evil.toml +++ b/tests/test_evil.toml @@ -12,8 +12,7 @@ code = ''' lfs_formatcfg(&lfs, &cfg) => 0; // change tail-pointer to invalid pointers - lfs.cfg = &cfg; - lfs_initcommon(&lfs) => 0; + lfs_initcommon(&lfs, &cfg) => 0; lfs_mdir_t mdir; lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( @@ -39,8 +38,7 @@ code = ''' lfs_unmount(&lfs) => 0; // change the dir pointer to be invalid - lfs.cfg = &cfg; - lfs_initcommon(&lfs) => 0; + lfs_initcommon(&lfs, &cfg) => 0; lfs_mdir_t mdir; lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; // make sure id 1 == our directory @@ -88,8 +86,7 @@ code = ''' lfs_unmount(&lfs) => 0; // change the file pointer to be invalid - lfs.cfg = &cfg; - lfs_initcommon(&lfs) => 0; + lfs_initcommon(&lfs, &cfg) => 0; lfs_mdir_t mdir; lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; // make sure id 1 == our file @@ -140,8 +137,7 @@ code = ''' lfs_file_close(&lfs, &file) => 0; lfs_unmount(&lfs) => 0; // change pointer in CTZ skip-list to be invalid - lfs.cfg = &cfg; - lfs_initcommon(&lfs) => 0; + lfs_initcommon(&lfs, &cfg) => 0; lfs_mdir_t mdir; lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; // make sure id 1 == our file and get our CTZ structure @@ -194,8 +190,7 @@ code = ''' lfs_formatcfg(&lfs, &cfg) => 0; // create an invalid gstate - lfs.cfg = &cfg; - lfs_initcommon(&lfs) => 0; + lfs_initcommon(&lfs, &cfg) => 0; lfs_mdir_t mdir; lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; lfs_fs_prepmove(&lfs, 1, (lfs_block_t [2]){ @@ -221,8 +216,7 @@ code = ''' lfs_formatcfg(&lfs, &cfg) => 0; // change tail-pointer to point to ourself - lfs.cfg = &cfg; - lfs_initcommon(&lfs) => 0; + lfs_initcommon(&lfs, &cfg) => 0; lfs_mdir_t mdir; lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; lfs_dir_commit(&lfs, &mdir, LFS_MKATTRS( @@ -244,8 +238,7 @@ code = ''' lfs_unmount(&lfs) => 0; // find child - lfs.cfg = &cfg; - lfs_initcommon(&lfs) => 0; + lfs_initcommon(&lfs, &cfg) => 0; lfs_mdir_t mdir; lfs_block_t pair[2]; lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; @@ -275,8 +268,7 @@ code = ''' lfs_unmount(&lfs) => 0; // find child - lfs.cfg = &cfg; - lfs_initcommon(&lfs) => 0; + lfs_initcommon(&lfs, &cfg) => 0; lfs_mdir_t mdir; lfs_block_t pair[2]; lfs_dir_fetch(&lfs, &mdir, (lfs_block_t[2]){0, 1}) => 0; diff --git a/tests/test_truncate.toml b/tests/test_truncate.toml index 38346e2..981443d 100644 --- a/tests/test_truncate.toml +++ b/tests/test_truncate.toml @@ -100,7 +100,7 @@ code = ''' lfs_file_open(&lfs, &file, "sequence", LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC) => 0; - size = lfs_min(lfs.cfg->cache_size, sizeof(buffer)/2); + size = lfs_min(lfs.cfg.cache_size, sizeof(buffer)/2); lfs_size_t qsize = size / 4; uint8_t *wb = buffer; uint8_t *rb = buffer + size;