unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Matt Armstrong <matt@rfc20.org>
To: Stefan Monnier <monnier@iro.umontreal.ca>
Cc: emacs-devel@gnu.org
Subject: Re: noverlay branch
Date: Sun, 16 Oct 2022 14:53:00 -0700	[thread overview]
Message-ID: <878rlf77wj.fsf@rfc20.org> (raw)
In-Reply-To: <jwv7d14tlxg.fsf-monnier+emacs@gnu.org>

[-- Attachment #1: Type: text/plain, Size: 501 bytes --]

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> Thanks Stefan, FWIW I don't see the merges on feature/noverlay yet.
>> Have you pushed?
>
> Hmm... I think so, yes.  `git log` tells me:
>
>     commit 65a7b5a802a15daa6274403fef822ec3c9b95469 (HEAD -> noverlay, origin/feature/noverlay)
>     Author: Matt Armstrong <matt@rfc20.org>
>     Date:   Tue Oct 11 20:19:16 2022 -0700
>
>         ; * src/itree.c (check_subtree): fix logical error in eassert

Ah, but one patch was missed (the biggie):


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Delete-the-itree_null-sentinel-node-use-NULL-everywh.patch --]
[-- Type: text/x-diff, Size: 19601 bytes --]

From 82125d32c5170bcc6ecf480be1f8265524f0aa4e Mon Sep 17 00:00:00 2001
From: Matt Armstrong <matt@rfc20.org>
Date: Wed, 12 Oct 2022 10:06:03 -0700
Subject: [PATCH] Delete the itree_null sentinel node, use NULL everywhere.

This effort caught a few (already commited) places that were
dereferencing through ITREE_NULL in a confusing way.  It makes some
functions have to check for NULL in more places, but in my experinece
this is worth it from a code clarity point of view.

In doing this I rewrote `interval_tree_remove` completely.  There
there was one final bug in that function that I simply could not find
when I #define'd ITREE_NULL to NULL.  I couldn't easily understand
that function, so instead I rewrote it completely with a focus on code
clarity.  Bug went away.

I have left the ITREE_NULL #define in code, to reduce code review
noise.  It is easily removed later, mechanically.

* src/itree.h: #define ITREE_NULL to NULL and leave a FIXME.
* src/itree.c (itree_null): Delete the itree_null static variable.
(null_is_sane): Delete (and all callers).
(interval_tree_insert): Insert the first node as black straight away.
(itree_newlimit): Handle NULL children.
(interval_tree_remove_fix): ditto.
(interval_tree_insert_fix): ditto.
(interval_tree_remove): Rewrite for clarity.
(null_safe_is_red): New function.
(null_safe_is_black): New function.
(interval_tree_replace_child): Renamed from interval_tree_transplant.
(interval_tree_transplant): New function that something I think is
more like a full transplantation.  (names are hard)
---
 src/itree.c | 278 ++++++++++++++++++++++++++--------------------------
 src/itree.h |   5 +-
 2 files changed, 140 insertions(+), 143 deletions(-)

diff --git a/src/itree.c b/src/itree.c
index 1728a8ab3a..6e2dfc6567 100644
--- a/src/itree.c
+++ b/src/itree.c
@@ -140,44 +140,17 @@ Copyright (C) 2017-2022  Free Software Foundation, Inc.
 static void interval_tree_rotate_left (struct interval_tree *, struct interval_node *);
 static void interval_tree_rotate_right (struct interval_tree *, struct interval_node *);
 static void interval_tree_insert_fix (struct interval_tree *, struct interval_node *);
-static void interval_tree_transplant (struct interval_tree *, struct interval_node *, struct interval_node *);
-static struct interval_generator* interval_generator_create (struct interval_tree *);
+static void interval_tree_replace_child (struct interval_tree *,
+					 struct interval_node *,
+					 struct interval_node *);
+static void interval_tree_transplant (struct interval_tree *tree,
+                                      struct interval_node *source,
+                                      struct interval_node *dest);
+static struct interval_generator *
+interval_generator_create (struct interval_tree *);
 static void interval_tree_insert (struct interval_tree *, struct interval_node *);
