diff options
| author | Stefan Monnier | 2022-11-17 17:00:22 -0500 |
|---|---|---|
| committer | Stefan Monnier | 2022-11-17 17:00:22 -0500 |
| commit | fb7f1864da4aa4c09756cfe47db6c56b4e87bd14 (patch) | |
| tree | 4ccd8ca7e29c5609fa437884c739106a4f1b2cc2 /src | |
| parent | 13003105a8edf746a8e8819122bd1bcdf7f9ecdd (diff) | |
| download | emacs-fb7f1864da4aa4c09756cfe47db6c56b4e87bd14.tar.gz emacs-fb7f1864da4aa4c09756cfe47db6c56b4e87bd14.zip | |
itree.c: Make the iterator reentrant (bug#59183)
Get rid of the global iterator object and instead allocate
a separate iterator for every loop. This still uses the "duplicate
iterator" code, including the old iterator which needs a stack,
make ITREE_FOREACH a bit more expensive than we'd like.
* src/itree.h (init_itree, forget_itree, itree_iterator_busy_p):
Delete declarations.
(itree_iterator_start): Add iterator arg and remove `line` and `file` args.
(struct itree_iterator): Move from `itree.c`. Remove `line` and
`file` fields.
(ITREE_FOREACH): Stack allocate an iterator object and pass it to
`itree_iterator_start`.
* src/itree.c (struct itree_iterator): Move to itree.h.
(iter): Delete global variable.
(itree_iterator_create, init_itree, forget_itree, itree_iterator_busy_p):
Delete functions.
(itree_contains): Adjust assertion.
(itree_iterator_finish): Deallocate the iterator's stack.
(itree_iterator_start): Take the (uninitialized) iterator as argument.
Allocate a fresh new stack. Remove `file` and `line` arguments.
Don't check `running` any more since the iterator is not expected to be
initialized at all.
* src/eval.c (signal_or_quit):
* src/alloc.c (garbage_collect): Don't check `itree_iterator_busy_p`
any more.
* src/emacs.c (main): No need to `init_itree` any more.
(Fdump_emacs): No need to `forget_itree` any more.
Diffstat (limited to 'src')
| -rw-r--r-- | src/alloc.c | 5 | ||||
| -rw-r--r-- | src/emacs.c | 3 | ||||
| -rw-r--r-- | src/eval.c | 1 | ||||
| -rw-r--r-- | src/itree.c | 91 | ||||
| -rw-r--r-- | src/itree.h | 46 |
5 files changed, 37 insertions, 109 deletions
diff --git a/src/alloc.c b/src/alloc.c index 6862cf916fb..b9d12dff7e0 100644 --- a/src/alloc.c +++ b/src/alloc.c | |||
| @@ -6279,11 +6279,6 @@ garbage_collect (void) | |||
| 6279 | image_prune_animation_caches (false); | 6279 | image_prune_animation_caches (false); |
| 6280 | #endif | 6280 | #endif |
| 6281 | 6281 | ||
| 6282 | /* ELisp code run by `gc-post-hook' could result in itree iteration, | ||
| 6283 | which must not happen while the itree is already busy. See | ||
| 6284 | bug#58639. */ | ||
| 6285 | eassert (!itree_iterator_busy_p ()); | ||
| 6286 | |||
| 6287 | if (!NILP (Vpost_gc_hook)) | 6282 | if (!NILP (Vpost_gc_hook)) |
| 6288 | { | 6283 | { |
| 6289 | specpdl_ref gc_count = inhibit_garbage_collection (); | 6284 | specpdl_ref gc_count = inhibit_garbage_collection (); |
diff --git a/src/emacs.c b/src/emacs.c index c4c8bfc82fb..85102acd28e 100644 --- a/src/emacs.c +++ b/src/emacs.c | |||
| @@ -1932,7 +1932,6 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem | |||
| 1932 | running_asynch_code = 0; | 1932 | running_asynch_code = 0; |
| 1933 | init_random (); | 1933 | init_random (); |
| 1934 | init_xfaces (); | 1934 | init_xfaces (); |
| 1935 | init_itree (); | ||
| 1936 | 1935 | ||
| 1937 | #if defined HAVE_JSON && !defined WINDOWSNT | 1936 | #if defined HAVE_JSON && !defined WINDOWSNT |
| 1938 | init_json (); | 1937 | init_json (); |
| @@ -3105,8 +3104,6 @@ You must run Emacs in batch mode in order to dump it. */) | |||
| 3105 | gflags.will_dump_with_unexec_ = false; | 3104 | gflags.will_dump_with_unexec_ = false; |
| 3106 | gflags.dumped_with_unexec_ = true; | 3105 | gflags.dumped_with_unexec_ = true; |
| 3107 | 3106 | ||
| 3108 | forget_itree (); | ||
| 3109 | |||
| 3110 | alloc_unexec_pre (); | 3107 | alloc_unexec_pre (); |
| 3111 | 3108 | ||
| 3112 | unexec (SSDATA (filename), !NILP (symfile) ? SSDATA (symfile) : 0); | 3109 | unexec (SSDATA (filename), !NILP (symfile) ? SSDATA (symfile) : 0); |
diff --git a/src/eval.c b/src/eval.c index ea238299488..78e606267f6 100644 --- a/src/eval.c +++ b/src/eval.c | |||
| @@ -1716,7 +1716,6 @@ signal_or_quit (Lisp_Object error_symbol, Lisp_Object data, bool keyboard_quit) | |||
| 1716 | Lisp_Object clause = Qnil; | 1716 | Lisp_Object clause = Qnil; |
| 1717 | struct handler *h; | 1717 | struct handler *h; |
| 1718 | 1718 | ||
| 1719 | eassert (!itree_iterator_busy_p ()); | ||
| 1720 | if (gc_in_progress || waiting_for_input) | 1719 | if (gc_in_progress || waiting_for_input) |
| 1721 | emacs_abort (); | 1720 | emacs_abort (); |
| 1722 | 1721 | ||
diff --git a/src/itree.c b/src/itree.c index 4f25a924b40..9e60071980d 100644 --- a/src/itree.c +++ b/src/itree.c | |||
| @@ -238,72 +238,12 @@ itree_stack_pop (struct itree_stack *stack) | |||
| 238 | 238 | ||
| 239 | /* +-----------------------------------------------------------------------+ */ | 239 | /* +-----------------------------------------------------------------------+ */ |
| 240 | 240 | ||
| 241 | /* State used when iterating interval. */ | ||
| 242 | struct itree_iterator | ||
| 243 | { | ||
| 244 | struct itree_stack *stack; | ||
| 245 | struct itree_node *node; | ||
| 246 | ptrdiff_t begin; | ||
| 247 | ptrdiff_t end; | ||
| 248 | |||
| 249 | /* A copy of the tree's `otick`. */ | ||
| 250 | uintmax_t otick; | ||
| 251 | enum itree_order order; | ||
| 252 | bool running; | ||
| 253 | const char *file; | ||
| 254 | int line; | ||
| 255 | }; | ||
| 256 | |||
| 257 | /* Ideally, every iteration would use its own `iter` object, so we could | ||
| 258 | have several iterations active at the same time. In practice, iterations | ||
| 259 | are limited by the fact we don't allow modifying the tree at the same | ||
| 260 | time, making the use of nested iterations quite rare anyway. | ||
| 261 | So we just use a single global iterator instead for now. */ | ||
| 262 | static struct itree_iterator *iter = NULL; | ||
| 263 | |||
| 264 | static int | 241 | static int |
| 265 | itree_max_height (const struct itree_tree *tree) | 242 | itree_max_height (const struct itree_tree *tree) |
| 266 | { | 243 | { |
| 267 | return 2 * log (tree->size + 1) / log (2) + 0.5; | 244 | return 2 * log (tree->size + 1) / log (2) + 0.5; |
| 268 | } | 245 | } |
| 269 | 246 | ||
| 270 | /* Allocate a new iterator for TREE. */ | ||
| 271 | |||
| 272 | static struct itree_iterator * | ||
| 273 | itree_iterator_create (struct itree_tree *tree) | ||
| 274 | { | ||
| 275 | struct itree_iterator *g = xmalloc (sizeof *g); | ||
| 276 | /* 19 here just avoids starting with a silly-small stack. | ||
| 277 | FIXME: Since this stack only needs to be about 2*max_depth | ||
| 278 | in the worst case, we could completely pre-allocate it to something | ||
| 279 | like word-bit-size * 2 and then never worry about growing it. */ | ||
| 280 | const int size = (tree ? itree_max_height (tree) : 19) + 1; | ||
| 281 | |||
| 282 | g->stack = itree_stack_create (size); | ||
| 283 | g->node = NULL; | ||
| 284 | g->running = false; | ||
| 285 | g->begin = 0; | ||
| 286 | g->end = 0; | ||
| 287 | g->file = NULL; | ||
| 288 | g->line = 0; | ||
| 289 | return g; | ||
| 290 | } | ||
| 291 | |||
| 292 | void | ||
| 293 | init_itree (void) | ||
| 294 | { | ||
| 295 | eassert (!iter); | ||
| 296 | iter = itree_iterator_create (NULL); | ||
| 297 | } | ||
| 298 | |||
| 299 | #ifdef HAVE_UNEXEC | ||
| 300 | void | ||
| 301 | forget_itree (void) | ||
| 302 | { | ||
| 303 | iter = NULL; | ||
| 304 | } | ||
| 305 | #endif | ||
| 306 | |||
| 307 | struct check_subtree_result | 247 | struct check_subtree_result |
| 308 | { | 248 | { |
| 309 | /* Node count of the tree. */ | 249 | /* Node count of the tree. */ |
| @@ -871,7 +811,7 @@ itree_node_set_region (struct itree_tree *tree, | |||
| 871 | static bool | 811 | static bool |
| 872 | itree_contains (struct itree_tree *tree, struct itree_node *node) | 812 | itree_contains (struct itree_tree *tree, struct itree_node *node) |
| 873 | { | 813 | { |
| 874 | eassert (iter && node); | 814 | eassert (node); |
| 875 | struct itree_node *other; | 815 | struct itree_node *other; |
| 876 | ITREE_FOREACH (other, tree, node->begin, PTRDIFF_MAX, ASCENDING) | 816 | ITREE_FOREACH (other, tree, node->begin, PTRDIFF_MAX, ASCENDING) |
| 877 | if (other == node) | 817 | if (other == node) |
| @@ -1304,18 +1244,13 @@ itree_delete_gap (struct itree_tree *tree, | |||
| 1304 | * | Iterator | 1244 | * | Iterator |
| 1305 | * +=======================================================================+ */ | 1245 | * +=======================================================================+ */ |
| 1306 | 1246 | ||
| 1307 | bool | ||
| 1308 | itree_iterator_busy_p (void) | ||
| 1309 | { | ||
| 1310 | return (iter && iter->running); | ||
| 1311 | } | ||
| 1312 | |||
| 1313 | /* Stop using the iterator. */ | 1247 | /* Stop using the iterator. */ |
| 1314 | 1248 | ||
| 1315 | void | 1249 | void |
| 1316 | itree_iterator_finish (struct itree_iterator *iter) | 1250 | itree_iterator_finish (struct itree_iterator *iter) |
| 1317 | { | 1251 | { |
| 1318 | eassert (iter && iter->running); | 1252 | eassert (iter && iter->running); |
| 1253 | itree_stack_destroy (iter->stack); | ||
| 1319 | iter->running = false; | 1254 | iter->running = false; |
| 1320 | } | 1255 | } |
| 1321 | 1256 | ||
| @@ -1504,18 +1439,18 @@ itree_iterator_first_node (struct itree_tree *tree, | |||
| 1504 | given ORDER. Only one iterator per tree can be running at any time. */ | 1439 | given ORDER. Only one iterator per tree can be running at any time. */ |
| 1505 | 1440 | ||
| 1506 | struct itree_iterator * | 1441 | struct itree_iterator * |
| 1507 | itree_iterator_start (struct itree_tree *tree, ptrdiff_t begin, | 1442 | itree_iterator_start (struct itree_iterator *iter, |
| 1508 | ptrdiff_t end, enum itree_order order, | 1443 | struct itree_tree *tree, |
| 1509 | const char *file, int line) | 1444 | ptrdiff_t begin, ptrdiff_t end, enum itree_order order) |
| 1510 | { | 1445 | { |
| 1511 | eassert (iter); | 1446 | eassert (iter); |
| 1512 | if (iter->running) | 1447 | /* eassert (!iter->running); */ |
| 1513 | { | 1448 | /* 19 here just avoids starting with a silly-small stack. |
| 1514 | fprintf (stderr, | 1449 | FIXME: Since this stack only needs to be about 2*max_depth |
| 1515 | "Detected nested iteration!\nOuter: %s:%d\nInner: %s:%d\n", | 1450 | in the worst case, we could completely pre-allocate it to something |
| 1516 | iter->file, iter->line, file, line); | 1451 | like word-bit-size * 2 and then never worry about growing it. */ |
| 1517 | emacs_abort (); | 1452 | const int size = (tree ? itree_max_height (tree) : 19) + 1; |
| 1518 | } | 1453 | iter->stack = itree_stack_create (size); |
| 1519 | uintmax_t otick= tree->otick; | 1454 | uintmax_t otick= tree->otick; |
| 1520 | iter->begin = begin; | 1455 | iter->begin = begin; |
| 1521 | iter->end = end; | 1456 | iter->end = end; |
| @@ -1524,8 +1459,6 @@ itree_iterator_start (struct itree_tree *tree, ptrdiff_t begin, | |||
| 1524 | itree_stack_clear (iter->stack); | 1459 | itree_stack_clear (iter->stack); |
| 1525 | if (begin <= end && tree->root != NULL) | 1460 | if (begin <= end && tree->root != NULL) |
| 1526 | itree_stack_push_flagged (iter->stack, tree->root, false); | 1461 | itree_stack_push_flagged (iter->stack, tree->root, false); |
| 1527 | iter->file = file; | ||
| 1528 | iter->line = line; | ||
| 1529 | iter->running = true; | 1462 | iter->running = true; |
| 1530 | /* itree_stack_ensure_space (iter->stack, | 1463 | /* itree_stack_ensure_space (iter->stack, |
| 1531 | 2 * itree_max_height (tree)); */ | 1464 | 2 * itree_max_height (tree)); */ |
diff --git a/src/itree.h b/src/itree.h index dc448233224..37cd423d34a 100644 --- a/src/itree.h +++ b/src/itree.h | |||
| @@ -107,10 +107,6 @@ enum itree_order | |||
| 107 | ITREE_POST_ORDER, | 107 | ITREE_POST_ORDER, |
| 108 | }; | 108 | }; |
| 109 | 109 | ||
| 110 | extern void init_itree (void); | ||
| 111 | #ifdef HAVE_UNEXEC | ||
| 112 | extern void forget_itree (void); | ||
| 113 | #endif | ||
| 114 | extern void itree_node_init (struct itree_node *, bool, bool, Lisp_Object); | 110 | extern void itree_node_init (struct itree_node *, bool, bool, Lisp_Object); |
| 115 | extern ptrdiff_t itree_node_begin (struct itree_tree *, struct itree_node *); | 111 | extern ptrdiff_t itree_node_begin (struct itree_tree *, struct itree_node *); |
| 116 | extern ptrdiff_t itree_node_end (struct itree_tree *, struct itree_node *); | 112 | extern ptrdiff_t itree_node_end (struct itree_tree *, struct itree_node *); |
| @@ -129,17 +125,28 @@ extern void itree_delete_gap (struct itree_tree *, ptrdiff_t, ptrdiff_t); | |||
| 129 | 125 | ||
| 130 | /* Iteration functions. Almost all code should use ITREE_FOREACH | 126 | /* Iteration functions. Almost all code should use ITREE_FOREACH |
| 131 | instead. */ | 127 | instead. */ |
| 132 | extern bool itree_iterator_busy_p (void); | 128 | extern struct itree_iterator *itree_iterator_start (struct itree_iterator *, |
| 133 | extern struct itree_iterator *itree_iterator_start (struct itree_tree *, | 129 | struct itree_tree *, |
| 134 | ptrdiff_t, | 130 | ptrdiff_t, |
| 135 | ptrdiff_t, | 131 | ptrdiff_t, |
| 136 | enum itree_order, | 132 | enum itree_order); |
| 137 | const char *, int); | ||
| 138 | extern void itree_iterator_narrow (struct itree_iterator *, ptrdiff_t, | 133 | extern void itree_iterator_narrow (struct itree_iterator *, ptrdiff_t, |
| 139 | ptrdiff_t); | 134 | ptrdiff_t); |
| 140 | extern void itree_iterator_finish (struct itree_iterator *); | 135 | extern void itree_iterator_finish (struct itree_iterator *); |
| 141 | extern struct itree_node *itree_iterator_next (struct itree_iterator *); | 136 | extern struct itree_node *itree_iterator_next (struct itree_iterator *); |
| 142 | 137 | ||
| 138 | /* State used when iterating interval. */ | ||
| 139 | struct itree_iterator | ||
| 140 | { | ||
| 141 | struct itree_node *node; /* FIXME: It should be either `node` or `stack`. */ | ||
| 142 | struct itree_stack *stack; | ||
| 143 | ptrdiff_t begin; | ||
| 144 | ptrdiff_t end; | ||
| 145 | uintmax_t otick; /* A copy of the tree's `otick`. */ | ||
| 146 | enum itree_order order; | ||
| 147 | bool running; | ||
| 148 | }; | ||
| 149 | |||
| 143 | /* Iterate over the intervals between BEG and END in the tree T. | 150 | /* Iterate over the intervals between BEG and END in the tree T. |
| 144 | N will hold successive nodes. ORDER can be one of : `ASCENDING`, | 151 | N will hold successive nodes. ORDER can be one of : `ASCENDING`, |
| 145 | `DESCENDING`, or `PRE_ORDER`. | 152 | `DESCENDING`, or `PRE_ORDER`. |
| @@ -153,29 +160,26 @@ extern struct itree_node *itree_iterator_next (struct itree_iterator *); | |||
| 153 | BEWARE: | 160 | BEWARE: |
| 154 | - The expression T may be evaluated more than once, so make sure | 161 | - The expression T may be evaluated more than once, so make sure |
| 155 | it is cheap and pure. | 162 | it is cheap and pure. |
| 156 | - Only a single iteration can happen at a time, so make sure none of the | ||
| 157 | code within the loop can start another tree iteration, i.e. it shouldn't | ||
| 158 | be able to run ELisp code, nor GC since GC can run ELisp by way | ||
| 159 | of `post-gc-hook`. | ||
| 160 | - If you need to exit the loop early, you *have* to call `ITREE_ABORT` | 163 | - If you need to exit the loop early, you *have* to call `ITREE_ABORT` |
| 161 | just before exiting (e.g. with `break` or `return`). | 164 | just before exiting (e.g. with `break` or `return`). |
| 162 | - Non-local exits are not supported within the body of the loop. | 165 | - Non-local exits are not supported within the body of the loop. |
| 163 | - Don't modify the tree during the iteration. | 166 | - Don't modify the tree during the iteration. |
| 164 | */ | 167 | */ |
| 165 | #define ITREE_FOREACH(n, t, beg, end, order) \ | 168 | #define ITREE_FOREACH(n, t, beg, end, order) \ |
| 166 | /* FIXME: We'd want to declare `x` right here, but I can't figure out | 169 | /* FIXME: We'd want to declare `n` right here, but I can't figure out |
| 167 | how to make that work here: the `for` syntax only allows a single | 170 | how to make that work here: the `for` syntax only allows a single |
| 168 | clause for the var declarations where we need 2 different types. | 171 | clause for the var declarations where we need 2 different types. |
| 169 | We could use the `struct {foo x; bar y; } p;` trick to declare two | 172 | We could use the `struct {foo x; bar y; } p;` trick to declare two |
| 170 | vars `p.x` and `p.y` of unrelated types, but then none of the names | 173 | vars `p.x` and `p.y` of unrelated types, but then none of the names |
| 171 | of the vars matches the `n` we receive :-(. */ \ | 174 | of the vars matches the `n` we receive :-(. */ \ |
| 172 | if (!t) \ | 175 | if (!t) \ |
| 173 | { } \ | 176 | { } \ |
| 174 | else \ | 177 | else \ |
| 175 | for (struct itree_iterator *itree_iter_ \ | 178 | for (struct itree_iterator itree_local_iter_, \ |
| 176 | = itree_iterator_start (t, beg, end, ITREE_##order, \ | 179 | *itree_iter_ \ |
| 177 | __FILE__, __LINE__); \ | 180 | = itree_iterator_start (&itree_local_iter_, \ |
| 178 | ((n = itree_iterator_next (itree_iter_)) \ | 181 | t, beg, end, ITREE_##order); \ |
| 182 | ((n = itree_iterator_next (itree_iter_)) \ | ||
| 179 | || (itree_iterator_finish (itree_iter_), false));) | 183 | || (itree_iterator_finish (itree_iter_), false));) |
| 180 | 184 | ||
| 181 | #define ITREE_FOREACH_ABORT() \ | 185 | #define ITREE_FOREACH_ABORT() \ |