Commit Graph

454 Commits

Author SHA1 Message Date
Christopher Haster 1d037f4ec9 Symbol cleanup as weight representation becomes more concrete 2023-02-12 15:24:04 -06:00
Christopher Haster 21012dbb1b Some minor append cleanup, mostly around follow conditions 2023-02-12 15:23:48 -06:00
Christopher Haster 6a5aae98fc Cleaned up the leaf splitting condition
Turns out if do the less-than condition first, we can simplify things. I
think this is due to how we bias the tree weights.
2023-02-12 15:22:21 -06:00
Christopher Haster 5c42e52324 Revert "A different thought on tracking found tags during lfs_rbyd_append" 2023-02-12 15:21:42 -06:00
Christopher Haster 209e70897c A different thought on tracking found tags during lfs_rbyd_append
The idea here is to just use a separate variable for found tags, instead
of stealing a bit for tracking which tags were found.

I was hoping this would simplify more operations on the tags to avoid
some masking operations, but it doesn't seem like it.
2023-02-12 15:19:03 -06:00
Christopher Haster 3d008b793d This method of leaf splitting works, but I want to explore another option
Committing current progress.
2023-02-12 15:18:15 -06:00
Christopher Haster d8540974d4 Significant cleanup of lfs_rbyd_append, simplified pruning rules 2023-02-12 15:17:54 -06:00
Christopher Haster 17a043f4b8 Merged lfs_rbyd_append and lfs_rbyd_delete
Now we have one massive chimera of a function that does everything. This
is good software design, trust me.
2023-02-12 15:14:23 -06:00
Christopher Haster 179a1df3d3 Changed rbyd removes to remove from the tree, similar to deletes
Tag removal is basically a range-delete of one that doesn't change
the tree weights. This deduplicates the two methods of deleting tags and
completely gets rid of tombstoning.

Note we still need a "removed tag" encoding so that we can invalidate
tags that may be found during fetch operations. Fortunately this
encoding is basically free due to overlap with alt encoding.
2023-02-12 15:14:06 -06:00
Christopher Haster 9a1ce1be47 Removed erroneous new red edges on deletion 2023-02-12 15:14:01 -06:00
Christopher Haster 1a5efcfa89 Removed unnecessary use of graft in early continues 2023-02-12 15:14:01 -06:00
Christopher Haster 788e98a989 Made delete remove the path from the tree completely, fixed failing tests 2023-02-12 15:13:52 -06:00
Christopher Haster 045a1098fd Added similar delete permutation tests, note that some are failing 2023-02-12 15:02:45 -06:00
Christopher Haster b14851b657 Added more tests, mostly around remove permutations 2023-02-12 15:02:27 -06:00
Christopher Haster dc5e74ac41 Added range deletion testing 2023-02-12 15:02:11 -06:00
Christopher Haster fbb6bd0919 A working delete implementation?
Though note this still uses tombstoning.
2023-02-12 15:01:05 -06:00
Christopher Haster b621774759 Adopted upper/lower bounds in lfs_rbyd_append
There are two ways to represent the bounds in the search down the rbyd
tree:

1. Using lower/upper bounds and the id we are searching for:

     lower bound    id    upper bound
     |              |     |
     v              v     v
   <-a--b--c--d--e--f--g--h->

2. Using the lower/upper weights, which implicitly encodes the id,
   saving a word:

       lower weight     upper weight
       |                |
     .-'-----------. .--'-.
   <-a--b--c--d--e--f--g--h->

Now that I am diving deep into the rbyd algorithm again, the lower/upper
weight based approach just isn't worth the extra mental steps required
to understand what the algorithm is doing. Besides, we likely pay for
the implicit id anyways since we need enough state to remove the
ambiguity of sparse tags.
2023-02-12 14:52:46 -06:00
Christopher Haster d78c4412b6 Half-adopted upper/lower bounds in lookup and append 2023-02-12 14:51:34 -06:00
Christopher Haster ef7ee6eb7d Added delete permutation testing (failing as expected) and some minor tweaks 2023-02-12 14:48:33 -06:00
Christopher Haster 22879c1cbb Avoided issue with tail deletions by tossing out-of-bound lookups early 2023-02-12 14:47:38 -06:00
Christopher Haster 1b6e1fdd33 Fixed delete issue with cut starting in a red edge
Though this is a bit of an ugly fix...