-
-/* The sentinel node, the null node.  */
-struct interval_node itree_null = {
-  .parent = NULL, /* never accessed */
-  .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)
-{
-  /* 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);
-  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;
-}
+static bool null_safe_is_red (struct interval_node *node);
+static bool null_safe_is_black (struct interval_node *node);
 
 /* +------------------------------------------------------------------------------------+ */
 
@@ -214,8 +187,6 @@ null_is_sane (void)
 static void
 itree_init (void)
 {
-  eassert (null_is_sane ());
-
   iter = interval_generator_create (NULL);
 }
 
@@ -299,8 +270,6 @@ check_subtree (struct interval_node *node,
 check_tree (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));
@@ -549,7 +518,7 @@ interval_tree_insert (struct interval_tree *tree, struct interval_node *node)
   struct interval_node *child = tree->root;
   uintmax_t otick = tree->otick;
   /* It's the responsability of the caller to set `otick` on the node,
-     to "confirm" that the begin/end fields are uptodate.  */
+     to "confirm" that the begin/end fields are up to date.  */
   eassert (node->otick == otick);
 
   /* Find the insertion point, accumulate node's offset and update
@@ -577,7 +546,7 @@ interval_tree_insert (struct interval_tree *tree, struct interval_node *node)
   node->parent = parent;
   node->left = ITREE_NULL;
   node->right = ITREE_NULL;
-  node->red = true;
+  node->red = node != tree->root;
   node->offset = 0;
   node->limit = node->end;
   eassert (node->parent == ITREE_NULL || node->parent->otick >= node->otick);
@@ -620,8 +589,12 @@ itree_newlimit (struct interval_node *node)
 {
   eassert (node != ITREE_NULL);
   return max (node->end,
-              max (node->left->limit + node->left->offset,
-                   node->right->limit + node->right->offset));
+              max (node->left == ITREE_NULL
+                     ? PTRDIFF_MIN
+                     : node->left->limit + node->left->offset,
+                   node->right == ITREE_NULL
+                     ? PTRDIFF_MIN
+                     : node->right->limit + node->right->offset));
 }
 
 static bool
@@ -653,17 +626,20 @@ interval_tree_remove_fix (struct interval_tree *tree,
                           struct interval_node *node,
                           struct interval_node *parent)
 {
+  if (parent == ITREE_NULL)
+    eassert (node == tree->root);
+  else
   eassert (node == ITREE_NULL || node->parent == parent);
-  eassert (parent == ITREE_NULL
-           || node == parent->left || node == parent->right);
 
-  while (parent != ITREE_NULL && !node->red)
+  while (parent != ITREE_NULL && null_safe_is_black (node))
     {
+      eassert (node == parent->left || node == parent->right);
+
       if (node == parent->left)
 	{
 	  struct interval_node *other = parent->right;
 
-	  if (other->red) /* case 1.a */
+          if (null_safe_is_red (other)) /* case 1.a */
 	    {
 	      other->red = false;
 	      parent->red = true;
@@ -671,8 +647,8 @@ interval_tree_remove_fix (struct interval_tree *tree,
 	      other = parent->right;
             }
 
-	  if (!other->left->red /* 2.a */
-              && !other->right->red)
+          if (null_safe_is_black (other->left) /* 2.a */
+              && null_safe_is_black (other->right))
 	    {
 	      other->red = true;
 	      node = parent;
@@ -681,7 +657,7 @@ interval_tree_remove_fix (struct interval_tree *tree,
             }
 	  else
 	    {
-	      if (!other->right->red) /* 3.a */
+              if (null_safe_is_black (other->right)) /* 3.a */
 		{
 		  other->left->red = false;
 		  other->red = true;
@@ -700,7 +676,7 @@ interval_tree_remove_fix (struct interval_tree *tree,
 	{
 	  struct interval_node *other = parent->left;
 
-	  if (other->red) /* 1.b */
+          if (null_safe_is_red (other)) /* 1.b */
 	    {
 	      other->red = false;
 	      parent->red = true;
@@ -708,8 +684,8 @@ interval_tree_remove_fix (struct interval_tree *tree,
 	      other = parent->left;
             }
 
-	  if (!other->right->red /* 2.b */
-              && !other->left->red)
+          if (null_safe_is_black (other->right) /* 2.b */
+              && null_safe_is_black (other->left))
 	    {
 	      other->red = true;
 	      node = parent;
@@ -718,7 +694,7 @@ interval_tree_remove_fix (struct interval_tree *tree,
             }
 	  else
 	    {
-	      if (!other->left->red) /* 3.b */
+              if (null_safe_is_black (other->left)) /* 3.b */
 		{
 		  other->right->red = false;
 		  other->red = true;
@@ -736,6 +712,7 @@ interval_tree_remove_fix (struct interval_tree *tree,
         }
     }
 
+  if (node != ITREE_NULL)
   node->red = false;
 }
 
@@ -747,86 +724,70 @@ interval_tree_remove (struct interval_tree *tree, struct interval_node *node)
   eassert (interval_tree_contains (tree, node));
   eassert (check_tree (tree, true)); /* FIXME: Too expensive.  */
 
-  /* `broken`, if non-NULL, holds a node that's being moved up to where a black
-     node used to be, which may thus require further fixups in its parents
-     (done in `interval_tree_remove_fix`).  */
-  struct interval_node *broken = NULL;
-  /* `broken` may be null but `interval_tree_remove_fix` still
-     needs to know its "parent".
-     Cormen et al.'s Introduction to Algorithms uses a trick where
-     they rely on the null sentinel node's `parent` field to hold
-     the right value.  While this works, it breaks the rule that
-     the `parent` field is write-only making correctness much more tricky
-     and introducing a dependency on a global state (which is incompatible
-     with concurrency among other things), so instead we keep track of
-     `broken`'s parent manually.  */
-  struct interval_node *broken_parent = NULL;
-
+  /* Find `splice`, the leaf node to splice out of the tree.  When
+     `node` has at most one child this is `node` itself.  Otherwise,
+     it is the in order successor of `node`.  */
   interval_tree_inherit_offset (tree->otick, node);
-  if (node->left == ITREE_NULL || node->right == ITREE_NULL)
-    {
-      struct interval_node *subst
-	= node->right == ITREE_NULL ? node->left : node->right;
-      if (!node->red)
-        {
-          broken = subst;
-          broken_parent = node->parent; /* The future parent.  */
-        }
-      interval_tree_transplant (tree, subst, node);
-    }
-  else
+  struct interval_node *splice
+    = (node->left == ITREE_NULL || node->right == ITREE_NULL)
+        ? node
+        : interval_tree_subtree_min (tree->otick, node->right);
+
+  /* Find `subtree`, the only child of `splice` (may be NULL).  Note:
+     `subtree` will not be modified other than changing its parent to
+     `splice`.  */
+  eassert (splice->left == ITREE_NULL || splice->right == ITREE_NULL);
+  struct interval_node *subtree
+    = (splice->left != ITREE_NULL) ? splice->left : splice->right;
+
+  /* Save a pointer to the parent of where `subtree` will eventually
+     be in `subtree_parent`.  */
+  struct interval_node *subtree_parent
+    = (splice->parent != node) ? splice->parent : splice;
+
+  /* If `splice` is black removing it may violate Red-Black
+     invariants, so note this for later.  */
+
+  /* Replace `splice` with `subtree` under subtree's parent.  If
+     `splice` is black, this creates a red-red violation, so remember
+     this now as the field can be overwritten when splice is
+     transplanted below.  */
+  interval_tree_replace_child (tree, subtree, splice);
+  bool removed_black = !splice->red;
+
+  /* Replace `node` with `splice` in the tree and propagate limit
+     upwards, if necessary.  Note: Limit propagation can stabilize at
+     any point, so we must call from bottom to top for every node that
+     has a new child.  */
+  if (splice != node)
     {
-      struct interval_node *min
-        = interval_tree_subtree_min (tree->otick, node->right);
-      struct interval_node *min_right = min->right;
-      struct interval_node *min_parent = min->parent;
-
-      if (!min->red)
-        broken = min_right;
-      eassert (min != ITREE_NULL);
-      /* `min` should not have any offsets any more so we can move nodes
-         underneath it without risking changing their begin/end.  */
-      eassert (min->offset == 0);
-      if (min->parent == node)
-        broken_parent = min; /* The future parent.  */
-      else
-        {
-          interval_tree_transplant (tree, min_right, min);
-          broken_parent = min->parent; /* The parent.  */
-          min->right = node->right;
-        }
-      min->left = node->left;
-      min->left->parent = min;
-      min->red = node->red;
-      /* FIXME: At this point node->right->parent = min but node->right
-         is a parent of `min` so total_offsets gets stuck in an inf-loop!  */
-      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.  */
-      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
-         here ("late") because otherwise it would sometimes update part of
-         the tree with values that would be invalidated by the second
-         `interval_tree_transplant`.  */
-      interval_tree_propagate_limit (min_parent);
+      interval_tree_transplant (tree, splice, node);
+      interval_tree_propagate_limit (subtree_parent);
+      if (splice != subtree_parent)
+        interval_tree_propagate_limit (splice);
     }
-  interval_tree_propagate_limit (node->parent);
-  --tree->size;
+  interval_tree_propagate_limit (splice->parent);
 
-  if (broken)
-    {
-      eassert (check_tree (tree, false)); /* FIXME: Too expensive.  */
-      interval_tree_remove_fix (tree, broken, broken_parent);
-    }
+  --tree->size;
 
-  node->right = node->left = node->parent = NULL;
+  /* Fix any black height violation caused by removing a black
+     node.  */
+  if (removed_black)
+    interval_tree_remove_fix (tree, subtree, subtree_parent);
 
   eassert ((tree->size == 0) == (tree->root == ITREE_NULL));
   eassert (check_tree (tree, true)); /* FIXME: Too expensive.  */
 
+  /* Clear fields related to the tree for sanity while debugging.  */
+  node->red = false;
+  node->right = node->left = node->parent = NULL;
+  node->limit = 0;
+
+  /* Must be clean (all offsets applied).  Also, some callers rely on
+     node's otick being the tree's otick.  */
+  eassert (node->otick == tree->otick);
+  eassert (node->offset == 0);
+
   return node;
 }
 
@@ -860,7 +821,6 @@ interval_tree_iter_start (struct interval_tree *tree,
                           enum interval_tree_order order,
 			  const char* file, int line)
 {
-  eassert (null_is_sane ());
   /* struct interval_generator *iter = tree->iter; */
   if (iter->running)
     {
@@ -1171,6 +1131,18 @@ interval_generator_narrow (struct interval_generator *g,
  * | Internal Functions
  * +===================================================================================+ */
 
+static bool
+null_safe_is_red (struct interval_node *node)
+{
+  return node != ITREE_NULL && node->red;
+}
+
+static bool
+null_safe_is_black (struct interval_node *node)
+{
+  return node == ITREE_NULL || !node->red; /* NULL nodes are black */
+}
+
 /* Update NODE's limit attribute according to its children. */
 
 static void
@@ -1224,9 +1196,6 @@ interval_tree_inherit_offset (uintmax_t otick, struct interval_node *node)
 
 /* Update limit of NODE and its ancestors.  Stop when it becomes
    stable, i.e. new_limit = old_limit.
-
-   NODE may also be the null node, in which case its parent is
-   used. (This feature is due to the RB algorithm.)
 */
 
 static void
@@ -1331,7 +1300,9 @@ interval_tree_rotate_right (struct interval_tree *tree, struct interval_node *no
 static void
 interval_tree_insert_fix (struct interval_tree *tree, struct interval_node *node)
 {
-  while (node->parent->red)
+  eassert (tree->root->red == false);
+
+  while (null_safe_is_red (node->parent))
     {
       /* NODE is red and its parent is red.  This is a violation of
 	 red-black tree property #3.  */
@@ -1343,7 +1314,7 @@ interval_tree_insert_fix (struct interval_tree *tree, struct interval_node *node
 	     our "uncle".  */
 	  struct interval_node *uncle = node->parent->parent->right;
 
-	  if (uncle->red) /* case 1.a */
+          if (null_safe_is_red (uncle)) /* case 1.a */
 	    {
 	      /* Uncle and parent are red but should be black because
 		 NODE is red.  Change the colors accordingly and
@@ -1373,7 +1344,7 @@ interval_tree_insert_fix (struct interval_tree *tree, struct interval_node *node
 	  /* This is the symmetrical case of above.  */
 	  struct interval_node *uncle = node->parent->parent->left;
 
-	  if (uncle->red) /* case 1.b */
+          if (null_safe_is_red (uncle)) /* case 1.b */
 	    {
 	      node->parent->red = false;
 	      uncle->red = false;
@@ -1415,15 +1386,20 @@ itree_total_offset (struct interval_node *node)
   return offset;
 }
 
-/* Link node SOURCE in DEST's place.
-   It's the caller's responsability to refresh the `limit`s
-   of DEST->parents afterwards.  */
+/* Replace DEST with SOURCE as a child of DEST's parent.  Adjusts
+   *only* the parent linkage of SOURCE and either the parent's child
+   link the tree root.
+
+   Warning: DEST is left unmodified.  SOURCE's child links are
+   unchanged.  Caller is responsible for recalculation of `limit`.
+   Requires both nodes to be using the same effective `offset`.  */
 
 static void
-interval_tree_transplant (struct interval_tree *tree, struct interval_node *source,
-                          struct interval_node *dest)
+interval_tree_replace_child (struct interval_tree *tree,
+                             struct interval_node *source,
+                             struct interval_node *dest)
 {
-  eassert (tree && source && dest && dest != ITREE_NULL);
+  eassert (tree && dest != ITREE_NULL);
   eassert (source == ITREE_NULL
            || itree_total_offset (source) == itree_total_offset (dest));
 
@@ -1438,6 +1414,28 @@ interval_tree_transplant (struct interval_tree *tree, struct interval_node *sour
     source->parent = dest->parent;
 }
 
+/* Replace DEST with SOURCE in the tree.  Copies the following fields
+   from DEST to SOURCE: red, parent, left, right.  Also updates
+   parent, left and right in surrounding nodes to point to SOURCE.
+
+   Warning: DEST is left unmodified.  Caller is responsible for
+   recalculation of `limit`.  Requires both nodes to be using the same
+   effective `offset`. */
+static void
+interval_tree_transplant (struct interval_tree *tree,
+                          struct interval_node *source,
+                          struct interval_node *dest)
+{
+  interval_tree_replace_child (tree, source, dest);
+  source->left = dest->left;
+  if (source->left != ITREE_NULL)
+    source->left->parent = source;
+  source->right = dest->right;
+  if (source->right != ITREE_NULL)
+    source->right->parent = source;
+  source->red = dest->red;
+}
+
 \f
 /* +===================================================================================+
  * | Debugging
diff --git a/src/itree.h b/src/itree.h
index 5733ec1530..0e2e7d1f81 100644
--- a/src/itree.h
+++ b/src/itree.h
@@ -95,9 +95,8 @@ #define ITREE_H
   bool_bf front_advance : 1;    /* Same as for marker and overlays.  */
 };
 
-/* The sentinel node, the null node.  */
-extern struct interval_node itree_null;
-#define ITREE_NULL (&itree_null)
+/* FIXME: replace ITREE_NULL -> NULL everywhere  */
+#define ITREE_NULL NULL
 
 struct interval_tree
 {
-- 
2.35.1


  reply	other threads:[~2022-10-16 21:53 UTC|newest]

Thread overview: 71+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-09-25 22:38 noverlay branch Stefan Monnier
2022-09-25 22:50 ` Lars Ingebrigtsen
2022-09-25 22:56   ` Stefan Monnier
2022-09-26  2:52 ` Ihor Radchenko
2022-09-26  3:17   ` Stefan Monnier
2022-09-26  6:11   ` Eli Zaretskii
2022-09-26 13:08     ` Ihor Radchenko
2022-09-26 15:59       ` Eli Zaretskii
     [not found]         ` <87v8ovdosz.fsf@localhost>
2022-10-08  6:57           ` Eli Zaretskii
2022-10-09  3:25             ` Matt Armstrong
2022-10-09  4:30               ` Eli Zaretskii
2022-10-09  3:23           ` Matt Armstrong
2022-10-09  3:47           ` Stefan Monnier
2022-10-13 12:09             ` Ihor Radchenko
2022-09-29 18:12       ` Stefan Monnier
2022-09-27  5:12 ` Matt Armstrong
2022-09-27  6:53   ` Eli Zaretskii
2022-09-27 17:31     ` Matt Armstrong
2022-09-27 18:45       ` Stefan Monnier
2022-09-28 23:09   ` Stefan Monnier
2022-09-29 14:54     ` Gerd Möllmann
2022-09-29 21:36       ` Stefan Monnier
2022-09-30  5:20         ` Gerd Möllmann
2022-10-06  4:47         ` Matt Armstrong
2022-10-06  5:43           ` Gerd Möllmann
2022-10-07  4:11             ` Matt Armstrong
2022-10-07  4:34               ` Gerd Möllmann
2022-10-07 13:33                 ` Stefan Monnier
2022-10-07 14:29                   ` Gerd Möllmann
2022-10-07 14:51                     ` Stefan Monnier
2022-10-07 15:12                       ` Gerd Möllmann
2022-10-07 17:12                         ` Stefan Monnier
2022-10-07 14:56                   ` Stefan Monnier
2022-10-07 15:59                   ` Matt Armstrong
2022-10-07 15:34                 ` Matt Armstrong
2022-10-06 12:08           ` Stefan Monnier
2022-09-27  8:39 ` Gerd Möllmann
2022-09-27  9:38   ` Eli Zaretskii
2022-10-06 20:41 ` Matt Armstrong
2022-10-07 16:51 ` Matt Armstrong
2022-10-07 18:28   ` Stefan Monnier
2022-10-08 23:33     ` Matt Armstrong
2022-10-09  3:44       ` Matt Armstrong
2022-10-09  4:12       ` Stefan Monnier
2022-10-09 15:34         ` Stefan Monnier
2022-10-10  2:57           ` Matt Armstrong
2022-10-10  6:24             ` Eli Zaretskii
2022-10-10 16:26               ` Matt Armstrong
2022-10-10 14:44             ` Stefan Monnier
2022-10-11  3:46               ` Matt Armstrong
2022-10-11  4:09                 ` Stefan Monnier
2022-10-11 18:02                   ` Matt Armstrong
2022-10-11 18:59                     ` Stefan Monnier
2022-10-12  5:18                       ` Matt Armstrong
2022-10-12 18:02                         ` Stefan Monnier
2022-10-12 22:26                           ` Matt Armstrong
2022-10-13  4:03                             ` Stefan Monnier
2022-10-09 23:47       ` Stefan Monnier
2022-10-10  0:05         ` Emanuel Berg
2022-10-10 16:27           ` Matt Armstrong
2022-10-10 16:33         ` Matt Armstrong
2022-10-10 18:32           ` Matt Armstrong
2022-10-11 16:06             ` Stefan Monnier
2022-10-12 17:33               ` Matt Armstrong
2022-10-13  3:59                 ` Stefan Monnier
2022-10-16 21:53                   ` Matt Armstrong [this message]
2022-10-23  4:49 ` Matt Armstrong
2022-10-24  9:14   ` Stefan Kangas
2022-10-24 16:21     ` Matt Armstrong
2022-10-24 12:51   ` Eli Zaretskii
2022-10-25 20:57     ` Dmitry Gutov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=878rlf77wj.fsf@rfc20.org \
    --to=matt@rfc20.org \
    --cc=emacs-devel@gnu.org \
    --cc=monnier@iro.umontreal.ca \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).