rbyd-rr: Eagerly flip, adopt branch before/after to disambiguate ysplits

It's been annoying for a while how many flip operations we need in
lfsr_rbyd_appendattr to implement diverging range removals correctly.
Unfortuantely, we need all of these flips since we need to know the
original alt ordering in order to know how to split yellow nodes.

Keep in mind yellow splits depend on what alts exist in our history:

          <y                              >b
  .-------'|                            .-'|
  |       <r  take red/yellow           | >b
  |  .----'|        =>            .-----|-'|
  |  |    <b                      |    <b  |
  |  |  .-'|                      |  .-'|  |
  1  2  3  4                   1  2  3  4  1

                                          <b
                                        .-'|
                                       <y  |
                take black     .-------'|  |
                    =>         |       <r  |
                               |  .----'   |
                               |  |       <b
                               |  |  .----'|
                               1  2  3  4  4

Or so I thought! Turns out there is a sort of hack we can use to
figure out the yellow split even after flipping.

Take a look at this example yellow node, and the various possible
jump/branch destinations:

                                   .-- branch    = 0xb20
  00000b10: altrle 0x401 w0 0xa10 -|-> p[0].jump = 0xa10
  00000b20: altrle 0x402 w0 0xa20 <'-> jump      = 0xa20
  00000b30: altble 0x403 w0 0xa30 <--- branch_   = 0xb30

Anything jump out? That's right! only branch_ is > branch.

This holds even after flips:

  branch    = 0xb20        branch    = 0xb20  flip2  branch    = 0xb20
  p[0].jump = 0xa10  flip  p[0].jump = 0xa10 --.---> p[0].jump = 0xb30
  jump      = 0xa20 --.--> jump      = 0xb30 --'-.-> jump      = 0xa20
  branch_   = 0xb30 --'--> branch_   = 0xa20 ----'-> branch_   = 0xa10

This is provable by noting that our alts can't even encode forward
jumps. So... proof by lack of encoding?

We can use this to determine which yellow split is needed even after
flipping:

- branch_ < branch && jump < branch => take yellow alt
- branch_ < branch && jump > branch => take red alt
- branch_ > branch                  => take black alt

This lets us move/deduplicate the flipping logic before the diverging
logic and operate in a sort of "flipped space", where branch_ is always
the next branch we will take.

Unfortunately we do need to flip red alts that don't get split back
before descending down red nodes, which sort of matches our weird access
pattern, but this extra flip is well worth the code savings elsewhere.

---

This greatly simplifies the state space of lfsr_rbyd_appendattr, and it
already shows in code size measurements:

           code          stack
  before: 34528           2864
  after:  34320 (-0.6%)   2864 (+0.0%)

                     code          frame          stack
  appendattr before: 2378            216            568
  appendattr after:  2280 (-4.1%)    216 (+0.0%)    568 (+0.0%)