Red edges mess everything up during deletion since we always need to
go down red edges in case we need to do a red flip. This puts our
algorithm into an awkward state.

It would be easier to do away with colors completely when we start
deleting (after all this branch of the tree _will_ be shrinking), but
this conflicts with the ultimate goal of deduplicate rbyd append.
2023-02-12 14:41:47 -06:00
Christopher Haster ca710b5a29 Initial, very, very rough implementation of rbyd range deletion
Tree deletion is such a pain. It always seems like an easy addition to
the core algorithm but always comes with problems.

The initial plan for deletes was to iterate through all tags, tombstone,
and then adjust weights as needed. This accomplishes deletes with little
change to the rbyd algorithm, but adds a complex traversal inside the
commit logic. Doable in one commit, but complex. It also risks weird
unintuitive corner cases since the cost of deletion grows with the number
of tags being deleted (O(m log n)).

But this rbyd data structure is a tree, so in theory it's possible to
delete a whole range of tags in a single O(log n) operation.

---

This is a proof-of-concept range deletion algorithm for rbyd trees.

Note, this does not preserve rbyd's balancing properties! But it is no
worse than tombstoning. This is acceptable for littlefs as any
unbalanced trees will be rebalanced during compaction.

The idea is to follow the same underlying dhara algorithm, where we
follow a search path and save any alt pointers not taken, but we follow
both search paths that form the outside of the range, and only keep
outside edges.

For example, a tree:

        .-------o-------.
        |               |
    .---o---.       .---o---.
    |       |       |       |
  .-o-.   .-o-.   .-o-.   .-o-.
  |   |   |   |   |   |   |   |
  a   b   c   d   e   f   g   h

To delete the range d-e, we would search for d, and search for e:

        ********o********
        *               *
    .---*****       *****---.
    |       *       *       |
  .-o-.   .-***   ***-.   .-o-.
  |   |   |   *   *   |   |   |
  a   b   c   d   e   f   g   h

And keep the outside edges:

    .---                 ---.
    |                       |
  .-o-.   .-         -.   .-o-.
  |   |   |           |   |   |
  a   b   c           f   g   h

But how do we combine the outside edges? The simpler option is to do
both searches seperately, one after the other. This would end up with a
tree like this:

    .---------o
    |         |
  .-o-.   .---o
  |   |   |   |
  a   b   c   o---------.
              |         |
              o---.   .-o-.
              |   |   |   |
              _   f   g   h

But this horribly throws off the balance of our tree! It's worse than
tombstoning, and gets worse with more tags.

An alternative strategy, which is used here, is to alternate edges as we
descend down the tree. This unfortunately is more complex, and requires
~2x the RAM, but better preserves the balance of our tree. It isn't
perfect, because we lose color information, but we can leave that up to
compaction:

  .---------o
  |         |
.-o-.       o---------.
|   |       |         |
a   b   .---o       .-o-.
        |   |       |   |
        c   o---.   g   h
            |   |
            _   f

