From 85e43d51ba90452e914d219e5cb9bb371900c9a4 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Thu, 14 Mar 2024 02:46:59 -0500 Subject: [PATCH] Found a balance-preserving solution to tail-recursive range recoloring It feels a bit clumsy, but by using an additional bit of state to keep track of if the last alt was pruned, we can cancel recolorings that may risk recursion. If we look at how this plays out on the underlying 2-3-4 tree: .-----. .-------. .-------. .-------. |.a.h.| |.a.c.h.| |.a.c.h.| |.a.c.h.| '|-|-|' '|-|-|-|' '|-|-|-|' '|-|-|-|' | .-' '-. .--' '--. .-' '-. v v v v v v v .-------. .---. .---. .---. .-------. .---. .---. |.b.c.g.| => |.b.| |.g.| => |.b.| |.d.e.f.| => |.b.| |.e.| '|-|-|-|' '|-|' '|-|' '|-|' '|-|-|-|' '|-|' '|-|' | x | x .-' '-. v v v v .-------. .-------. .---. .---. |.d.e.f.| |.d.e.f.| |.d.| |.f.| '|-|-|-|' '|-|-|-|' '|-|' '|-|' Note the important property that no nodes ended up at a height _worse_ than where they started. It's interesting to note this is equivalent to splitting the nodes _before_ prunning: .-----. .-------. .-------. .-------. |.a.h.| |.a.c.h.| |.a.c.h.| |.a.c.h.| '|-|-|' '|-|-|-|' '|-|-|-|' '|-|-|-|' | .-' '-. .-' '--. .-' '-. v v v v v v v .-------. .---. .---. .---. .-----. .---. .---. |.b.c.g.| => |.b.| |.g.| => |.b.| |.e.g.| => |.b.| |.e.| '|-|-|-|' '|-|' '|-|' '|-|' '|-|-|' '|-|' '|-|' | x | x .---' | x .-' '-. v v v v v v .-------. .-------. .---. .---. .---. .---. |.d.e.f.| |.d.e.f.| |.d.| |.f.| |.d.| |.f.| '|-|-|-|' '|-|-|-|' '|-|' '|-|' '|-|' '|-|' Which is probably why most of our 2-3-4 tree invariants hold. In the actual implementation, we encode the current pruned state as a part of our diverging state machine, since we don't non-trivially prune outside of diverging trunks. This ends up with the following, slightly-extended, diverging state machine: diverge possible? diverge not possible? | | v | DIVERGINGLOWER-------------------. | | | | v v v DIVERGEDLOWER<->PRUNEDLOWER NOTDIVERGING | .------------' | v v | DIVERGINGUPPER | | | v | DIVERGEDUPPER<->PRUNEDUPPER | '------------. | .----------' v v v leaf stuff Writing out the state machine like this actually highlights the slightly annoying transition from PRUNEDUPPER to leaf stuff, which was buggy in the first impl. We also encode some common information (lower/upper, pruned, etc) in the state machine's bit encoding to try to avoid too many if statements. Though this impl does seem a bit heavy handed. The additional complexity results in of course more code cost, but as a trade-off our range recoloring should be a bit more sturdy and provably preserves the h=2log2(b) worst case height of our tree: code stack broken recoloring: 33880 2880 unbalanced recoloring: 33912 (+0.1%) 2880 (+0.0%) balanced recoloring: 33944 (+0.2%) 2880 (+0.0%) --- lfs.c | 84 +++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/lfs.c b/lfs.c index bfc17865..5c85d6fb 100644 --- a/lfs.c +++ b/lfs.c @@ -2655,8 +2655,6 @@ static void lfsr_rbyd_p_red( lfsr_rid_t p_weights[static 3], lfs_size_t p_jumps[static 3]) { // propagate a red edge upwards - p_alts[0] &= ~LFSR_TAG_R; - if (p_alts[1]) { p_alts[1] |= LFSR_TAG_R; @@ -2696,20 +2694,46 @@ static void lfsr_rbyd_p_red( // diverged state machine for range appends enum { - LFSR_D_NOTDIVERGING = 0, - LFSR_D_DIVERGINGLOWER = 1, - LFSR_D_DIVERGINGUPPER = 2, - LFSR_D_DIVERGEDLOWER = 3, - LFSR_D_DIVERGEDUPPER = 4, + LFSR_D_NOTDIVERGING = 0x0, + LFSR_D_DIVERGINGLOWER = 0x2, + LFSR_D_DIVERGINGUPPER = 0x3, + LFSR_D_DIVERGEDLOWER = 0x4, + LFSR_D_DIVERGEDUPPER = 0x5, + LFSR_D_PRUNEDLOWER = 0x6, + LFSR_D_PRUNEDUPPER = 0x7, }; static inline bool lfsr_d_isdiverged(uint8_t d_state) { return d_state >= LFSR_D_DIVERGEDLOWER; } +static inline bool lfsr_d_isupper(uint8_t d_state) { + return d_state & 0x1; +} + +static inline bool lfsr_d_islower(uint8_t d_state) { + return !(d_state & 0x1); +} + +static inline bool lfsr_d_ispruned(uint8_t d_state) { + return d_state >= LFSR_D_PRUNEDLOWER; +} + static inline uint8_t lfsr_d_diverge(uint8_t d_state) { LFS_ASSERT(d_state != LFSR_D_NOTDIVERGING); - return d_state + (LFSR_D_DIVERGEDLOWER - LFSR_D_DIVERGINGLOWER); + return (!lfsr_d_isdiverged(d_state)) + ? d_state + (LFSR_D_DIVERGEDLOWER - LFSR_D_DIVERGINGLOWER) + : d_state; +} + +static inline uint8_t lfsr_d_prune(uint8_t d_state) { + LFS_ASSERT(lfsr_d_isdiverged(d_state)); + return d_state | 0x2; +} + +static inline uint8_t lfsr_d_unprune(uint8_t d_state) { + LFS_ASSERT(lfsr_d_isdiverged(d_state)); + return d_state & ~0x2; } // core rbyd algorithm @@ -2881,7 +2905,7 @@ again:; d_state = lfsr_d_diverge(d_state); // stitch together diverged branches - if (d_state == LFSR_D_DIVERGEDUPPER && d_tag) { + if (lfsr_d_isupper(d_state) && d_tag) { err = lfsr_rbyd_p_push(lfs, rbyd, p_alts, p_weights, p_jumps, LFSR_TAG_ALT(LFSR_TAG_LE, LFSR_TAG_B, d_tag), @@ -2904,16 +2928,15 @@ again:; // | |