Commit Graph

5 Commits

Author SHA1 Message Date
Christopher Haster 5d905e6da4 Dropped LFS3_KVONLY and LFS3_2BONLY modes for now
I think these are good ideas to bring back when littlefs3 is more
mature, but at the moment the number of different builds is creating too
much friction.

LFS3_KVONLY and LFS3_2BONLY in particular _add_ significant chunks of
code (lfs3_file_readget_, lfs3_file_flushset_, and various extra logic
sprinkled throughout the codebase), and the current state of testing
means I have no idea if any of it still works.

These are also low-risk for introducing any disk related changes.

So, ripping out for now to keep the current experimental development
tractable. May reintroduce in the future (probably after littlefs3 is
stabilized) if there is sufficient user interest. But doing so will
probably also need to come with actual testing in CI.
2025-10-24 00:20:53 -05:00
Christopher Haster 1f824a029b Renamed LFS3_T_COMPACT -> LFS3_T_COMPACTMETA (and gc_compactmeta_thresh)
- LFS3_T_COMPACT -> LFS3_T_COMPACTMETA
- gc_compact_thresh -> gc_compactmeta_thresh

And friends:

  LFS3_M_COMPACTMETA   0x00000800  Compact metadata logs
  LFS3_GC_COMPACTMETA  0x00000800  Compact metadata logs
  LFS3_I_COMPACTMETA   0x00000800  Filesystem may have uncompacted metadata
  LFS3_T_COMPACTMETA   0x00000800  Compact metadata logs

---

This does two things:

1. Highlights that LFS3_T_COMPACTMETA only interacts with metadata logs,
   and has no effect on data blocks.

2. Better matches the verb+noun names used for other gc/traversal flags
   (REPOPGBMAP, CKMETA, etc).

It is a bit more of a mouthful, but I'm not sure that's entirely a bad
thing. These are pretty low-level flags.
2025-10-23 23:54:57 -05:00
Christopher Haster 2c27c61f25 kv: Added LFS3_KVONLY to opt-out of advanced file operations
One of the ideas behind the key-value API is that it is potentially much
cheaper than a full file API. With the key-value API, we get the
guarantee that all data must fit in RAM, and avoid headaches like
random reads/writes and needing to broadcast file state.

For an example of just how much complexity is avoided, the see the
difference between lfs3_file_flushonce_ vs the mess that is
lfs3_file_flush_ + lfs3_file_crystallize + lfs3_file_graft.

However, littlefs is designed around files, and a couple design
decisions hold back how much code saving is possible:

1. littlefs's shrubs are designed around being enrolled in the omdir
   linked-list, so internally we still have most of the file open/close
   code lumbering around.

2. Directories and traversals still exist, so we'd need the omdir
   linked-list anyways, and we still need to broadcast _some_ changes.

3. Despite being intended for small amounts of data, lfs3_set/get can
   still be used to create arbitrarily large files. So we still need all
   of the bshrub/btree logic.

   Which we still need for the mtree anyways, so this isn't really that
   much of a downside.

It also may be possible to save more code by aggressively rewriting the
_entire_ read/write path for lfs3_set/get, to not reuse any of the
existing file logic in LFS3_KVONLY mode. But I decided against this due
to concerns around maintainability.

The duplicate lfs3_file_read + lfs3_file_readonce and lfs3_file_flush_ +
lfs3_file_flushonce_ are already enough of a concern.

Anyways, here's LFS3_KVONLY:

                  code           stack           ctx
  default:       37824            2416           636
  kvonly:        30936 (-18.2%)   2168 (-10.3%)  636 (+0.0%)

LFS3_RDONLY + LFS3_KVONLY is also interesting:

                  code           stack           ctx
  rdonly:        10776             856           508
  rdonly+kvonly:  9904 (-8.1%)     888 (+3.7%)   508 (+0.0%)

---

This also added some noise to the default build's code, mainly due to
tweaks in lfs3_file_readnext to allow better reuse in LFS3_KVONLY:

           code          stack          ctx
  before: 37824           2416          636
  after:  37860 (+0.1%)   2416 (+0.0%)  636 (+0.0%)
2025-06-24 16:14:02 -05:00
Christopher Haster 92844cce3e kv: Added *_set_zero and *_set_null tests
These are high-risk corner cases for the key-value API, so we should
test them.

At one point I was relying on an optional buffer parameter in
lfs3_file_sync_, but that would have broken if lfs3_set's buffer was
NULL.
2025-06-22 15:36:53 -05:00
Christopher Haster a75537faff kv: Implemented a simple key-value API
This adds a couple functions that treat files as simple key-value pairs:

- lfs3_get    - Read a file
- lfs3_size   - Get the size of a file
- lfs3_set    - Write a file
- lfs3_remove - Remove a file (this one already exists!)

The idea is the only real difference between a filesystem and key-value
store in the microcontroller space is the API, and the key-value API
_is_ much easier to use.

It also opens the door to making the file API opt-out in the future to
trade code cost for feature set. littlefs will probably never be
competitive with other microcontroller-scale key-value stores, but it
may be interesting for systems already using littlefs for other storage.

And don't worry, these are still files, so they can always be opened
with the full file API when more advanced operations are needed.

These APIs also matches the custom attribute APIs, which makes sense
because they're both key-values. Any mismatch should be considered an
API bug, because the best user interface is a consistent one.

This new API is tested in tests/test_kv.toml.

---

At the moment the implementation is naive, just sitting on top of the
file API. This works remarkably well thanks to littlefs's cache
bypassing logic, but does have some downsides:

- lfs3_set always writes two commits: one for the stickynote and one for
  the file sync.

  Unfortunately this is a fundamental limitation of littlefs's file API.
  One nice benefit of lfs3_set is in theory we can bypass this
  limitation, but not if we just sit on top of the file API.

- There may be code savings from more tightly integrating the key-value
  code.

This also highlighted an awkward corner case with per-file cache
configuration in which the buffer needs to be non-null even if zero. Not
the end of the world, but just a bit awkward. Maybe this deserves
revisiting in the config API rework?

---

Code changes were relatively minimal given that this is a whole new API,
unfortunately the stack took quite a hit:

           code          stack          ctx
  before: 37352           2280          636
  after:  37644 (+0.8%)   2448 (+7.4%)  636 (+0.0%)

The stack surprised me, but in hindsight it makes sense. In sitting on
top of the reset of the codebase, the key-value API adds very little
code, but every stack allocation in these functions add to the stack
hot-path.

This isn't the end of the world, and it's actually probably a good thing
to have an lfs3_file_t allocated in the stack hot-path. lfs3_file_t's
size has been a bit difficult to track thanks to struct lfs3_info
dominating ctx measurements...
2025-06-22 15:22:21 -05:00