I also hope this can be merged into lfs_rbyd_append, deduplicating the
entire core rbyd append algorithm.
2023-02-12 13:29:06 -06:00
Christopher Haster cde3ba4cd8 Different rbyd struct usage in commit, moved append into its own function
Under any optimization level the compiler should inline lfs_rbyd_append
into lfs_rbyd_commit, but this code organization was desperately needed.
2023-02-12 13:25:51 -06:00
Christopher Haster 2979e6273a Added weight-adjusting insert operations, though it is currently very hacky 2023-02-12 13:22:45 -06:00
Christopher Haster 6c66e20349 Added lfs_rbyd_get, bumped hint back up to full during lookup
The idea behind the full hint is that we may stay on the current search
path, benefiting from a loaded cache. Though this should probably be
benchmarked eventually.
2023-02-12 13:21:41 -06:00
Christopher Haster 3c17c94b94 Added test_rbyd_large with better boundary conditions near end-of-block
test_rbyd_large also doubles as a decent fuzz test, since it involves
many more tags than the permutation testing can ever hit.
2023-02-12 13:20:12 -06:00
Christopher Haster 5cdda57373 Added the ability to remove rbyd tags via tombstoning
It's quite lucky a spare bit is free in the tag encoding, this means we
don't need a reserved length value as originally planned. We end up using
all of the bits that overlap the alt pointer encoding, which is nice and
unexpected.
2023-02-12 13:16:55 -06:00
Christopher Haster a4fd070fc1 Dropped attr-size down to at most 28-bits (4 leb128 bytes)
This really doesn't need to be this large. The extra 4-bits is not worth
the required extra byte of waste in every commit (CRCs tags are forced
to use a fully expanded leb128 due to a catch-22 issue).
2023-02-12 13:12:44 -06:00
Christopher Haster 084094116f Got all <=7 lookup permutations working, required a recoloring fix
The alt pointers change position, but the colors are supposed to stay
the same. If you randomly mix up the colors it sure does make things
confusing.
2023-02-12 13:11:18 -06:00
Christopher Haster e73f1c0e8d A number of small rbyd tweaks and cleanup 2023-02-12 13:11:06 -06:00
Christopher Haster d9b419d36a Rbyd prune now working 2023-02-12 13:09:25 -06:00
Christopher Haster 471f12c79a Fixed issue with rcache going out-of-date when overlapping pcache
This was a inter-abstraction-layer assumption that won't hold true with
the rbyd blocks.

The whole caching layer probably deserves a rewrite at this point.
2023-02-12 13:07:29 -06:00
Christopher Haster b03420ccfc Got rbyd yellow splitting working 2023-02-12 13:06:58 -06:00
Christopher Haster ac021f623c Added rbyd traverse testing 2023-02-12 13:02:31 -06:00
Christopher Haster e95ca03ff1 Minor rbyd tweaks and cleanup 2023-02-12 13:01:52 -06:00
Christopher Haster 53efaac243 Ok, now fixed rflip, branch/jump needed tweaking 2023-02-12 13:01:29 -06:00
Christopher Haster fe28837861 Rbyd trees with 4-leaves now working, fixed lfs_rtag_flip bug
- This is when flips starts happening during lfs_rbyd_append
- lfs_rtag_flip had an off-by-one math mistake
2023-02-12 12:58:55 -06:00
Christopher Haster c5fec90465 Rbyd rflips are now working, quite nicely actually
It turns out statefulness works quite well with this algorithm (The
prototype was in Haskell, which created some artificial problems. I
think it may have just been too high-level a language for this
near-instruction-level algorithm).
2023-02-12 12:58:29 -06:00
Christopher Haster 05276cef9a Added a bias to alt weights so in-between tags prefer larger tags
This bias makes it so that tag lookups always find a tag strictly >= the
requested tag, unless we are at the end of the tree.

This makes tree traversal trivial, which is quite nice.

Need to remove ntag now, it's no longer needed.
2023-02-12 12:49:19 -06:00
Christopher Haster 168977ffad Implemented lfs_rbyd_lookup
Though maybe this should not require exact matches?
2023-02-12 12:48:06 -06:00
Christopher Haster 024aaeba56 Some small tweaks
- Moved alt encoding 0x1 => 0x4, which can lead to slightly better
  lookup tables, the perturb bit takes the same place as the color bit,
  which means both can be ignored in readonly operations.

- Dropped lfs_rbyd_fetchmatch, asking each lfs_rbyd_fetch to include NULL
  isn't that bad.

