From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Matt Armstrong Newsgroups: gmane.emacs.devel Subject: Re: noverlay branch Date: Mon, 10 Oct 2022 20:46:43 -0700 Message-ID: <87sfjvvx7g.fsf@rfc20.org> References: <87sfjzefvv.fsf@rfc20.org> <875ygt6gbj.fsf@rfc20.org> <87pmf04c7s.fsf@rfc20.org> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="28769"; mail-complaints-to="usenet@ciao.gmane.io" Cc: emacs-devel@gnu.org To: Stefan Monnier Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Tue Oct 11 05:50:17 2022 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1oi6HY-0007Hq-Cy for ged-emacs-devel@m.gmane-mx.org; Tue, 11 Oct 2022 05:50:16 +0200 Original-Received: from localhost ([::1]:52628 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1oi6HX-0007HA-6a for ged-emacs-devel@m.gmane-mx.org; Mon, 10 Oct 2022 23:50:15 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:36630) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oi6EL-0003M6-89 for emacs-devel@gnu.org; Mon, 10 Oct 2022 23:46:57 -0400 Original-Received: from relay2-d.mail.gandi.net ([2001:4b98:dc4:8::222]:59377) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oi6EG-0007sC-Cx for emacs-devel@gnu.org; Mon, 10 Oct 2022 23:46:56 -0400 Original-Received: (Authenticated sender: matt@rfc20.org) by mail.gandi.net (Postfix) with ESMTPSA id 76B4740003; Tue, 11 Oct 2022 03:46:45 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=rfc20.org; s=gm1; t=1665460007; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=KzxwUhhmoz+WoH/USnfEbz8Vt6bjUE3pHjolwt79ax4=; b=Vhm0oJWc4noDSLrFbPMCv3cmEIGjfkJ60LRx6vnj6poKXOms9Co4zsyAkw31IhQtcl9lcz zYBGggmTEK8gVsbgIYCWWZkZKCGT/Dxwba/C+T3KcfO4t8estAIAIqfTAPNqTdAhp9+V+t Qid9HcWH4Sb5TvsHxtgPFsujhqC4rkZeJSN5PAfbdtDGNKsKOtG3QnFLMD/gAgMTgItCGU JcgyDq73BTztEq2X7xm0IS8w1odBA0HCQpdSMwYxIv4+UVXWNP+OtGRJzRDgx3eBv7Uce1 vRDc/7loTXh1X0mtPwD2yE6ZllUho1WOrPaHVJGzUCjCkgq+3R/aZv2ycWLxpA== Original-Received: from matt by naz with local (Exim 4.96) (envelope-from ) id 1oi6E7-009TEa-1C; Mon, 10 Oct 2022 20:46:43 -0700 In-Reply-To: Received-SPF: pass client-ip=2001:4b98:dc4:8::222; envelope-from=matt@rfc20.org; helo=relay2-d.mail.gandi.net X-Spam_score_int: -23 X-Spam_score: -2.4 X-Spam_bar: -- X-Spam_report: (-2.4 / 5.0 requ) BAYES_00=-1.9, DKIM_INVALID=0.1, DKIM_SIGNED=0.1, RCVD_IN_DNSWL_LOW=-0.7, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:297411 Archived-At: --=-=-= Content-Type: text/plain Stefan, I read only Eli's reply this morning. Got to yours just now. Stefan Monnier writes: >> I had a different idea of tightening the otree invariant to this: >> >> 1) A node's otick must be greater than or equal to its children's. > > That's already checked in the current code, right? Not yet...until...just now. See patches below. >> 3) All downward tree traversal propagates offsets and otick. > > I think we already do that, but if there are places we missed, then yes, > of course. Yes, I think we do. The wrinkle is that we don't always start inheriting at the root, but otick is not updated in that case. > Regarding `otick`, I can see 2 more options: > - Get rid of it completely: its sole purpose is to try and keep > `overlay-start/end` O(1) in the usual case instead of O(log N), but > I'm not convinced it's worth the cost of propagating `otick` everywhere > all the time. > - A halfway point is to keep `otick` but update it more lazily, > i.e. only update it when we do `overlay-start/end` (e.g. in > `interval_tree_validate`). These ideas are simpler but similar in direction to my idea to use a btree instead. Then some of the "augmentation" could live in btree arrays, which are a lot faster to traverse and modify in bulk. (pie in sky idea, maybe for Emacs 30 but more likely 40!). >> Subject: [PATCH 2/2] Add ./configure --enable-checking=overlays > > I think this is going overboard. I'd rather focus on providing good > "everyday checks" (i.e. cheap checks which help document our invariants, > controlled by the normal ENABLE_CHECKING) and leave the rest of the > debugging code under "manual" control rather than expose it as an > `--enable-checking=` option. Yes, Eli said the same. I've removed it. > My assumption here is that once debugged, this overlay code will stay > untouched for the next many years (based on the past history of our > overlay code). Yep, a great reason to have good ENABLE_CHECKING code. >> +static struct check_subtree_result >> +check_subtree (struct interval_node *node, uintmax_t tree_otick, >> + int max_depth, ptrdiff_t offset, ptrdiff_t min_begin, >> + ptrdiff_t max_begin) > > This max_depth also sounds to me like over-engineering. I'd like to keep max_depth. My reason for adding it is informed by experience on similar projects. It is easier to understand an assertion that the tree is too deep than it is to debug, say, stack overflow when there is a cycle in the link structure of the tree. It is easier to limit the height of checking if, indeed, on some night I am diagnosing an unrelated bug but the perf cost here is too much. With max_depth in place I can easily manually hack up a functon to verify checks only in a small subset of the tree (e.g. around a rotation operation, etc.). I've done and benefited from this sort of thing in the past. There is a small concrete benefit to it right now. MAX_DEPTH is now initialized from interval_tree_max_height(), which currently could wildy under estimate the height and we wouldn't notice (because generator stacks auto-grow). Since we call that fucntion to make "big enough to not need growing" stacks, it is nice to have test coverage for it. I don't like over-engeneering, but I wouldn't call this engineering at all. It is just an arg and a bit of related code, with some utility, and a tiny maintenance cost. :-) >> - /* We don't check the RB invariants here (neither the absence of >> - red+red nor the equal-black-depth), so that we can use this check >> - even while the tree is temporarily breaking some of those invarints. */ >> - /* Red nodes cannot have red parents. */ >> - /* eassert (node->parent == ITREE_NULL >> - || !(node->red && node->parent->red)); */ >> + /* We don't normally check the RB invariants here (neither the >> + absence of red+red nor the equal-black-depth), so that we can use >> + this check even while the tree is temporarily breaking some of >> + those invarints. You can enable them if you want. */ >> + if (false) >> + { >> + /* If a node is red then both of its children are black. Red >> + nodes cannot have red parents. */ >> + if (node->red) >> + { >> + eassert (node->left == ITREE_NULL >> + || node->left->red == false); >> + eassert (node->right == ITREE_NULL >> + || node->right->red == false); >> + eassert (node->parent == ITREE_NULL || !node->parent->red); >> + } >> + } > > Since we'll be recursing into the children, the first two easserts seem > redundant with the third (IOW the new code just does the same as the old > code, only more verbosely and less efficiently :-( ). > What am I missing? Good point, I reworked this for simplicity. >> + result.complete = left_result.complete && right_result.complete; >> + if (result.complete) > > I think all calls to `check_tree` should be complete traversals. > > Most of the invariants checked in it are already checked incrementally > via sprinkled assertions elsewhere, so it's only really useful when > debugging a concrete known issue where the "local" checks aren't good > enough. All `check_tree` calls are complete traversals. If they aren't there is a bug and an `eassert` will fire. > We could also include a few "cheap" calls to `check_tree` via `eassert` > (i.e. calls which we know shouldn't be algorithmically too expensive > because we're already traversing the whole tree for some other reason, > e.g. when killing a buffer (e.g. to verify that, at the end of the day, > we still preversed the RB invariants :-) or maybe during GC). ...I'm not understanding something here. All `check_tree` calls are in `eassert` already. >> + { >> + /* Every path from a node to a descendent leaf contains the same >> + number of black nodes. Often said this way: all nodes have >> + the same "black height". */ >> + eassert (left_result.black_height == right_result.black_height); > > IIUC this assertion should be conditionalized in the same way as the > red+red check, since this invariant can be temporarily false. Done, and now we have `check_tree_no_rb` for the one call site. At https://git.sr.ht/~matta/emacs/log/feature/noverlay or below. --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0001-Improve-check_subtree.patch >From 67b9e89a5aa0052b1abdcd8971a0fa83f52e13a5 Mon Sep 17 00:00:00 2001 From: Matt Armstrong Date: Mon, 10 Oct 2022 08:48:41 -0700 Subject: [PATCH 1/4] Improve check_subtree * src/itree.c (struct check_subtree_result): new struct returned by check_subtree. (check_subtree): new function, renamed from recurse_check_tree. Add new black height assertions. (check_tree): assert that the tree has non-negative size, assert that limiting to interval_tree_max_height(tree) levels is enough to traverses the complete tree. --- src/itree.c | 156 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 126 insertions(+), 30 deletions(-) diff --git a/src/itree.c b/src/itree.c index bbab70dac7..aa8a5f7f3b 100644 --- a/src/itree.c +++ b/src/itree.c @@ -205,14 +205,44 @@ itree_init (void) iter = interval_generator_create (NULL); } -static ptrdiff_t -recurse_check_tree (struct interval_node *node, uintmax_t tree_otick, - ptrdiff_t offset, ptrdiff_t min_begin, - ptrdiff_t max_begin, intmax_t *size) +struct check_subtree_result +{ + /* Were all nodes visited? */ + bool complete; + + /* Computed node count of the tree. */ + int size; + + /* Computed limit of the tree (max END). */ + ptrdiff_t limit; + + /* Computed black height of the tree (count of black nodes from the + bottom up to the root). */ + int black_height; +}; + +static struct check_subtree_result +check_subtree (struct interval_node *node, uintmax_t tree_otick, + int max_depth, ptrdiff_t offset, ptrdiff_t min_begin, + ptrdiff_t max_begin) { + struct check_subtree_result result = { .complete = false, + .size = 0, + .limit = PTRDIFF_MIN, + .black_height = 0 }; if (node == ITREE_NULL) - return PTRDIFF_MIN; - ++*size; + { + /* Every nil node of a Red-Black tree is black */ + result.black_height = 1; + result.complete = true; + return result; + }; + + if (max_depth == 0) + { + result.complete = false; + return result; + } /* Validate structure. */ eassert ( @@ -221,14 +251,35 @@ recurse_check_tree (struct interval_node *node, uintmax_t tree_otick, eassert (node->left == ITREE_NULL || node->left->parent == node); eassert (node->right == ITREE_NULL || node->right->parent == node); - /* We don't check the RB invariants here (neither the absence of - red+red nor the equal-black-depth), so that we can use this check - even while the tree is temporarily breaking some of those invarints. */ - /* Red nodes cannot have red parents. */ - /* eassert (node->parent == ITREE_NULL - || !(node->red && node->parent->red)); */ + /* We don't normally check the RB invariants here (neither the + absence of red+red nor the equal-black-depth), so that we can use + this check even while the tree is temporarily breaking some of + those invarints. You can enable them if you want. */ + if (false) + { + /* If a node is red then both of its children are black. Red + nodes cannot have red parents. */ + if (node->red) + { + eassert (node->left == ITREE_NULL + || node->left->red == false); + eassert (node->right == ITREE_NULL + || node->right->red == false); + eassert (node->parent == ITREE_NULL || !node->parent->red); + } + } - eassert (node->offset == 0 || node->otick < tree_otick); + /* Validate otick. A node's otick must be <= to the tree's otick + and <= to its parent's otick. + + Note: we cannot assert that (NODE.otick == NODE.parent.otick) + implies (NODE.offset == 0) because interval_tree_inherit_offset() + doesn't always update otick. It could, but it is not clear there + is a need. */ + eassert (node->otick <= tree_otick); + eassert (node->parent == ITREE_NULL + || node->otick <= node->parent->otick); + eassert (node->otick != tree_otick || node->offset == 0); offset += node->offset; ptrdiff_t begin = node->begin + offset; @@ -240,29 +291,74 @@ recurse_check_tree (struct interval_node *node, uintmax_t tree_otick, eassert (begin <= max_begin); eassert (end <= limit); - ptrdiff_t left_limit - = recurse_check_tree (node->left, tree_otick, offset, min_begin, - begin, size); - ptrdiff_t right_limit - = recurse_check_tree (node->right, tree_otick, offset, begin, - max_begin, size); - eassert (left_limit <= limit); - eassert (right_limit <= limit); - eassert (limit == max (end, max (left_limit, right_limit))); - return limit; + struct check_subtree_result left_result + = check_subtree (node->left, tree_otick, max_depth - 1, offset, + min_begin, begin); + struct check_subtree_result right_result + = check_subtree (node->right, tree_otick, max_depth - 1, offset, + begin, max_begin); + + eassert (left_result.limit <= limit); + eassert (right_result.limit <= limit); + + result.complete = left_result.complete && right_result.complete; + if (result.complete) + { + /* Every path from a node to a descendent leaf contains the same + number of black nodes. Often said this way: all nodes have + the same "black height". */ + eassert (left_result.black_height == right_result.black_height); + result.black_height + = (node->red ? 0 : 1) + left_result.black_height; + + result.size = 1 + left_result.size + right_result.size; + result.limit + = max (end, max (left_result.limit, right_result.limit)); + + eassert (limit == result.limit); + } + + return result; } +/* Validate invariants for TREE. + + This runs in constant time when ENABLE_OVERLAY_CHECKING is 0 + (i.e. Emacs is not configured with + "--enable_checking=yes,overlays"). In this mode it can't check all + the invariants. When ENABLE_OVERLAY_CHECKING is 1 it checks the + entire tree and validates all invariants. +*/ static bool check_tree (struct interval_tree *tree) { eassert (tree != NULL); + eassert (tree->size >= 0); eassert ((tree->size == 0) == (tree->root == ITREE_NULL)); if (tree->root == ITREE_NULL) return true; - intmax_t size = 0; - recurse_check_tree (tree->root, tree->otick, 0, - PTRDIFF_MIN, PTRDIFF_MAX, &size); + /* Limit the traversal depth to what 'interval_tree_max_height' + returns. Later, verify that this is enough height to traverse + the complete tree. */ + const int max_height = interval_tree_max_height (tree); + eassert (max_height >= 0); + eassert (max_height <= 120); + + /* NOTE: if this check is too expensive an easy fix is to reduce + max_height to for large trees, then relax the assertion on + result.complete. Assertions in check_subtree will still be made + at the bottom of the tree (where they are probably most + interesting), but some will be skipped closer to the root. */ + + struct interval_node *node = tree->root; + struct check_subtree_result result + = check_subtree (node, tree->otick, max_height, node->offset, + PTRDIFF_MIN, PTRDIFF_MAX); + eassert (result.complete); + eassert (result.size == tree->size); + + /* The only way this function fails is eassert(). */ return true; } @@ -1145,10 +1241,10 @@ interval_tree_inherit_offset (uintmax_t otick, struct interval_node *node) } /* Offsets can be inherited from dirty nodes (with out of date - otick) during insert and remove. Offsets aren't inherited - downward from the root for these operations so rotations are - performed on potentially "dirty" nodes, where we only make sure - the *local* offsets are all zero. */ + otick) during removal, since we do not travel down from the root + in that case. In this case rotations are performed on + potentially "dirty" nodes, where we only need to make sure the + *local* offsets are zero. */ if (node->offset) { -- 2.35.1 --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0002-Check-red-black-invariants-in-most-places.patch >From 51a8e375ebea2e6e05eed623bddfb323b8e408f0 Mon Sep 17 00:00:00 2001 From: Matt Armstrong Date: Mon, 10 Oct 2022 09:07:42 -0700 Subject: [PATCH 2/4] Check red-black invariants in most places Stefan recently disabled this but I happened to want it back soon after. * src/itree.c (check_subtree): new arg: allow_red_red (check_tree_common): renamed from check_tree, pass allow_red_red through. (check_tree): new function, pass allow_red_red=false (interval_tree_insert): check_tree -> check_tree_common with allow_red_red=true. --- src/itree.c | 80 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/src/itree.c b/src/itree.c index aa8a5f7f3b..7ac400398b 100644 --- a/src/itree.c +++ b/src/itree.c @@ -222,7 +222,8 @@ itree_init (void) }; static struct check_subtree_result -check_subtree (struct interval_node *node, uintmax_t tree_otick, +check_subtree (struct interval_node *node, + bool check_red_black_invariants, uintmax_t tree_otick, int max_depth, ptrdiff_t offset, ptrdiff_t min_begin, ptrdiff_t max_begin) { @@ -251,23 +252,9 @@ check_subtree (struct interval_node *node, uintmax_t tree_otick, eassert (node->left == ITREE_NULL || node->left->parent == node); eassert (node->right == ITREE_NULL || node->right->parent == node); - /* We don't normally check the RB invariants here (neither the - absence of red+red nor the equal-black-depth), so that we can use - this check even while the tree is temporarily breaking some of - those invarints. You can enable them if you want. */ - if (false) - { - /* If a node is red then both of its children are black. Red - nodes cannot have red parents. */ - if (node->red) - { - eassert (node->left == ITREE_NULL - || node->left->red == false); - eassert (node->right == ITREE_NULL - || node->right->red == false); - eassert (node->parent == ITREE_NULL || !node->parent->red); - } - } + /* No red nodes have red parents. */ + if (check_red_black_invariants && node->parent != ITREE_NULL) + eassert (!node->red || !node->parent->red); /* Validate otick. A node's otick must be <= to the tree's otick and <= to its parent's otick. @@ -292,11 +279,13 @@ check_subtree (struct interval_node *node, uintmax_t tree_otick, eassert (end <= limit); struct check_subtree_result left_result - = check_subtree (node->left, tree_otick, max_depth - 1, offset, - min_begin, begin); + = check_subtree (node->left, check_red_black_invariants, + tree_otick, max_depth - 1, offset, min_begin, + begin); struct check_subtree_result right_result - = check_subtree (node->right, tree_otick, max_depth - 1, offset, - begin, max_begin); + = check_subtree (node->right, check_red_black_invariants, + tree_otick, max_depth - 1, offset, begin, + max_begin); eassert (left_result.limit <= limit); eassert (right_result.limit <= limit); @@ -304,24 +293,29 @@ check_subtree (struct interval_node *node, uintmax_t tree_otick, result.complete = left_result.complete && right_result.complete; if (result.complete) { - /* Every path from a node to a descendent leaf contains the same - number of black nodes. Often said this way: all nodes have - the same "black height". */ - eassert (left_result.black_height == right_result.black_height); - result.black_height - = (node->red ? 0 : 1) + left_result.black_height; - result.size = 1 + left_result.size + right_result.size; result.limit = max (end, max (left_result.limit, right_result.limit)); eassert (limit == result.limit); + + if (check_red_black_invariants) + { + /* Every path from a node to a descendent leaf contains the + same number of black nodes. Often said this way: all + nodes have the same "black height". */ + eassert (left_result.black_height + == right_result.black_height); + result.black_height + = (node->red ? 0 : 1) + left_result.black_height; + } } return result; } -/* Validate invariants for TREE. +/* Validate invariants for TREE. If CHECK_RED_BLACK_INVARIANTS, red + nodes with red children are considered invalid. This runs in constant time when ENABLE_OVERLAY_CHECKING is 0 (i.e. Emacs is not configured with @@ -330,7 +324,8 @@ check_subtree (struct interval_node *node, uintmax_t tree_otick, entire tree and validates all invariants. */ static bool -check_tree (struct interval_tree *tree) +check_tree_common (struct interval_tree *tree, + bool check_red_black_invariants) { eassert (tree != NULL); eassert (tree->size >= 0); @@ -353,8 +348,9 @@ check_tree (struct interval_tree *tree) struct interval_node *node = tree->root; struct check_subtree_result result - = check_subtree (node, tree->otick, max_height, node->offset, - PTRDIFF_MIN, PTRDIFF_MAX); + = check_subtree (node, check_red_black_invariants, tree->otick, + max_height, node->offset, PTRDIFF_MIN, + PTRDIFF_MAX); eassert (result.complete); eassert (result.size == tree->size); @@ -362,6 +358,22 @@ check_tree (struct interval_tree *tree) return true; } +/* Check the tree with all invariant checks enabled. */ +static bool +check_tree (struct interval_tree *tree) +{ + return check_tree_common (tree, true); +} + +/* Check the tree with all invariant checks enabled, except for the + red-black tree invariants. Useful for asserting the other + invariants while inserting or removing. */ +static bool +check_tree_no_rb (struct interval_tree *tree) +{ + return check_tree_common (tree, false); +} + /* +===================================================================================+ * | Stack * +===================================================================================+ */ @@ -626,7 +638,7 @@ interval_tree_insert (struct interval_tree *tree, struct interval_node *node) /* Fix/update the tree */ ++tree->size; - eassert (check_tree (tree)); + eassert (check_tree_no_rb (tree)); interval_tree_insert_fix (tree, node); } -- 2.35.1 --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0003-Simplify-itree_null-initialization.patch >From a154259bfacf7f1406794a952e80a8197b9a83fb Mon Sep 17 00:00:00 2001 From: Matt Armstrong Date: Mon, 10 Oct 2022 08:32:56 -0700 Subject: [PATCH 3/4] Simplify itree_null initialization * src/itree.c (null_is_sane): call eassert directly, check REAR_ADVANCE, FRONT_ADVANCE. Add FIXME that PARENT is still read/write. (itree_null): initialize statically (itree_init): remove initialization code, call eassert(null_is_sane()) (check_tree_common): call eassert (null_is_sane()) --- src/itree.c | 52 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/src/itree.c b/src/itree.c index 7ac400398b..d9f9ec8cd6 100644 --- a/src/itree.c +++ b/src/itree.c @@ -145,19 +145,42 @@ Copyright (C) 2017-2022 Free Software Foundation, Inc. static void interval_tree_insert (struct interval_tree *, struct interval_node *); /* The sentinel node, the null node. */ -struct interval_node itree_null; +struct interval_node itree_null = { + .parent = &itree_null, + .left = &itree_null, + .right = &itree_null, + .begin = PTRDIFF_MIN, + .end = PTRDIFF_MIN, + .limit = PTRDIFF_MIN, /* => max(x, null.limit) = x */ + .offset = 0, + .otick = 0, + .red = false, + .rear_advance = false, + .front_advance = false, +}; static bool null_is_sane (void) { - /* The sentinel node has most of its fields read-only, except for `parent`, - `left`, `right` which are write only. */ - return itree_null.red == false - && itree_null.otick == 0 - && itree_null.offset == 0 - && itree_null.begin == PTRDIFF_MIN - && itree_null.end == PTRDIFF_MIN - && itree_null.limit == PTRDIFF_MIN; + /* The sentinel node has most of its fields read-only. + + FIXME: PARENT is still read/write. It is written to + ininterval_tree_transplant, and later read. --matt + */ + /* eassert (itree_null.parent == &itree_null); */ + eassert (itree_null.left == &itree_null); + eassert (itree_null.right == &itree_null); + eassert (itree_null.begin == PTRDIFF_MIN); + eassert (itree_null.end == PTRDIFF_MIN); + eassert (itree_null.limit == PTRDIFF_MIN); + eassert (itree_null.offset == 0); + eassert (itree_null.otick == 0); + eassert (itree_null.red == false); + eassert (itree_null.rear_advance == false); + eassert (itree_null.front_advance == false); + + /* if we get this far things must be good */ + return true; } /* +------------------------------------------------------------------------------------+ */ @@ -195,13 +218,8 @@ null_is_sane (void) static void itree_init (void) { - struct interval_node *null = ITREE_NULL; - null->left = null->right = null->parent = null; - null->offset = null->otick = 0; - null->begin = PTRDIFF_MIN; - null->end = PTRDIFF_MIN; - null->limit = PTRDIFF_MIN; /* => max(x, null.limit) = x */ - null->red = false; + eassert (null_is_sane ()); + iter = interval_generator_create (NULL); } @@ -327,6 +345,8 @@ check_subtree (struct interval_node *node, check_tree_common (struct interval_tree *tree, bool check_red_black_invariants) { + eassert (null_is_sane ()); + eassert (tree != NULL); eassert (tree->size >= 0); eassert ((tree->size == 0) == (tree->root == ITREE_NULL)); -- 2.35.1 --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0004-Stop-reading-and-writing-the-itree_null.parent-field.patch >From da0387f0fe79f577fae6d5453c758f600e1ae495 Mon Sep 17 00:00:00 2001 From: Matt Armstrong Date: Mon, 10 Oct 2022 10:45:05 -0700 Subject: [PATCH 4/4] Stop reading and writing the itree_null.parent field entirely. With this change all fields in the itree_null sentinel are read only. This makes accessing itree_null thread safe in an obvious way. Because it took two commits from two peole to get this right, I think we can call this design fragile and difficult to reason about. Another benefit of this commit is as preparation for removing sentinel node completely, and just using NULL. * src/itree.c (itree_null): Statically initialize itree_null.parent to NULL. It is never accessed. (null_is_sane): Assert parent == NULL. (interval_tree_remove_fix): Remove unecessary assignments to parent from node->parent. These were the last places itree_null.parent were read. (interval_tree_remove): Avoid an assignment to itree_null.parent through min->right->parent. (interval_tree_transplant): Avoid an assignment to itree_null.parent through source->parent. --- src/itree.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/itree.c b/src/itree.c index d9f9ec8cd6..9c5d8ce142 100644 --- a/src/itree.c +++ b/src/itree.c @@ -146,7 +146,7 @@ Copyright (C) 2017-2022 Free Software Foundation, Inc. /* The sentinel node, the null node. */ struct interval_node itree_null = { - .parent = &itree_null, + .parent = NULL, /* never accessed */ .left = &itree_null, .right = &itree_null, .begin = PTRDIFF_MIN, @@ -162,12 +162,8 @@ Copyright (C) 2017-2022 Free Software Foundation, Inc. static bool null_is_sane (void) { - /* The sentinel node has most of its fields read-only. - - FIXME: PARENT is still read/write. It is written to - ininterval_tree_transplant, and later read. --matt - */ - /* eassert (itree_null.parent == &itree_null); */ + /* All sentinel node fields are read-only. */ + eassert (itree_null.parent == NULL); eassert (itree_null.left == &itree_null); eassert (itree_null.right == &itree_null); eassert (itree_null.begin == PTRDIFF_MIN); @@ -742,7 +738,6 @@ interval_tree_remove_fix (struct interval_tree *tree, other->red = false; parent->red = true; interval_tree_rotate_left (tree, parent); - parent = node->parent; other = parent->right; } @@ -761,7 +756,6 @@ interval_tree_remove_fix (struct interval_tree *tree, other->left->red = false; other->red = true; interval_tree_rotate_right (tree, other); - parent = node->parent; other = parent->right; } other->red = parent->red; /* 4.a */ @@ -781,7 +775,6 @@ interval_tree_remove_fix (struct interval_tree *tree, other->red = false; parent->red = true; interval_tree_rotate_right (tree, parent); - parent = node->parent; other = parent->left; } @@ -800,7 +793,6 @@ interval_tree_remove_fix (struct interval_tree *tree, other->right->red = false; other->red = true; interval_tree_rotate_left (tree, other); - parent = node->parent; other = parent->left; } @@ -881,7 +873,8 @@ interval_tree_remove (struct interval_tree *tree, struct interval_node *node) interval_tree_transplant (tree, min, node); /* We set min->right->parent after `interval_tree_transplant` so that calls to `itree_total_offset` don't get stuck in an inf-loop. */ - min->right->parent = min; + if (min->right != ITREE_NULL) + min->right->parent = min; interval_tree_update_limit (min); /* This call "belongs" with the first `interval_tree_transplant` (of `min_right`, done earlier in the `if`) but we prefer to do it @@ -1508,7 +1501,8 @@ interval_tree_transplant (struct interval_tree *tree, struct interval_node *sour else dest->parent->right = source; - source->parent = dest->parent; + if (source != ITREE_NULL) + source->parent = dest->parent; } -- 2.35.1 --=-=-=--