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.
As an embedded library, littlefs's configuration straddles two worlds.
In most cases the configuration is usually constant at build time, but
when integrated into OSs, the configuration needs to be dynamically
configurable.
To help with this, littlefs has a separate lfs_config struct that can be
placed into ROM when possible.
But you know what's better than ROM configuration? Truely inlinable
static configuration known at compile-time. In addition to avoiding the
RAM cost, compile-time configuration allows for additional compiler
optimizations, such as constexpr-elimination and removal of unused
code-paths.
So how to enable static configuration?
1. define LFS_STATICCFG
2. implement callbacks as global functions:
- lfs_read
- lfs_prog
- lfs_erase
- lfs_sync
2. define the now-required constants that configure littlefs:
- LFS_READ_SIZE
- LFS_PROG_SIZE
- LFS_BLOCK_SIZE
- LFS_BLOCK_COUNT
- LFS_BLOCK_CYCLES
- LFS_CACHE_SIZE
- LFS_LOOKAHEAD_SIZE
- LFS_READ_BUFFER (optional)
- LFS_PROG_BUFFER (optional)
- LFS_LOOKAHEAD_BUFFER (optional)
- LFS_NAME_MAX (optional)
- LFS_FILE_MAX (optional)
- LFS_ATTR_MAX (optional)
Note, there is a separate configuration for the file configuration, this
can be enabled/disabled independently of LFS_STATICCFG. You will likely
want to define this as well if you are looking for the smallest code
size.
In order to avoid a mess of #ifdefs, the internals of littlefs use a
simple macro that redirects to either the dynamic or static config at
compile time:
#ifdef LFS_STATICCFG
#define LFS_CFG_READ_SIZE(lfs) ((void)lfs, LFS_READ_SIZE)
#else
#define LFS_CFG_READ_SIZE(lfs) lfs->cfg->read_size
#endif
Unfortunately it does look like there still may be a lot of issues
related to warnings of comparisons against constants... If only C had
a way to ignore warnings on individual statements...
Original idea by apmorton
Moved .travis.yml over to use the new test framework. A part of this
involved testing all of the configurations ran on the old framework
and deciding which to carry over. The new framework duplicates some of
the cases tested by the configurations so some configurations could be
dropped.
The .travis.yml includes some extreme ones, such as no inline files,
relocations every cycle, no intrinsics, power-loss every byte, unaligned
block_count and lookahead, and odd read_sizes.
There were several configurations were some tests failed because of
limitations in the tests themselves, so many conditions were added
to make sure the configurations can run on as many tests as possible.
- Removed old tests and test scripts
- Reorganize the block devices to live under one directory
- Plugged new test framework into Makefile
renamed:
- scripts/test_.py -> scripts/test.py
- tests_ -> tests
- {file,ram,test}bd/* -> bd/*
It took a surprising amount of effort to make the Makefile behave since
it turns out the "test_%" rule could override "tests/test_%.toml.test"
which is generated as part of test.py.