New encoding:

  tags:
  iiii iiiiiii iiiiiTT TTTTTTt ttt0tpv
                   ^--------^------^^^- 16-bit id
                            '------|||- 8-bit type2
                                   '||- 5-bit type1
                                    '|- perturb bit
                                     '- valid bit
  llll lllllll lllllll lllllll lllllll
                                     ^- n-bit length

  alts:
  wwww wwwwwww wwwwwww wwwwwww www1dcv
                                 ^^^-^- 28-bit weight
                                  '|-|- color bit
                                   '-|- direction bit
                                     '- valid bit
  jjjj jjjjjjj jjjjjjj jjjjjjj jjjjjjj
                                     ^- n-bit jump
2023-02-12 12:40:19 -06:00
Christopher Haster 9a0e3fc749 More rbyd tests, multi-commit now working 2023-02-12 12:39:17 -06:00
Christopher Haster ad00ca79e2 Added dbgrbyd.py script, fixed some small things in rbyd commit
- We need to actually write the perturb bit
- It helps to encode the crc's leb128 length field correctly
2023-02-12 12:38:07 -06:00
Christopher Haster 2802880eaa Initial groundwork for rbyd trees
- primitive lfs_rbyd_fetch
- primitive lfs_rbyd_commit
- tag reading/progging and encoding machinery

The tag encoding scheme here uses pairs of leb128s, encoding either
a normal tag:

  iiii iiiiiii iiiiiTT TTTTTTt ttttt0v
                   ^--------^------^-^- 16-bit id
                            '------|-|- 8-bit type2
                                   '-|- 6-bit type1
                                     '- valid bit
  llll lllllll lllllll lllllll lllllll
                                     ^- n-bit length

Or an alt pointer:

  wwww wwwwwww wwwwwww wwwwwww wwwcd1v
                                 ^^^-^- 28-bit weight
                                  '|-|- color bit
                                   '-|- direction bit
                                     '- valid bit
  jjjj jjjjjjj jjjjjjj jjjjjjj jjjjjjj
                                     ^- n-bit jump

Note that two bits overlap the alt pointer dir/color encoding, this
is actually not a problem at all since some tags (crcs/fcrcs) don't
participate in the rbyd tree and can use these bits.

There's a number of benefits to using leb128s, which should probably
be written about, most notably is the abstraction of the device's
word-size. The "n-bits" above can be whatever word size works on the
device, trading off code-size for storage capabilities without breaking
compatibility with other devices. This will eventually be negotiated via
the superblock.
2023-02-12 12:37:59 -06:00
Christopher Haster 2d272bb62a Merge branch 'brent-cycle-detection' into rbyd-master 2022-12-17 21:00:04 -06:00
Christopher Haster ba1c76435a Fixed issue where deorphan could get stuck circling between two half-orphans
This of course should never happen normally, two half-orphans requires
two parents, which is disallowed in littlefs for this reason. But it can
happen if there is an outdated half-orphan later in the metadata
linked-list. The two half-orphans can cause the deorphan step to get
stuck, constantly "fixing" the first half-orphan before it has a chance
to remove the problematic, outdated half-orphan later in the list.

The solution here is to do a full check for half-orphans before
restarting the half-orphan loop. This strategy has the potential to
visit more metadata blocks unnecessarily, but avoids situations where
removing a later half-orphan will eventually cause an earlier
half-orphan to resolve itself.

Found with heuristic powerloss testing with test_relocations_reentrant_renames
after 192 nested powerlosses.
2022-12-17 12:42:05 -06:00
Christopher Haster d1b254da2c Reverted removal of 1-bit counter threaded through tags
Initially I thought the fcrc would be sufficient for all of the
end-of-commit context, since indicating that there is a new commit is a
simple as invalidating the fcrc. But it turns out there are cases that
make this impossible.