But this is really only after simplifying the diverging logic and yellow
splits. I think there may be even more savings if we can figure out how
to move all of the alt logic into the "flipped space"...
This commit is contained in:
Christopher Haster
2024-04-14 12:00:09 -05:00
parent f06ef46e8b
commit 8f8dd9f981
+134 -152
View File
@@ -2928,119 +2928,56 @@ trunk:;
jump = branch - jump;
lfs_size_t branch_ = branch + d;
// do bounds want to take different paths? begin diverging
if (!diverged
// diverging black?
&& (((lfsr_tag_isblack(alt)
// give up if we find a yellow alt
|| lfsr_tag_isred(p[0].alt))
&& lfsr_tag_diverging2(
alt, weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag,
b_rid, b_tag))
// diverging red?
|| (lfsr_tag_isred(p[0].alt)
&& lfsr_tag_diverging(
p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag,
b_rid, b_tag)))) {
diverged = true;
// diverging red? flip
if (lfsr_tag_isred(p[0].alt)
&& lfsr_tag_diverging(
p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag,
b_rid, b_tag)) {
if (lfsr_tag_isparallel(alt, p[0].alt)) {
lfsr_tag_flip2(&alt, &weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid);
lfs_swap32(&jump, &branch_);
}
lfs_swap16(&p[0].alt, &alt);
lfs_swap32(&p[0].weight, &weight);
lfs_swap32(&p[0].jump, &jump);
p[0].alt |= LFSR_TAG_R;
alt &= ~LFSR_TAG_R;
// both diverging? collapse
if (lfsr_tag_diverging(
p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag,
b_rid, b_tag)) {
LFS_ASSERT(!lfsr_tag_isparallel(alt, p[0].alt));
lfsr_tag_flip2(&alt, &weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid);
lfs_swap32(&jump, &branch_);
weight += p[0].weight;
jump = p[0].jump;
lfsr_p_pop(p);
}
}
// diverging upper? stitch together both trunks
if (a_rid > b_rid || a_tag > b_tag) {
if (lfsr_tag_isgt(alt)) {
lfsr_tag_flip2(
&alt, &weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid);
lfs_swap32(&jump, &branch_);
}
lfsr_tag_trim2(
alt, weight,
p[0].alt, p[0].weight,
&lower_rid, &upper_rid,
&lower_tag, &upper_tag);
// stitch together both trunks
err = lfsr_p_push(lfs, rbyd, p,
LFSR_TAG_ALT(LFSR_TAG_LE, LFSR_TAG_B, d_tag),
d_rid - (lower_rid - weight),
jump);
if (err) {
return err;
}
// continue to next alt
branch = branch_;
continue;
}
// yellow alts should be parallel
LFS_ASSERT(!(lfsr_tag_isred(alt) && lfsr_tag_isred(p[0].alt))
|| lfsr_tag_isparallel(alt, p[0].alt));
// force diverged alts to be pruned
} else if (diverged
if (diverged
&& lfsr_tag_diverging2(
alt, weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag,
b_rid, b_tag)) {
if (lfsr_tag_follow2(
alt, weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag)) {
lfsr_tag_flip2(
&alt, &weight,
// both diverged? collapse
if (lfsr_tag_isred(p[0].alt)
&& lfsr_tag_diverging(
p[0].alt, p[0].weight,
lower_rid, upper_rid);
lfs_swap32(&jump, &branch_);
}
lower_rid, upper_rid,
a_rid, a_tag,
b_rid, b_tag)) {
if (!lfsr_tag_isparallel(alt, p[0].alt)) {
lfsr_tag_flip2(&alt, &weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid);
lfs_swap32(&jump, &branch_);
}
lfsr_tag_trim(
alt, weight,
&lower_rid, &upper_rid,
&lower_tag, &upper_tag);
weight = 0;
weight += p[0].weight;
jump = p[0].jump;
lfsr_p_pop(p);
// one diverged? trim so alt is pruned
} else {
if (lfsr_tag_follow2(
alt, weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag)) {
lfsr_tag_flip2(
&alt, &weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid);
lfs_swap32(&jump, &branch_);
}
lfsr_tag_trim(
alt, weight,
&lower_rid, &upper_rid,
&lower_tag, &upper_tag);
weight = 0;
}
}
// prune?
@@ -3093,10 +3030,89 @@ trunk:;
}
}
// two reds makes a yellow, split?
if (lfsr_tag_isred(alt) && lfsr_tag_isred(p[0].alt)) {
LFS_ASSERT(lfsr_tag_isparallel(alt, p[0].alt));
// take black alt? needs a flip
// <b >b
// .-'| => .-'|
// 1 2 1 2 1
if (lfsr_tag_follow2(
alt, weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag)) {
lfsr_tag_flip2(&alt, &weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid);
lfs_swap32(&jump, &branch_);
}
// should've taken red alt? needs a flip
// <r >r
// .----'| .-'|
// | <b => | >b
// | .-'| .--|-'|
// 1 2 3 1 2 3 1
if (lfsr_tag_isred(p[0].alt)
&& lfsr_tag_follow(p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag)) {
lfs_swap16(&p[0].alt, &alt);
lfs_swap32(&p[0].weight, &weight);
lfs_swap32(&p[0].jump, &jump);
alt = (alt & ~LFSR_TAG_R) | (p[0].alt & LFSR_TAG_R);
p[0].alt |= LFSR_TAG_R;
lfsr_tag_flip2(&alt, &weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid);
lfs_swap32(&jump, &branch_);
}
// do bounds want to take different paths? begin diverging
if (!diverged
// eagerly diverge on lower trunk
&& ((a_rid < b_rid || a_tag < b_tag)
// diverging black?
|| lfsr_tag_isblack(alt)
// give up if we find a yellow alt
|| lfsr_tag_isred(p[0].alt))
// we only need to check the second alt, either
// lower or upper trunk will flip on follow
&& lfsr_tag_diverging2(
alt, weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag,
b_rid, b_tag)) {
diverged = true;
// diverging upper? stitch together both trunks
if (a_rid > b_rid || a_tag > b_tag) {
lfsr_tag_trim2(
alt, weight,
p[0].alt, p[0].weight,
&lower_rid, &upper_rid,
&lower_tag, &upper_tag);
// stitch together both trunks
err = lfsr_p_push(lfs, rbyd, p,
LFSR_TAG_ALT(LFSR_TAG_LE, LFSR_TAG_B, d_tag),
d_rid - (lower_rid - weight),
jump);
if (err) {
return err;
}
// continue to next alt
branch = branch_;
continue;
}
}
// two reds makes a yellow, split?
//
// note we've lost the original yellow edge because of flips, but
// we know the red edge is the only branch_ > branch
if (lfsr_tag_isred(alt) && lfsr_tag_isred(p[0].alt)) {
// if we take the red or yellow alt we can just point
// to the black alt
// <y >b
@@ -3106,19 +3122,12 @@ trunk:;
// | | <b | <b |
// | | .-'| | .-'| |
// 1 2 3 4 1 2 3 4 1
if (lfsr_tag_follow2(
alt, weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag)) {
lfsr_tag_flip2(&alt, &weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid);
lfs_swap32(&jump, &branch_);
lfs_swap16(&p[0].alt, &alt);
lfs_swap32(&p[0].weight, &weight);
lfs_swap32(&p[0].jump, &jump);
if (branch_ < branch) {
if (jump > branch) {
lfs_swap16(&p[0].alt, &alt);
lfs_swap32(&p[0].weight, &weight);
lfs_swap32(&p[0].jump, &jump);
}
alt &= ~LFSR_TAG_R;
lfsr_tag_trim(
@@ -3155,45 +3164,18 @@ trunk:;
}
}
// black alts terminate 2-3-4 nodes
if (lfsr_tag_isblack(alt)) {
// take black alt? needs a flip
// <b >b
// .-'| => .-'|
// 1 2 1 2 1
if (lfsr_tag_follow2(
alt, weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag)) {
lfsr_tag_flip2(&alt, &weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid);
lfs_swap32(&jump, &branch_);
}
// should've taken red alt? needs a flip
// <r >r
// .----'| .-'|
// | <b => | >b
// | .-'| .--|-'|
// 1 2 3 1 2 3 1
if (lfsr_tag_isred(p[0].alt)
&& lfsr_tag_follow(p[0].alt, p[0].weight,
lower_rid, upper_rid,
a_rid, a_tag)) {
lfs_swap16(&p[0].alt, &alt);
lfs_swap32(&p[0].weight, &weight);
lfs_swap32(&p[0].jump, &jump);
p[0].alt |= LFSR_TAG_R;
alt &= ~LFSR_TAG_R;
// red alt? we need to read the rest of the 2-3-4 node
if (lfsr_tag_isred(alt)) {
// undo flip temporarily
if (branch_ < branch) {
lfsr_tag_flip2(&alt, &weight,
p[0].alt, p[0].weight,
lower_rid, upper_rid);
lfs_swap32(&jump, &branch_);
}
// black alt? terminate 2-3-4 nodes
} else {
// trim alts from our current bounds
lfsr_tag_trim2(
alt, weight,