The surprising, and actually common, case, is that of an fcrc that
will end up containing a full commit. This is common as soon as the
prog_size is big, as small commits are padded to the prog_size at
minimum.

  .------------------. \
  |     metadata     | |
  |                  | |
  |                  | +-.
  |------------------| | |
  |   foward CRC ------------.
  |------------------| / |   |
  |   commit CRC    -----'   |
  |------------------|       |
  |     padding      |       |
  |                  |       |
  |------------------| \   \ |
  |     metadata     | |   | |
  |                  | +-. | |
  |                  | | | +-'
  |------------------| / | |
  |   commit CRC --------' |
  |------------------|     |
  |                  |     /
  '------------------'

When the commit + crc is all contained in the fcrc, something silly
happens with the math behind crcs. Everything in the commit gets
canceled out:

  crc(m) = m(x) x^|P|-1 mod P(x)

  m ++ crc(m) = m(x) x^|P|-1 + (m(x) x^|P|-1 mod P(x))

  crc(m ++ crc(m)) = (m(x) x^|P|-1 + (m(x) x^|P|-1 mod P(x))) x^|P|-1 mod P(x)

  crc(m ++ crc(m)) = (m(x) x^|P|-1 + m(x) x^|P|-1) x^|P|-1 mod P(x)

  crc(m ++ crc(m)) = 0 * x^|P|-1 mod P(x)

This is the reason the crc of a message + naive crc is zero. Even with an
initializer/bit-fiddling, the crc of the whole commit ends up as some
constant.

So no manipulation of the commit can change the fcrc...

But even if this did work, or we changed this scheme to use two
different checksums, it would still require calculating the fcrc of
the whole commit to know if we need to tweak the first bit to invalidate
the unlikely-but-problematic case where we happen to match the fcrc. This
would add a large amount of complexity to the commit code.

It's much simpler and cheaper to keep the 1-bit counter in the tag, even
if it adds another moving part to the system.
2022-12-17 12:42:05 -06:00
Christopher Haster 2f26966710 Continued implementation of forward-crcs, adopted new test runners
This fixes most of the remaining bugs (except one with multiple padding
commits + noop erases in test_badblocks), with some other code tweaks.

The biggest change was dropping reliance on end-of-block commits to know
when to stop parsing commits. We can just continue to parse tags and
rely on the crc for catch bad commits, avoiding a backwards-compatiblity
hiccup. So no new commit tag.

Also renamed nprogcrc -> fcrc and commitcrc -> ccrc and made naming in
the code a bit more consistent.
2022-12-17 12:42:05 -06:00
Christopher Haster b4091c6871 Switched to separate-tag encoding of forward-looking CRCs
Previously forward-looking CRCs was just two new CRC types, one for
commits with forward-looking CRCs, one without. These both contained the
CRC needed to complete the current commit (note that the commit CRC
must come last!).

         [--   32   --|--   32   --|--   32   --|--   32   --]
with:    [  crc3 tag  | nprog size |  nprog crc | commit crc ]
without: [  crc2 tag  | commit crc ]

This meant there had to be several checks for the two possible structure
sizes, messying up the implementation.

         [--   32   --|--   32   --|--   32   --|--   32   --|--   32   --]
with:    [nprogcrc tag| nprog size |  nprog crc | commit tag | commit crc ]
without: [ commit tag | commit crc ]

But we already have a mechanism for storing optional metadata! The
different metadata tags! So why not use a separate tage for the
forward-looking CRC, separate from the commit CRC?

I wasn't sure this would actually help that much, there are still
necessary conditions for wether or not a forward-looking CRC is there,
but in the end it simplified the code quite nicely, and resulted in a ~200 byte
code-cost saving.
2022-12-17 12:42:05 -06:00
Christopher Haster 91ad673c45 Cleaned up a few additional commit corner cases
- General cleanup from integration, including cleaning up some older
  commit code
- Partial-prog tests do not make sense when prog_size == block_size
  (there can't be partial-progs!)
- Fixed signed-comparison issue in modified filebd
2022-12-17 12:42:05 -06:00