* noverlay branch @ 2022-09-25 22:38 Stefan Monnier 2022-09-25 22:50 ` Lars Ingebrigtsen ` (6 more replies) 0 siblings, 7 replies; 71+ messages in thread From: Stefan Monnier @ 2022-09-25 22:38 UTC (permalink / raw) To: emacs-devel I just updated the noverlay branch to the code in `master` (most of it was mechanical except for the fact that since the last commit on that branch we have gotten rid of Lisp_Misc and we moved to pdumper). I'm getting ready to install this in `master`, so I'd encourage people to try this code as much as possible to try and weed out the most glaring problems before it hits master. The code generally looks good, but it touches some quite "core" code in the redisplay (with lots of off-by-one opportunities) and in the memory management (with opportunities for crashes and memory leaks). For those who're not familiar with this branch, it changes the way overlays are stored in a buffer: instead of keeping them in naïve singly-linked lists, it keeps them in balanced binary trees, so as to replace an O(N) complexity with O(log N). This way Emacs should not get sluggish even with millions of overlays (of course, if a given buffer position is covered by a million overlays, it'll still be sluggish when operating around that position). Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 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 ` (5 subsequent siblings) 6 siblings, 1 reply; 71+ messages in thread From: Lars Ingebrigtsen @ 2022-09-25 22:50 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: > I just updated the noverlay branch to the code in `master` Great! I've tried using it for a couple minutes doing my normal setup (which involves a bunch of stuff), and I don't see any anomalies, which is promising. But I'm getting these two test failures in buffer-tests.el: 2 unexpected results: FAILED test-overlays-in-2 FAILED test-remove-overlays ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-25 22:50 ` Lars Ingebrigtsen @ 2022-09-25 22:56 ` Stefan Monnier 0 siblings, 0 replies; 71+ messages in thread From: Stefan Monnier @ 2022-09-25 22:56 UTC (permalink / raw) To: Lars Ingebrigtsen; +Cc: emacs-devel Lars Ingebrigtsen [2022-09-26 00:50:40] wrote: > Stefan Monnier <monnier@iro.umontreal.ca> writes: >> I just updated the noverlay branch to the code in `master` > > Great! > > I've tried using it for a couple minutes doing my normal setup (which > involves a bunch of stuff), and I don't see any anomalies, which is > promising. > > But I'm getting these two test failures in buffer-tests.el: > > 2 unexpected results: > FAILED test-overlays-in-2 > FAILED test-remove-overlays Yes, they're in my todo already :-) Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-25 22:38 noverlay branch Stefan Monnier 2022-09-25 22:50 ` Lars Ingebrigtsen @ 2022-09-26 2:52 ` Ihor Radchenko 2022-09-26 3:17 ` Stefan Monnier 2022-09-26 6:11 ` Eli Zaretskii 2022-09-27 5:12 ` Matt Armstrong ` (4 subsequent siblings) 6 siblings, 2 replies; 71+ messages in thread From: Ihor Radchenko @ 2022-09-26 2:52 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 976 bytes --] On Mon, Sep 26, 2022 at 6:38 AM Stefan Monnier <monnier@iro.umontreal.ca> wrote: > I just updated the noverlay branch to the code in `master` (most of it > was mechanical except for the fact that since the last commit on that > branch we have gotten rid of Lisp_Misc and we moved to pdumper). > > Thanks! I have been waiting for this for years. > I'm getting ready to install this in `master`, so I'd encourage people > to try this code as much as possible to try and weed out the most > glaring problems before it hits master. The code generally looks good, > but it touches some quite "core" code in the redisplay (with lots of > off-by-one opportunities) and in the memory management (with > opportunities for crashes and memory leaks). > I tried with my Emacs config and after some fiddling Emacs crashed during helm search via helm-org-ql (a lot of regexp searching). I was unable to trigger the crash using unoptimized build, so just giving a heads-up. Best, Ihor [-- Attachment #2: Type: text/html, Size: 1556 bytes --] ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-26 2:52 ` Ihor Radchenko @ 2022-09-26 3:17 ` Stefan Monnier 2022-09-26 6:11 ` Eli Zaretskii 1 sibling, 0 replies; 71+ messages in thread From: Stefan Monnier @ 2022-09-26 3:17 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-devel > I tried with my Emacs config and after some fiddling Emacs crashed during > helm search via helm-org-ql If you can run it under GDB and provide a backtrace when you get such a crash, that would be immensely helpful (especially if you can build with `--enable-checking` and without too high an optimization level). Even better of course is a reproducible recipe. > (a lot of regexp searching). The regexp search code is largely untouched, so it's probably not directly connected (famous last word?). > I was unable to trigger the crash using unoptimized build, so just giving a > heads-up. Oh, well, thanks anyway, it's still a useful data point. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 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 1 sibling, 1 reply; 71+ messages in thread From: Eli Zaretskii @ 2022-09-26 6:11 UTC (permalink / raw) To: Ihor Radchenko; +Cc: monnier, emacs-devel > From: Ihor Radchenko <yantar92@gmail.com> > Date: Mon, 26 Sep 2022 10:52:11 +0800 > Cc: emacs-devel <emacs-devel@gnu.org> > > I tried with my Emacs config and after some fiddling Emacs crashed during helm search via helm-org-ql (a > lot of regexp searching). > I was unable to trigger the crash using unoptimized build, so just giving a heads-up. A backtrace from an optimized build can also be of help. It is certainly better than nothing. Thanks. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-26 6:11 ` Eli Zaretskii @ 2022-09-26 13:08 ` Ihor Radchenko 2022-09-26 15:59 ` Eli Zaretskii 2022-09-29 18:12 ` Stefan Monnier 0 siblings, 2 replies; 71+ messages in thread From: Ihor Radchenko @ 2022-09-26 13:08 UTC (permalink / raw) To: Eli Zaretskii; +Cc: monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> I tried with my Emacs config and after some fiddling Emacs crashed during helm search via helm-org-ql (a >> lot of regexp searching). >> I was unable to trigger the crash using unoptimized build, so just giving a heads-up. > > A backtrace from an optimized build can also be of help. It is > certainly better than nothing. (the other attempt with unoptimized build was using ./configure --enable-checking='yes,glyphs' --enable-check-lisp-object-type CFLAGS='-O0 -g3' --without-native-compilation) Backtrace after ./configure && make bootstrap I 1. Opened bunch of Org files 2. Generated agenda view (agenda creates a bunch of markers) 3. Ran helm-org-ql in an Org buffer. The results never arrived and once I hit C-g to abort helm process, Emacs crashed. Fatal error 6: Aborted Backtrace: /home/yantar92/Git/emacs/src/emacs(+0x19ec41)[0x5555556f2c41] /home/yantar92/Git/emacs/src/emacs(+0x4ae2c)[0x55555559ee2c] /home/yantar92/Git/emacs/src/emacs(+0x4b37f)[0x55555559f37f] /home/yantar92/Git/emacs/src/emacs(+0x517fa)[0x5555555a57fa] /home/yantar92/Git/emacs/src/emacs(+0x279cf2)[0x5555557cdcf2] /home/yantar92/Git/emacs/src/emacs(+0x2031a4)[0x5555557571a4] /home/yantar92/Git/emacs/src/emacs(+0x1c4819)[0x555555718819] /home/yantar92/Git/emacs/src/emacs(+0x2511b7)[0x5555557a51b7] /home/yantar92/Git/emacs/src/emacs(+0x20aae3)[0x55555575eae3] /home/yantar92/Git/emacs/src/emacs(+0x208f41)[0x55555575cf41] /home/yantar92/Git/emacs/src/emacs(+0x17d854)[0x5555556d1854] /home/yantar92/Git/emacs/src/emacs(+0x2094bc)[0x55555575d4bc] /home/yantar92/Git/emacs/src/emacs(+0x184213)[0x5555556d8213] /home/yantar92/Git/emacs/src/emacs(+0x191825)[0x5555556e5825] /home/yantar92/Git/emacs/src/emacs(+0x208ca7)[0x55555575cca7] /home/yantar92/Git/emacs/src/emacs(+0x17c6b6)[0x5555556d06b6] /home/yantar92/Git/emacs/src/emacs(+0x208c01)[0x55555575cc01] /home/yantar92/Git/emacs/src/emacs(+0x17c601)[0x5555556d0601] /home/yantar92/Git/emacs/src/emacs(+0x183a74)[0x5555556d7a74] /home/yantar92/Git/emacs/src/emacs(+0x1b6efb)[0x55555570aefb] /home/yantar92/Git/emacs/src/emacs(+0x20d58a)[0x55555576158a] /home/yantar92/Git/emacs/src/emacs(+0x2511b7)[0x5555557a51b7] /home/yantar92/Git/emacs/src/emacs(+0x20aae3)[0x55555575eae3] /home/yantar92/Git/emacs/src/emacs(+0x20ad08)[0x55555575ed08] /home/yantar92/Git/emacs/src/emacs(+0x2511b7)[0x5555557a51b7] /home/yantar92/Git/emacs/src/emacs(+0x20aae3)[0x55555575eae3] /home/yantar92/Git/emacs/src/emacs(+0x20ad08)[0x55555575ed08] /home/yantar92/Git/emacs/src/emacs(+0x2511b7)[0x5555557a51b7] /home/yantar92/Git/emacs/src/emacs(+0x20f897)[0x555555763897] /home/yantar92/Git/emacs/src/emacs(+0x20e2f6)[0x5555557622f6] /home/yantar92/Git/emacs/src/emacs(+0x20f465)[0x555555763465] /home/yantar92/Git/emacs/src/emacs(+0x20aae3)[0x55555575eae3] /home/yantar92/Git/emacs/src/emacs(+0x20644f)[0x55555575a44f] /home/yantar92/Git/emacs/src/emacs(+0x20aae3)[0x55555575eae3] /home/yantar92/Git/emacs/src/emacs(+0x20aeb8)[0x55555575eeb8] /home/yantar92/Git/emacs/src/emacs(+0x207d67)[0x55555575bd67] /home/yantar92/Git/emacs/src/emacs(+0x20e4cf)[0x5555557624cf] /home/yantar92/Git/emacs/src/emacs(+0x2109c7)[0x5555557649c7] /home/yantar92/Git/emacs/src/emacs(+0x20e4df)[0x5555557624df] /home/yantar92/Git/emacs/src/emacs(+0x20e8ed)[0x5555557628ed] /home/yantar92/Git/emacs/src/emacs(+0x20e3f2)[0x5555557623f2] ... Thread 1 "emacs" received signal SIGABRT, Aborted. 0x00007ffff5172eac in ?? () from /lib64/libc.so.6 -- Ihor Radchenko, Org mode contributor, Learn more about Org mode at https://orgmode.org/. Support Org development at https://liberapay.com/org-mode, or support my work at https://liberapay.com/yantar92 ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-26 13:08 ` Ihor Radchenko @ 2022-09-26 15:59 ` Eli Zaretskii [not found] ` <87v8ovdosz.fsf@localhost> 2022-09-29 18:12 ` Stefan Monnier 1 sibling, 1 reply; 71+ messages in thread From: Eli Zaretskii @ 2022-09-26 15:59 UTC (permalink / raw) To: Ihor Radchenko; +Cc: monnier, emacs-devel > From: Ihor Radchenko <yantar92@gmail.com> > Cc: monnier@iro.umontreal.ca, emacs-devel@gnu.org > Date: Mon, 26 Sep 2022 21:08:02 +0800 > > Eli Zaretskii <eliz@gnu.org> writes: > > > A backtrace from an optimized build can also be of help. It is > > certainly better than nothing. > > (the other attempt with unoptimized build was using > ./configure --enable-checking='yes,glyphs' --enable-check-lisp-object-type CFLAGS='-O0 -g3' --without-native-compilation) > > Backtrace after ./configure && make bootstrap > > I > 1. Opened bunch of Org files > 2. Generated agenda view (agenda creates a bunch of markers) > 3. Ran helm-org-ql in an Org buffer. The results never arrived and once > I hit C-g to abort helm process, Emacs crashed. > > Fatal error 6: Aborted > Backtrace: > /home/yantar92/Git/emacs/src/emacs(+0x19ec41)[0x5555556f2c41] > /home/yantar92/Git/emacs/src/emacs(+0x4ae2c)[0x55555559ee2c] > /home/yantar92/Git/emacs/src/emacs(+0x4b37f)[0x55555559f37f] > /home/yantar92/Git/emacs/src/emacs(+0x517fa)[0x5555555a57fa] > /home/yantar92/Git/emacs/src/emacs(+0x279cf2)[0x5555557cdcf2] > /home/yantar92/Git/emacs/src/emacs(+0x2031a4)[0x5555557571a4] > /home/yantar92/Git/emacs/src/emacs(+0x1c4819)[0x555555718819] > /home/yantar92/Git/emacs/src/emacs(+0x2511b7)[0x5555557a51b7] > /home/yantar92/Git/emacs/src/emacs(+0x20aae3)[0x55555575eae3] > /home/yantar92/Git/emacs/src/emacs(+0x208f41)[0x55555575cf41] > /home/yantar92/Git/emacs/src/emacs(+0x17d854)[0x5555556d1854] > /home/yantar92/Git/emacs/src/emacs(+0x2094bc)[0x55555575d4bc] > /home/yantar92/Git/emacs/src/emacs(+0x184213)[0x5555556d8213] > /home/yantar92/Git/emacs/src/emacs(+0x191825)[0x5555556e5825] > /home/yantar92/Git/emacs/src/emacs(+0x208ca7)[0x55555575cca7] > /home/yantar92/Git/emacs/src/emacs(+0x17c6b6)[0x5555556d06b6] > /home/yantar92/Git/emacs/src/emacs(+0x208c01)[0x55555575cc01] > /home/yantar92/Git/emacs/src/emacs(+0x17c601)[0x5555556d0601] > /home/yantar92/Git/emacs/src/emacs(+0x183a74)[0x5555556d7a74] > /home/yantar92/Git/emacs/src/emacs(+0x1b6efb)[0x55555570aefb] > /home/yantar92/Git/emacs/src/emacs(+0x20d58a)[0x55555576158a] > /home/yantar92/Git/emacs/src/emacs(+0x2511b7)[0x5555557a51b7] > /home/yantar92/Git/emacs/src/emacs(+0x20aae3)[0x55555575eae3] > /home/yantar92/Git/emacs/src/emacs(+0x20ad08)[0x55555575ed08] > /home/yantar92/Git/emacs/src/emacs(+0x2511b7)[0x5555557a51b7] > /home/yantar92/Git/emacs/src/emacs(+0x20aae3)[0x55555575eae3] > /home/yantar92/Git/emacs/src/emacs(+0x20ad08)[0x55555575ed08] > /home/yantar92/Git/emacs/src/emacs(+0x2511b7)[0x5555557a51b7] > /home/yantar92/Git/emacs/src/emacs(+0x20f897)[0x555555763897] > /home/yantar92/Git/emacs/src/emacs(+0x20e2f6)[0x5555557622f6] > /home/yantar92/Git/emacs/src/emacs(+0x20f465)[0x555555763465] > /home/yantar92/Git/emacs/src/emacs(+0x20aae3)[0x55555575eae3] > /home/yantar92/Git/emacs/src/emacs(+0x20644f)[0x55555575a44f] > /home/yantar92/Git/emacs/src/emacs(+0x20aae3)[0x55555575eae3] > /home/yantar92/Git/emacs/src/emacs(+0x20aeb8)[0x55555575eeb8] > /home/yantar92/Git/emacs/src/emacs(+0x207d67)[0x55555575bd67] > /home/yantar92/Git/emacs/src/emacs(+0x20e4cf)[0x5555557624cf] > /home/yantar92/Git/emacs/src/emacs(+0x2109c7)[0x5555557649c7] > /home/yantar92/Git/emacs/src/emacs(+0x20e4df)[0x5555557624df] > /home/yantar92/Git/emacs/src/emacs(+0x20e8ed)[0x5555557628ed] > /home/yantar92/Git/emacs/src/emacs(+0x20e3f2)[0x5555557623f2] Thanks, but the above can only be interpreted on your machine. So either you can do this while running Emacs under GDB, and then post the backtrace produced by GDB; or use the technique described in the node "Crashing" of the Emacs user manual to convert the above addresses to source file names and line numbers, and post the results here. FTR: Emacs didn't crash, it aborted (most probably due to an assertion violation). ^ permalink raw reply [flat|nested] 71+ messages in thread
[parent not found: <87v8ovdosz.fsf@localhost>]
* Re: noverlay branch [not found] ` <87v8ovdosz.fsf@localhost> @ 2022-10-08 6:57 ` Eli Zaretskii 2022-10-09 3:25 ` Matt Armstrong 2022-10-09 3:23 ` Matt Armstrong 2022-10-09 3:47 ` Stefan Monnier 2 siblings, 1 reply; 71+ messages in thread From: Eli Zaretskii @ 2022-10-08 6:57 UTC (permalink / raw) To: Ihor Radchenko; +Cc: monnier, emacs-devel > From: Ihor Radchenko <yantar92@gmail.com> > Cc: monnier@iro.umontreal.ca, emacs-devel@gnu.org > Date: Sat, 08 Oct 2022 10:36:28 +0800 > > There are a couple of glitches though: > 1. hl-line-mode overlay sometimes disappear > 2. notmuch search buffer hides text in wrong places (see the attached video) > > Should I report these things as proper bugs or is it OK to discuss them > within this thread? Separate bug reports, please. And please mention there that the bugs pertain to the noverlay branch. Thanks. > Also, I did a small test comparing Org mode folding performance (using > overlays) on master vs feature/noverlay branch using the methodology > described in https://blog.tecosaur.com/tmio/2022-05-31-folding.html: > > On large 20Mb Org file using built-in Org version: > > master: Elapsed time: 142.940028s (1.428150s in 55 GCs) > feature/noverlay: Elapsed time: 4.369854s (1.451378s in 74 GCs) > > for comparison, the Org version from main yields: > > Emacs master: > - main Org branch using text properties for folding: Elapsed time: 2.185786s (0.639190s in 4 GCs) > - main Org branch using overlays for folding: Elapsed time: 27.244284s (0.731581s in 5 GCs) > > Emacs feature/noverlay: > - main Org branch using text properties for folding: Elapsed time: 1.586936s (0.476606s in 3 GCs) > - main Org branch using overlays for folding: Elapsed time: 2.039803s (0.724576s in 5 GCs) > > The improvement is very significant. Thanks, looks very encouraging. > We also have tests for marker scaling. If the overlay tree > implementation can be reused for markers, it would also be great. Not in Emacs 29, I'm afraid. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-08 6:57 ` Eli Zaretskii @ 2022-10-09 3:25 ` Matt Armstrong 2022-10-09 4:30 ` Eli Zaretskii 0 siblings, 1 reply; 71+ messages in thread From: Matt Armstrong @ 2022-10-09 3:25 UTC (permalink / raw) To: Eli Zaretskii, Ihor Radchenko; +Cc: monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> We also have tests for marker scaling. If the overlay tree >> implementation can be reused for markers, it would also be great. > > Not in Emacs 29, I'm afraid. Scalable markers will be *much* easier conceptually than overlays. I had this in mind for a post noverlay work. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-09 3:25 ` Matt Armstrong @ 2022-10-09 4:30 ` Eli Zaretskii 0 siblings, 0 replies; 71+ messages in thread From: Eli Zaretskii @ 2022-10-09 4:30 UTC (permalink / raw) To: Matt Armstrong; +Cc: yantar92, monnier, emacs-devel > From: Matt Armstrong <matt@rfc20.org> > Cc: monnier@iro.umontreal.ca, emacs-devel@gnu.org > Date: Sat, 08 Oct 2022 20:25:45 -0700 > > Eli Zaretskii <eliz@gnu.org> writes: > > >> We also have tests for marker scaling. If the overlay tree > >> implementation can be reused for markers, it would also be great. > > > > Not in Emacs 29, I'm afraid. > > Scalable markers will be *much* easier conceptually than overlays. I > had this in mind for a post noverlay work. I didn't say they were complicated. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch [not found] ` <87v8ovdosz.fsf@localhost> 2022-10-08 6:57 ` Eli Zaretskii @ 2022-10-09 3:23 ` Matt Armstrong 2022-10-09 3:47 ` Stefan Monnier 2 siblings, 0 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-09 3:23 UTC (permalink / raw) To: Ihor Radchenko, Eli Zaretskii; +Cc: monnier, emacs-devel Ihor Radchenko <yantar92@gmail.com> writes: > Update: I can no longer see crashes on the latest commit in the branch. > > I was able to use the branch with my config for while now, and it works > mostly fine. > > There are a couple of glitches though: > 1. hl-line-mode overlay sometimes disappear > 2. notmuch search buffer hides text in wrong places (see the attached video) > > Should I report these things as proper bugs or is it OK to discuss them > within this thread? Hi Ihor, yes, your glitches (1) and (2) make sense to me, given some fundamental bugs I have uncovered in noverlay branch. In some not-yet-commited debug code I have against the noverlay branch I am seeing binary tree invariants violated. For example, overlay nodes in a binary tree with BEGIN positions that are out of range for the subtree they are in, potentially caused by them being offset incorrectly. The algorithms involved are quite fragile. These bugs would cause functions like overlays-at and overlays-in to potentially miss overlays, as well as for BEGIN and/or END to be in incorrect positions. This would explain both scenarios you describe above. Polishing off this new debug code and getting it commited, as well as fixing the bugs, is what I'm working on. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch [not found] ` <87v8ovdosz.fsf@localhost> 2022-10-08 6:57 ` Eli Zaretskii 2022-10-09 3:23 ` Matt Armstrong @ 2022-10-09 3:47 ` Stefan Monnier 2022-10-13 12:09 ` Ihor Radchenko 2 siblings, 1 reply; 71+ messages in thread From: Stefan Monnier @ 2022-10-09 3:47 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel > Update: I can no longer see crashes on the latest commit in the branch. Thanks for the update. > There are a couple of glitches though: > 1. hl-line-mode overlay sometimes disappear > 2. notmuch search buffer hides text in wrong places (see the attached video) Hmm... that's worrisome. > Should I report these things as proper bugs or is it OK to discuss them > within this thread? As Eli said, bug-reports are best. I don't read the bug-reports list, so I'd appreciate if you can add me to the `X-Debbugs-Cc:` when you report those bugs. > Also, I did a small test comparing Org mode folding performance (using > overlays) on master vs feature/noverlay branch using the methodology > described in https://blog.tecosaur.com/tmio/2022-05-31-folding.html: Very cool, thanks! > We also have tests for marker scaling. If the overlay tree > implementation can be reused for markers, it would also be great. That's definitely not on the current menu. It's not out of the question, but would require a fair bit more work and will come with new tradeoffs. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-09 3:47 ` Stefan Monnier @ 2022-10-13 12:09 ` Ihor Radchenko 0 siblings, 0 replies; 71+ messages in thread From: Ihor Radchenko @ 2022-10-13 12:09 UTC (permalink / raw) To: Stefan Monnier; +Cc: Eli Zaretskii, emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: >> There are a couple of glitches though: >> 1. hl-line-mode overlay sometimes disappear >> 2. notmuch search buffer hides text in wrong places (see the attached video) > > Hmm... that's worrisome. > >> Should I report these things as proper bugs or is it OK to discuss them >> within this thread? > > As Eli said, bug-reports are best. I don't read the bug-reports list, > so I'd appreciate if you can add me to the `X-Debbugs-Cc:` when you > report those bugs. Sorry, I forgot about X-Debbugs-Cc, but I did report the bugs: 1. bug#58478 Already fixed; unrelated to noverlay branch. 2. bug#58479 -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-26 13:08 ` Ihor Radchenko 2022-09-26 15:59 ` Eli Zaretskii @ 2022-09-29 18:12 ` Stefan Monnier 1 sibling, 0 replies; 71+ messages in thread From: Stefan Monnier @ 2022-09-29 18:12 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Eli Zaretskii, emacs-devel > (the other attempt with unoptimized build was using > ./configure --enable-checking='yes,glyphs' --enable-check-lisp-object-type > CFLAGS='-O0 -g3' --without-native-compilation) > > Backtrace after ./configure && make bootstrap While I don't know what your error may come from, we just fixed a possible source of a "crash via abort", so there's a (slim) chance that this is the bug you hit. IOW you might like to try the new code on `feature/noverlay` to see if it still suffers from your problem. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-25 22:38 noverlay branch Stefan Monnier 2022-09-25 22:50 ` Lars Ingebrigtsen 2022-09-26 2:52 ` Ihor Radchenko @ 2022-09-27 5:12 ` Matt Armstrong 2022-09-27 6:53 ` Eli Zaretskii 2022-09-28 23:09 ` Stefan Monnier 2022-09-27 8:39 ` Gerd Möllmann ` (3 subsequent siblings) 6 siblings, 2 replies; 71+ messages in thread From: Matt Armstrong @ 2022-09-27 5:12 UTC (permalink / raw) To: Stefan Monnier, emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: > I just updated the noverlay branch to the code in `master` (most of it > was mechanical except for the fact that since the last commit on that > branch we have gotten rid of Lisp_Misc and we moved to pdumper). This is pretty exciting! I've been looking at the noverlay branch as well in some detail, but only as a background task, over the past year. I haven't considered the branch ready for merge because I saw some areas where I think the implementation should improve. But, working code is a compelling thing. If noverlay is ready to merge I'd suggest doing it. Code can improve later, as and if needed. I was planning to takle these problems before proposing a merge: 1) Improve the worst case run time of `previous-overlay-change' from O(n) to O(log N). The noverlay branch uses an O(N) algorithm, though it is difficult to spot. Since the point of using a tree is O(log N) algorithms, and O(n) algorithms can easily become exponential algorithms when composed in higher level loops (the problem overlays sees today), this strikes me as important. 2) Look at reducing the number of malloc'd overlay blocks in half by expressing the tree intrusively (the same way the overlay list is today). I don't see a lot of value in itree.h/c abstracting away the interval logic from the overlay object itself. 3) Improve quality of comments in the new code. Personally, I find the algorithms quite subtle and quite a bit more complex than what you find on, say, https://en.wikipedia.org/wiki/Interval_tree or the Cormen et al. Introduction to Algorithms Book. I think I pieced most of it together but it took a lot of effort. At top of mind is looking at the interval_node.visited flag and figure out how that flag is used, then describe the algorithm in detail. It isn't clear to me how that flag gets set/cleared. Best case: doing so proves me wrong on point (1). And lower priority: 4) The overlay `front-advance' and `rear-advance' booleans are conceptually part of the overlay's BEG and END positions, except that this is ignored everhwhere except insertion. Upon insertion at any given POS the overlays are according to *both* their BEG or END positions and the *-advance booleans. Yet, this is not used when ordering overlays in the tree. Doing that may bring an opportunity to simplify code or make it more efficient. (Side note: there *may* also be a way to encode the *-advance flags implicitly in the beg/end fields positions if a way can be found to "steal" the free bit in the currently signed ptrdiff_t fields, effectively causing the *-advance flags to count as this extra "half" position for the purpose of insertion). 5) Look at using an augmented B-tree of overlays instead of a binary tree. B-trees are quite often faster than binary trees (at least on hardware made over the past few decades), so there is that enticing proposition. They're also typically not harder to implement, either, requiring no "rotations" nor convluted deletion logic. An *augmented* B-tree may also allow for simplification of the relative vs. absolute offsets in the tree. The noverlay branch currently handles this by treating the absolute offsets as cached values, "dirtying" them whenever any mutation occurs in the tree, and recomputing absolute offsets on demand. If augmented in the right way a B-tree might be shallow enough in practice that recomputation on demand is always fast enough, so the the invalidation/caching approach (as well as the memory used to track it) can go away. (wild idea) ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-27 5:12 ` Matt Armstrong @ 2022-09-27 6:53 ` Eli Zaretskii 2022-09-27 17:31 ` Matt Armstrong 2022-09-28 23:09 ` Stefan Monnier 1 sibling, 1 reply; 71+ messages in thread From: Eli Zaretskii @ 2022-09-27 6:53 UTC (permalink / raw) To: Matt Armstrong; +Cc: monnier, emacs-devel > From: Matt Armstrong <matt@rfc20.org> > Date: Mon, 26 Sep 2022 22:12:44 -0700 > > Stefan Monnier <monnier@iro.umontreal.ca> writes: > > > I just updated the noverlay branch to the code in `master` (most of it > > was mechanical except for the fact that since the last commit on that > > branch we have gotten rid of Lisp_Misc and we moved to pdumper). > > This is pretty exciting! > > I've been looking at the noverlay branch as well in some detail, but > only as a background task, over the past year. > > I haven't considered the branch ready for merge because I saw some areas > where I think the implementation should improve. But, working code is a > compelling thing. If noverlay is ready to merge I'd suggest doing it. > Code can improve later, as and if needed. > > I was planning to takle these problems before proposing a merge: > > 1) Improve the worst case run time of `previous-overlay-change' from > O(n) to O(log N). The noverlay branch uses an O(N) algorithm, > though it is difficult to spot. Since the point of using a tree is > O(log N) algorithms, and O(n) algorithms can easily become > exponential algorithms when composed in higher level loops (the > problem overlays sees today), this strikes me as important. > > 2) Look at reducing the number of malloc'd overlay blocks in half by > expressing the tree intrusively (the same way the overlay list is > today). I don't see a lot of value in itree.h/c abstracting away > the interval logic from the overlay object itself. > > 3) Improve quality of comments in the new code. Personally, I find the > algorithms quite subtle and quite a bit more complex than what you > find on, say, https://en.wikipedia.org/wiki/Interval_tree or the > Cormen et al. Introduction to Algorithms Book. I think I pieced > most of it together but it took a lot of effort. At top of mind is > looking at the interval_node.visited flag and figure out how that > flag is used, then describe the algorithm in detail. It isn't clear > to me how that flag gets set/cleared. Best case: doing so proves me > wrong on point (1). > > And lower priority: > > 4) The overlay `front-advance' and `rear-advance' booleans are > conceptually part of the overlay's BEG and END positions, except > that this is ignored everhwhere except insertion. Upon insertion at > any given POS the overlays are according to *both* their BEG or END > positions and the *-advance booleans. Yet, this is not used when > ordering overlays in the tree. Doing that may bring an opportunity > to simplify code or make it more efficient. (Side note: there *may* > also be a way to encode the *-advance flags implicitly in the > beg/end fields positions if a way can be found to "steal" the free > bit in the currently signed ptrdiff_t fields, effectively causing > the *-advance flags to count as this extra "half" position for the > purpose of insertion). > > 5) Look at using an augmented B-tree of overlays instead of a binary > tree. B-trees are quite often faster than binary trees (at least on > hardware made over the past few decades), so there is that enticing > proposition. They're also typically not harder to implement, > either, requiring no "rotations" nor convluted deletion logic. An > *augmented* B-tree may also allow for simplification of the relative > vs. absolute offsets in the tree. The noverlay branch currently > handles this by treating the absolute offsets as cached values, > "dirtying" them whenever any mutation occurs in the tree, and > recomputing absolute offsets on demand. If augmented in the right > way a B-tree might be shallow enough in practice that recomputation > on demand is always fast enough, so the the invalidation/caching > approach (as well as the memory used to track it) can go away. > (wild idea) Thank you for your interest in this branch. We want to have this feature in Emacs 29, so, barring some grave bugs that will be uncovered soon enough, this branch will land on master soon. You can work on the items you've mentioned either now or after the branch is merged. Item 3 can be done immediately, and will be greatly appreciated, as comments that explain how the code works are always welcome. Of the rest, item 1 sounds like the most important one, but do you have ideas for how to achieve that? ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-27 6:53 ` Eli Zaretskii @ 2022-09-27 17:31 ` Matt Armstrong 2022-09-27 18:45 ` Stefan Monnier 0 siblings, 1 reply; 71+ messages in thread From: Matt Armstrong @ 2022-09-27 17:31 UTC (permalink / raw) To: Eli Zaretskii; +Cc: monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: [...] >> I was planning to takle these problems before proposing a merge: >> >> 1) Improve the worst case run time of `previous-overlay-change' from >> O(n) to O(log N). The noverlay branch uses an O(N) algorithm, >> though it is difficult to spot. Since the point of using a tree is >> O(log N) algorithms, and O(n) algorithms can easily become >> exponential algorithms when composed in higher level loops (the >> problem overlays sees today), this strikes me as important. [...] >> 3) Improve quality of comments in the new code. Personally, I find the >> algorithms quite subtle and quite a bit more complex than what you >> find on, say, https://en.wikipedia.org/wiki/Interval_tree or the >> Cormen et al. Introduction to Algorithms Book. I think I pieced >> most of it together but it took a lot of effort. At top of mind is >> looking at the interval_node.visited flag and figure out how that >> flag is used, then describe the algorithm in detail. It isn't clear >> to me how that flag gets set/cleared. Best case: doing so proves me >> wrong on point (1). [...] > We want to have this feature in Emacs 29, so, barring some grave bugs > that will be uncovered soon enough, this branch will land on master > soon. You can work on the items you've mentioned either now or after > the branch is merged. Item 3 can be done immediately, and will be > greatly appreciated, as comments that explain how the code works are > always welcome. I agree that this is a good plan. A nice thing about the noverlay branch is that it touches every place in Emacs that deals with overlay lists and, better, puts that behind an API. That is a good reason to merge noverlay now and, maybe, change things later. > Of the rest, item 1 sounds like the most important one, but do you > have ideas for how to achieve that? I think (3) -- improving the comments -- is a good starting point, because I want to verify my claim that the *-overlay-change algorithms are worst-case inefficient. My idea is: store each overlay in two trees, one ordered by ascending BEG and one ordered by descending END. These are the similar to the current "overlays before" and "overlays after" linked lists, except that the "overlay center" is gone and replaced by O(log N) lookups. (In fact we could retain the idea of an "overlay center" by maintaining a "finger" node in each tree...but I digress...) From there an algorithm that is O(log N) in an obvious way falls out trivially in the same way it does for the current overlay lists approach. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-27 17:31 ` Matt Armstrong @ 2022-09-27 18:45 ` Stefan Monnier 0 siblings, 0 replies; 71+ messages in thread From: Stefan Monnier @ 2022-09-27 18:45 UTC (permalink / raw) To: Matt Armstrong; +Cc: Eli Zaretskii, emacs-devel >> Of the rest, item 1 sounds like the most important one, but do you >> have ideas for how to achieve that? > I think (3) -- improving the comments -- is a good starting point, Not only that. It's an important end in itself. Even if you don't know what the code does or why it does something, writing it as a comment can be very useful (can help someone else, who does understand the code, notice that it deserves an explanation). > My idea is: store each overlay in two trees, one ordered by ascending > BEG and one ordered by descending END. These are the similar to the > current "overlays before" and "overlays after" linked lists, except that > the "overlay center" is gone and replaced by O(log N) lookups. (In fact > we could retain the idea of an "overlay center" by maintaining a > "finger" node in each tree...but I digress...) I don't want to discourage you, but please do remember that we have lived with a "ridiculously inefficient" implementation for the last 30 years or so, and that keeping two trees comes at a cost (coding/maintenance cost + runtime cost + heapsize cost). Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-27 5:12 ` Matt Armstrong 2022-09-27 6:53 ` Eli Zaretskii @ 2022-09-28 23:09 ` Stefan Monnier 2022-09-29 14:54 ` Gerd Möllmann 1 sibling, 1 reply; 71+ messages in thread From: Stefan Monnier @ 2022-09-28 23:09 UTC (permalink / raw) To: Matt Armstrong; +Cc: emacs-devel > 3) Improve quality of comments in the new code. Personally, I find the > algorithms quite subtle and quite a bit more complex than what you > find on, say, https://en.wikipedia.org/wiki/Interval_tree or the > Cormen et al. Introduction to Algorithms Book. I think I pieced > most of it together but it took a lot of effort. At top of mind is > looking at the interval_node.visited flag and figure out how that > flag is used, then describe the algorithm in detail. It isn't clear > to me how that flag gets set/cleared. Best case: doing so proves me > wrong on point (1). I can explain the `visited` bit (and I added a comment about it in the code). What I'm less clear about is the use of the `null` sentinel node. It seems that this node is sometimes modified (e.g. its color changed from black to red or vice versa, and maybe also its `parent` field), even though it's pointed to from lots of different nodes, so any help documenting the way it works (or why the value in those fields doesn't matter) would be welcome. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-28 23:09 ` Stefan Monnier @ 2022-09-29 14:54 ` Gerd Möllmann 2022-09-29 21:36 ` Stefan Monnier 0 siblings, 1 reply; 71+ messages in thread From: Gerd Möllmann @ 2022-09-29 14:54 UTC (permalink / raw) To: monnier; +Cc: emacs-devel, matt > What I'm less clear about is the use of the `null` sentinel node. > It seems that this node is sometimes modified (e.g. its color changed > from black to red or vice versa, and maybe also its `parent` field), > even though it's pointed to from lots of different nodes, so any help > documenting the way it works (or why the value in those fields doesn't > matter) would be welcome. Maybe I can say something because I used a similar trick in alloc.c. The gist of that was to make searching more elegant, and faster: static struct mem_node * mem_find (void *start) { struct mem_node *p; ... /* Make the search always successful to speed up the loop below. */ mem_z.start = start; mem_z.end = (char *) start + 1; p = mem_root; while (start < p->start || start >= p->end) p = start < p->start ? p->left : p->right; return p; } If that's done for the same reason in itree.c, I don't know. A hint that it is not, might be that each tree has a separated null node... ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 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 0 siblings, 2 replies; 71+ messages in thread From: Stefan Monnier @ 2022-09-29 21:36 UTC (permalink / raw) To: Gerd Möllmann; +Cc: emacs-devel, matt >> What I'm less clear about is the use of the `null` sentinel node. >> It seems that this node is sometimes modified (e.g. its color changed >> from black to red or vice versa, and maybe also its `parent` field), >> even though it's pointed to from lots of different nodes, so any help >> documenting the way it works (or why the value in those fields doesn't >> matter) would be welcome. > > Maybe I can say something because I used a similar trick in alloc.c. > The gist of that was to make searching more elegant, and faster: > > static struct mem_node * > mem_find (void *start) > { > struct mem_node *p; > > ... > > /* Make the search always successful to speed up the loop below. */ > mem_z.start = start; > mem_z.end = (char *) start + 1; > > p = mem_root; > while (start < p->start || start >= p->end) > p = start < p->start ? p->left : p->right; > return p; > } > > If that's done for the same reason in itree.c, I don't know. A hint that it > is not, might be that each tree has a separated null node... OTOH there's a single mem tree, so in a sense you also have a separate mem_nil node per tree :-) I actually do understand the above use. What I don't understand is code such as: interval_tree_remove (struct interval_tree *tree, struct interval_node *node) { struct interval_node *broken = NULL; if (node->left == &tree->null || node->right == &tree->null) { ... } else { struct interval_node *min = interval_tree_subtree_min (tree, node->right); struct interval_node *min_right = min->right; if (!min->red) broken = min->right; if (min->parent == node) min_right->parent = min; /* set parent, if min_right = null */ where `min_right` on this last line can definitely be the null node (my tests confirm it). So what does it mean that we set the null nodes' `parent` field here? How does it interact with other places where we use the `parent` field (such as the last-but-one line where I confirmed that `min` can also be the null node). I don't see any place where we (re)set the null's `parent` field (other than in `interval_tree_clear`)? So it looks like this field is "garbage" but not completely. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-29 21:36 ` Stefan Monnier @ 2022-09-30 5:20 ` Gerd Möllmann 2022-10-06 4:47 ` Matt Armstrong 1 sibling, 0 replies; 71+ messages in thread From: Gerd Möllmann @ 2022-09-30 5:20 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel, matt Stefan Monnier <monnier@iro.umontreal.ca> writes: W>> If that's done for the same reason in itree.c, I don't know. A hint that it >> is not, might be that each tree has a separated null node... > > OTOH there's a single mem tree, so in a sense you also have a separate > mem_nil node per tree :-) Hehe, true :-). > I actually do understand the above use. What I don't understand is code > such as: > > interval_tree_remove (struct interval_tree *tree, struct interval_node *node) > { > struct interval_node *broken = NULL; > if (node->left == &tree->null || node->right == &tree->null) > { ... } > else > { > struct interval_node *min = interval_tree_subtree_min (tree, node->right); > struct interval_node *min_right = min->right; > > if (!min->red) > broken = min->right; > if (min->parent == node) > min_right->parent = min; /* set parent, if min_right = null */ > > where `min_right` on this last line can definitely be the null node (my > tests confirm it). Ok. > So what does it mean that we set the null nodes' `parent` field here? > How does it interact with other places where we use the `parent` field > (such as the last-but-one line where I confirmed that `min` can also be > the null node). Yes, that's odd, but it should be harmless. In general, walking the tree should always stop at null, and not proceed with null itself. So null->parent shouldn't matter. But it feels odd. I don't remember other rb-tree implementations doing that. Maybe it's something special to Corman's implementation? (Introduction to Algorithms was always quite expensive here, so I never bought it). ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 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-06 12:08 ` Stefan Monnier 1 sibling, 2 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-06 4:47 UTC (permalink / raw) To: Stefan Monnier, Gerd Möllmann; +Cc: emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: >>> What I'm less clear about is the use of the `null` sentinel node. >>> It seems that this node is sometimes modified (e.g. its color changed >>> from black to red or vice versa, and maybe also its `parent` field), >>> even though it's pointed to from lots of different nodes, so any help >>> documenting the way it works (or why the value in those fields doesn't >>> matter) would be welcome. >> >> Maybe I can say something because I used a similar trick in alloc.c. >> The gist of that was to make searching more elegant, and faster: >> >> static struct mem_node * >> mem_find (void *start) >> { >> struct mem_node *p; >> >> ... >> >> /* Make the search always successful to speed up the loop below. */ >> mem_z.start = start; >> mem_z.end = (char *) start + 1; >> >> p = mem_root; >> while (start < p->start || start >= p->end) >> p = start < p->start ? p->left : p->right; >> return p; >> } >> >> If that's done for the same reason in itree.c, I don't know. A hint that it >> is not, might be that each tree has a separated null node... > > OTOH there's a single mem tree, so in a sense you also have a separate > mem_nil node per tree :-) > > I actually do understand the above use. What I don't understand is code > such as: > > interval_tree_remove (struct interval_tree *tree, struct interval_node *node) > { > struct interval_node *broken = NULL; > if (node->left == &tree->null || node->right == &tree->null) > { ... } > else > { > struct interval_node *min = interval_tree_subtree_min (tree, node->right); > struct interval_node *min_right = min->right; > > if (!min->red) > broken = min->right; > if (min->parent == node) > min_right->parent = min; /* set parent, if min_right = null */ > > where `min_right` on this last line can definitely be the null node (my > tests confirm it). > So what does it mean that we set the null nodes' `parent` field here? > How does it interact with other places where we use the `parent` field > (such as the last-but-one line where I confirmed that `min` can also be > the null node). > I don't see any place where we (re)set the null's `parent` field (other > than in `interval_tree_clear`)? So it looks like this field is > "garbage" but not completely. I see you removed some of the null->parent trick just today. I like that idea. It is realtively easy to use actual NULL instead of a sentinel NULL in tree algorithms, and I think on modern processors this works out for the better. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-06 4:47 ` Matt Armstrong @ 2022-10-06 5:43 ` Gerd Möllmann 2022-10-07 4:11 ` Matt Armstrong 2022-10-06 12:08 ` Stefan Monnier 1 sibling, 1 reply; 71+ messages in thread From: Gerd Möllmann @ 2022-10-06 5:43 UTC (permalink / raw) To: Matt Armstrong, Stefan Monnier; +Cc: emacs-devel On 22-10-06 6:47 , Matt Armstrong wrote: > I see you removed some of the null->parent trick just today. I like > that idea. It is realtively easy to use actual NULL instead of a > sentinel NULL in tree algorithms, and I think on modern processors this > works out for the better. Clang libstd++ uses NULL, BTW, and I already wondered a little bit why. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-06 5:43 ` Gerd Möllmann @ 2022-10-07 4:11 ` Matt Armstrong 2022-10-07 4:34 ` Gerd Möllmann 0 siblings, 1 reply; 71+ messages in thread From: Matt Armstrong @ 2022-10-07 4:11 UTC (permalink / raw) To: Gerd Möllmann, Stefan Monnier; +Cc: emacs-devel Gerd Möllmann <gerd.moellmann@gmail.com> writes: > On 22-10-06 6:47 , Matt Armstrong wrote: > >> I see you removed some of the null->parent trick just today. I like >> that idea. It is realtively easy to use actual NULL instead of a >> sentinel NULL in tree algorithms, and I think on modern processors this >> works out for the better. > > Clang libstd++ uses NULL, BTW, and I already wondered a little bit why. I believe GNU libstdc++ does not use sentinel nodes either. I have yet to see see sentinel nodes used in an optimized tree implementation. I think in the context of this overlay work the performance difference is not very significant, since the code is doing a lot of other stuff while traversing the tree. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 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 15:34 ` Matt Armstrong 0 siblings, 2 replies; 71+ messages in thread From: Gerd Möllmann @ 2022-10-07 4:34 UTC (permalink / raw) To: Matt Armstrong; +Cc: Stefan Monnier, emacs-devel Matt Armstrong <matt@rfc20.org> writes: >> Clang libstd++ uses NULL, BTW, and I already wondered a little bit why. > > I believe GNU libstdc++ does not use sentinel nodes either. I have yet > to see see sentinel nodes used in an optimized tree implementation. I looked it up a few days ago, out of interest, and GCC's rb-tree uses sentinels, in Git master at least. Somewhere in this thread I posted where to look in GCC's Git repo, I can't remember ATM. (And I can't stop saying that it would be a big step forward for Emacs to allow C++. With std::multimap/std::multiset, we would have a ready-made complete solution for the tree, tested by a gazillion of users. Just dreaming :-)) > I think in the context of this overlay work the performance difference > is not very significant, since the code is doing a lot of other stuff > while traversing the tree. I agree. I think NULL could be better in multi-threaded cases, as Stefan alluded to. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-07 4:34 ` Gerd Möllmann @ 2022-10-07 13:33 ` Stefan Monnier 2022-10-07 14:29 ` Gerd Möllmann ` (2 more replies) 2022-10-07 15:34 ` Matt Armstrong 1 sibling, 3 replies; 71+ messages in thread From: Stefan Monnier @ 2022-10-07 13:33 UTC (permalink / raw) To: Gerd Möllmann; +Cc: Matt Armstrong, emacs-devel > to allow C++. With std::multimap/std::multiset, we would have a > ready-made complete solution for the tree, tested by a gazillion of > users. Just dreaming :-)) I'm not familiar with C++ libs: does this `multiset` lib offer something similar to the lazy update of buffer positions that Andreas's code uses (via the `offset` field together with the `interval_tree_inherit_offset` function)? >> I think in the context of this overlay work the performance difference >> is not very significant, since the code is doing a lot of other stuff >> while traversing the tree. > I agree. I think NULL could be better in multi-threaded cases, as > Stefan alluded to. The current code should be multi-thread safe (except for the global iterator, of course), despite its use of a global sentinel node: there's no need to synchronize anything when reading a read-only field or when writing a write-only field. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 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 14:56 ` Stefan Monnier 2022-10-07 15:59 ` Matt Armstrong 2 siblings, 1 reply; 71+ messages in thread From: Gerd Möllmann @ 2022-10-07 14:29 UTC (permalink / raw) To: Stefan Monnier; +Cc: Matt Armstrong, emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: >> to allow C++. With std::multimap/std::multiset, we would have a >> ready-made complete solution for the tree, tested by a gazillion of >> users. Just dreaming :-)) > > I'm not familiar with C++ libs: does this `multiset` lib offer something > similar to the lazy update of buffer positions that Andreas's code uses > (via the `offset` field together with the `interval_tree_inherit_offset` > function)? No, just the tree-part, or better said not the tree directly. These implement an abstraction of an ordered set, or multiset (containing an element more than once), or map of (key, value) pairs, or multimap (multiple (key, value) pairs with the same key. Because of the complexities that the C++ standard guarantees, libstc++ uses rb-trees for the implmentation. There are also unordered forms of these concepts, using hash-tables under the hood. https://cplusplus.com/reference/stl/ The rest would have to be built on top, using that. std::multimap::iterator for example, would be an in-order iteration over the key or (key, value) pairs. C++ iterators are modelled after pointers (++, --, *). And there are a number of algorithms defined in C++ like lower_bound, equal_range, upper_bound and so on that work on pointer-like iterators, or real C pointers for that matter. https://en.cppreference.com/w/cpp/algorithm That's probably not a good explanation, sorry :-/. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-07 14:29 ` Gerd Möllmann @ 2022-10-07 14:51 ` Stefan Monnier 2022-10-07 15:12 ` Gerd Möllmann 0 siblings, 1 reply; 71+ messages in thread From: Stefan Monnier @ 2022-10-07 14:51 UTC (permalink / raw) To: Gerd Möllmann; +Cc: Matt Armstrong, emacs-devel >>> to allow C++. With std::multimap/std::multiset, we would have a >>> ready-made complete solution for the tree, tested by a gazillion of >>> users. Just dreaming :-)) >> >> I'm not familiar with C++ libs: does this `multiset` lib offer something >> similar to the lazy update of buffer positions that Andreas's code uses >> (via the `offset` field together with the `interval_tree_inherit_offset` >> function)? > > No, just the tree-part, or better said not the tree directly. These > implement an abstraction of an ordered set, or multiset (containing an > element more than once), or map of (key, value) pairs, or multimap > (multiple (key, value) pairs with the same key. Then I don't see how it would be a "ready-made complete solution" because it doesn't seem easy to make such updates lazily "from the outside". Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-07 14:51 ` Stefan Monnier @ 2022-10-07 15:12 ` Gerd Möllmann 2022-10-07 17:12 ` Stefan Monnier 0 siblings, 1 reply; 71+ messages in thread From: Gerd Möllmann @ 2022-10-07 15:12 UTC (permalink / raw) To: Stefan Monnier; +Cc: Matt Armstrong, emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: > Then I don't see how it would be a "ready-made complete solution" > because it doesn't seem easy to make such updates lazily "from the > outside". I was anwering the context of the whole thread on the bug list, which partly landed on emacs-devel. Where "tree" meant what we talked about implementing a successor function instead of a stateful stack that was being used at the time, and so on, and so on. Whatever. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-07 15:12 ` Gerd Möllmann @ 2022-10-07 17:12 ` Stefan Monnier 0 siblings, 0 replies; 71+ messages in thread From: Stefan Monnier @ 2022-10-07 17:12 UTC (permalink / raw) To: Gerd Möllmann; +Cc: Matt Armstrong, emacs-devel Gerd Möllmann [2022-10-07 17:12:04] wrote: > Stefan Monnier <monnier@iro.umontreal.ca> writes: >> Then I don't see how it would be a "ready-made complete solution" >> because it doesn't seem easy to make such updates lazily "from the >> outside". > > I was anwering the context of the whole thread on the bug list, which > partly landed on emacs-devel. Where "tree" meant what we talked about > implementing a successor function instead of a stateful stack that was > being used at the time, and so on, and so on. Ah, duh, sorry, Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-07 13:33 ` Stefan Monnier 2022-10-07 14:29 ` Gerd Möllmann @ 2022-10-07 14:56 ` Stefan Monnier 2022-10-07 15:59 ` Matt Armstrong 2 siblings, 0 replies; 71+ messages in thread From: Stefan Monnier @ 2022-10-07 14:56 UTC (permalink / raw) To: Gerd Möllmann; +Cc: Matt Armstrong, emacs-devel >>> I think in the context of this overlay work the performance difference >>> is not very significant, since the code is doing a lot of other stuff >>> while traversing the tree. >> I agree. I think NULL could be better in multi-threaded cases, as >> Stefan alluded to. > > The current code should be multi-thread safe (except for the global > iterator, of course), despite its use of a global sentinel node: there's > no need to synchronize anything when reading a read-only field or when > writing a write-only field. But indeed, while it might be safe, its performance in a multi-threaded situation (with multiple cores) might be affected, because of the cache-line granularity and because the CPUs can't know that those writes can be reordered (or even thrown away) since they will never be matched by a corresponding read. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-07 13:33 ` Stefan Monnier 2022-10-07 14:29 ` Gerd Möllmann 2022-10-07 14:56 ` Stefan Monnier @ 2022-10-07 15:59 ` Matt Armstrong 2 siblings, 0 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-07 15:59 UTC (permalink / raw) To: Stefan Monnier, Gerd Möllmann; +Cc: emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: >> to allow C++. With std::multimap/std::multiset, we would have a >> ready-made complete solution for the tree, tested by a gazillion of >> users. Just dreaming :-)) > > I'm not familiar with C++ libs: does this `multiset` lib offer something > similar to the lazy update of buffer positions that Andreas's code uses > (via the `offset` field together with the `interval_tree_inherit_offset` > function)? The ordered collections in the C++ standard library do not support augmentation. User code isn't allowed to see the left, right, parent pointers, for example, nor are there "hooks" that allow running user code as the tree is traversed or modified. The "treeness" of the collection is abstracted away entirely. The kind of augmentation used by noverly is quite bespoke, and I think it justifies a bespoke implementation. It is "augmented" both bottom up (the LIMIT field) and top down (the OFFSET field), and requires specific handling of each. There are enticing features in C++, but I don't think an off-the-shelf solution to the overlay problem, suitable for use in Emacs (e.g. licensing), is one of them. I like C++ quite a lot, and have decades of using it professionally, but I'd still be very cautious using it in Emacs. For one, Emacs aims to support all sorts of relatively obscure compilers on little used platforms. C++ tends to reduce what a program is portable to, though admitedly it is much more portable in practice than most "modern" programming languages. >>> I think in the context of this overlay work the performance difference >>> is not very significant, since the code is doing a lot of other stuff >>> while traversing the tree. >> I agree. I think NULL could be better in multi-threaded cases, as >> Stefan alluded to. > > The current code should be multi-thread safe (except for the global > iterator, of course), despite its use of a global sentinel node: there's > no need to synchronize anything when reading a read-only field or when > writing a write-only field. Pedantically, read-only fields do need synchronization with respect to previous writers. As for writing, you do need synchronization of writes to avoid undefined behavior. The memory model doesn't allow for unsynchronized writes. Anyway, neither is happening here. If this global sentinel node is never modified then it isn't ever used, so there can't be a synchronization problem! It is just there to generate a unique constant address that the tree code uses as a "null" value without actually using NULL. I think it is unusual and unecessary, but it is thread safe. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-07 4:34 ` Gerd Möllmann 2022-10-07 13:33 ` Stefan Monnier @ 2022-10-07 15:34 ` Matt Armstrong 1 sibling, 0 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-07 15:34 UTC (permalink / raw) To: Gerd Möllmann; +Cc: Stefan Monnier, emacs-devel Gerd Möllmann <gerd.moellmann@gmail.com> writes: > Matt Armstrong <matt@rfc20.org> writes: > >>> Clang libstd++ uses NULL, BTW, and I already wondered a little bit why. >> >> I believe GNU libstdc++ does not use sentinel nodes either. I have yet >> to see see sentinel nodes used in an optimized tree implementation. > > I looked it up a few days ago, out of interest, and GCC's rb-tree uses > sentinels, in Git master at least. Somewhere in this thread I posted > where to look in GCC's Git repo, I can't remember ATM. It uses a sentinel for the parent of the root node (a "header") but it does not use a sentinel node for null at the leaves. The easiest way to see it is by looking at the code for GNU libstdc++ "minimum()" algorithm on trees, which walks left ponters down to a leaf node: https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/include/bits/stl_tree.h;h=a4de61417652a288e361a55fcc8bb7a9838c58a5;hb=HEAD#l112 _S_minimum(_Base_ptr __x) _GLIBCXX_NOEXCEPT { while (__x->_M_left != 0) __x = __x->_M_left; return __x; } For the header node, see https://gcc.gnu.org/git?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/include/bits/stl_tree.h;h=a4de61417652a288e361a55fcc8bb7a9838c58a5;hb=HEAD#l89 ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-06 4:47 ` Matt Armstrong 2022-10-06 5:43 ` Gerd Möllmann @ 2022-10-06 12:08 ` Stefan Monnier 1 sibling, 0 replies; 71+ messages in thread From: Stefan Monnier @ 2022-10-06 12:08 UTC (permalink / raw) To: Matt Armstrong; +Cc: Gerd Möllmann, emacs-devel > I see you removed some of the null->parent trick just today. I like > that idea. It is realtively easy to use actual NULL instead of a > sentinel NULL in tree algorithms, and I think on modern processors this > works out for the better. The change I made only made it so the sentinel's fields are either read-only or write-only, which means the sentinel is not used to propagate information and is thus not a global state. Whether using NULL is worse or better than using a sentinel, I don't know, and I think the current code is fine in this respect. If someone wants to change it to use NULL, feel free, but it's not on my todo list. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-25 22:38 noverlay branch Stefan Monnier ` (2 preceding siblings ...) 2022-09-27 5:12 ` Matt Armstrong @ 2022-09-27 8:39 ` Gerd Möllmann 2022-09-27 9:38 ` Eli Zaretskii 2022-10-06 20:41 ` Matt Armstrong ` (2 subsequent siblings) 6 siblings, 1 reply; 71+ messages in thread From: Gerd Möllmann @ 2022-09-27 8:39 UTC (permalink / raw) To: monnier; +Cc: emacs-devel Should we report bugs to bug-gnu-emacs? And, should we fix things on the "noverlay" branch directly? Or send patches? It doesn't build on macOS, for instance, because that doesn't like the member named "nil" in struct interval. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-27 8:39 ` Gerd Möllmann @ 2022-09-27 9:38 ` Eli Zaretskii 0 siblings, 0 replies; 71+ messages in thread From: Eli Zaretskii @ 2022-09-27 9:38 UTC (permalink / raw) To: Gerd Möllmann; +Cc: monnier, emacs-devel > Date: Tue, 27 Sep 2022 10:39:22 +0200 > Cc: emacs-devel@gnu.org > From: Gerd Möllmann <gerd.moellmann@gmail.com> > > Should we report bugs to bug-gnu-emacs? Yes, please. Just mention the branch. > And, should we fix things on the "noverlay" branch directly? Yes, please. Thanks. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-25 22:38 noverlay branch Stefan Monnier ` (3 preceding siblings ...) 2022-09-27 8:39 ` Gerd Möllmann @ 2022-10-06 20:41 ` Matt Armstrong 2022-10-07 16:51 ` Matt Armstrong 2022-10-23 4:49 ` Matt Armstrong 6 siblings, 0 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-06 20:41 UTC (permalink / raw) To: Stefan Monnier, emacs-devel [-- Attachment #1: Type: text/plain, Size: 1392 bytes --] Stefan Monnier <monnier@iro.umontreal.ca> writes: > I just updated the noverlay branch to the code in `master` (most of it > was mechanical except for the fact that since the last commit on that > branch we have gotten rid of Lisp_Misc and we moved to pdumper). > > I'm getting ready to install this in `master`, so I'd encourage people > to try this code as much as possible to try and weed out the most > glaring problems before it hits master. The code generally looks good, > but it touches some quite "core" code in the redisplay (with lots of > off-by-one opportunities) and in the memory management (with > opportunities for crashes and memory leaks). > > For those who're not familiar with this branch, it changes the way > overlays are stored in a buffer: instead of keeping them in naïve > singly-linked lists, it keeps them in balanced binary trees, so as to > replace an O(N) complexity with O(log N). This way Emacs should not get > sluggish even with millions of overlays (of course, if a given buffer > position is covered by a million overlays, it'll still be sluggish when > operating around that position). > > > Stefan Stefan, I don't have Emacs commit so I've attached comment improvement patches here. Lemme know if you'd prefer this occur on a bug. These commits are also on https://git.sr.ht/~matta/emacs/log/feature/noverlay [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-src-itree.h-include-lisp.h-for-Lisp_Object.patch --] [-- Type: text/x-diff, Size: 951 bytes --] From 6dff825a9943434cfccd64916c506ab10977acf8 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Thu, 6 Oct 2022 09:36:24 -0700 Subject: [PATCH 1/4] ; * src/itree.h: include "lisp.h" for Lisp_Object --- src/itree.c | 2 +- src/itree.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/itree.c b/src/itree.c index ed31ef1156..a782410860 100644 --- a/src/itree.c +++ b/src/itree.c @@ -19,7 +19,7 @@ Copyright (C) 2017-2022 Free Software Foundation, Inc. #include <config.h> #include <math.h> -#include "lisp.h" + #include "itree.h" /* diff --git a/src/itree.h b/src/itree.h index 8f6bb667d6..9b79551f77 100644 --- a/src/itree.h +++ b/src/itree.h @@ -23,6 +23,8 @@ #define ITREE_H #include <stddef.h> #include <inttypes.h> +#include "lisp.h" + /* The tree and node structs are mainly here, so they can be allocated. NOTE: The only time where it is safe to modify node.begin and -- 2.35.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-src-itree.h-struct-interval_node-document-field-inva.patch --] [-- Type: text/x-diff, Size: 3314 bytes --] From 4048988f24c60104e6658b164a34df752f7b6167 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Thu, 6 Oct 2022 13:05:19 -0700 Subject: [PATCH 2/4] ; * src/itree.h (struct interval_node): document field invariants. --- src/itree.h | 58 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/itree.h b/src/itree.h index 9b79551f77..52219a8eef 100644 --- a/src/itree.h +++ b/src/itree.h @@ -25,22 +25,62 @@ #define ITREE_H #include "lisp.h" -/* The tree and node structs are mainly here, so they can be allocated. - - NOTE: The only time where it is safe to modify node.begin and - node.end directly, is while the node is not part of any tree. - - NOTE: It is safe to read node.begin and node.end directly, if the - node came from a generator, because it validates the nodes it - returns as a side-effect. -*/ +/* NOTE: the fields of the node and tree structs should for the most + * part be treated as opaque to the rest of Emacs. Exceptions are + * noted in comments. They are in the header file so they can be + * allocated. + */ struct interval_node; struct interval_node { + /* The normal parent, left and right links found in binary trees. + See also `red`, below, which completes the Red-Black tree + representation. */ struct interval_node *parent; struct interval_node *left; struct interval_node *right; + + /* The following five fields comprise the interval abstraction. + + BEGIN, END are buffer positions. BEGIN and END are the beginning + and end of this interval. These form an inclusive, exlusive (or + closed, open) range of buffer positions [BEGIN..END). When a + node is in a tree these fields are read only, written only by + itree functions. + + The LIMIT, OFFSET and OTICK fields should be considered internal + to itree.c and used only by itree functions. + + LIMIT is a buffer position, the maximum of END of this node and + its children. See itree.c for its use. + + OFFSET is in buffer position units, and will be non-zero only + when the node is dirty. + + OTICK determines whether BEGIN, END, LIMIT and OFFSET are + considered dirty. A node is clean when its OTICK is equal to the + OTICK of its tree (see struct interval_tree). Otherwise, it is + dirty. + + In a clean node, BEGIN, END and LIMIT are correct buffer + positions, and OFFSET is zero. The parent of a clean node is + clean also clean, recursively. + + In a dirty node, the node's OTICK won't equal its tree's OTICK, + and its OFFSET may be non-zero. At all times the descendents of + a dirty node are also dirty. BEGIN, END and LIMIT require + adjustment before use as buffer positions. + + NOTE: BEGIN and END must not be modified while the node is part + of a tree. Use interval_tree_insert_gap and + interval_tree_delete_gap instead. + + NOTE: The interval generators ensure nodes are clean before + yielding them, so BEGIN and END may be safely used as buffer + positions then. + */ + ptrdiff_t begin; /* The beginning of this interval. */ ptrdiff_t end; /* The end of the interval. */ ptrdiff_t limit; /* The maximum end in this subtree. */ -- 2.35.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #4: 0003-src-itree.c-change-comments-for-clarity.patch --] [-- Type: text/x-diff, Size: 2513 bytes --] From 52470aa06f5f037358d957271101b8dbaa3f44e7 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Thu, 6 Oct 2022 13:12:54 -0700 Subject: [PATCH 3/4] ; * src/itree.c: change comments for clarity. --- src/itree.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/itree.c b/src/itree.c index a782410860..3098fe1cf4 100644 --- a/src/itree.c +++ b/src/itree.c @@ -24,7 +24,7 @@ Copyright (C) 2017-2022 Free Software Foundation, Inc. /* Intervals of the form [BEGIN, END), are stored as nodes inside a RB - tree, sorted by BEGIN . The core operation of this tree (besides + tree, ordered by BEGIN. The core operation of this tree (besides insert, remove, etc.) is finding all intervals intersecting with some given interval. In order to perform this operation efficiently, every node stores a third value called LIMIT. (See @@ -65,10 +65,10 @@ Copyright (C) 2017-2022 Free Software Foundation, Inc. ==== Adjusting intervals ==== Since this data-structure will be used for overlays in an Emacs - buffer, a second core operation implements the ability to insert or - delete gaps in resp. from the tree. This models the insertion - resp. deletion of text in a buffer and the effects it may have on - the positions of overlays. + buffer, a second core operation is the ability to insert and delete + gaps in the tree. This models the insertion and deletion of text + in a buffer and the effects it may have on the positions of + overlays. Consider this: Something gets inserted at position P into a buffer and assume that all overlays occur strictly after P. Ordinarily, @@ -79,10 +79,10 @@ Copyright (C) 2017-2022 Free Software Foundation, Inc. The OFFSET of some some subtree, represented by its root, is the amount of shift that needs to be applied to its BEGIN, END and - LIMIT values, in order to get to the real values. Coming back to - the example, all we would need to do in this case, is to increment - the OFFSET of the tree's root, without any traversal of the tree - itself. + LIMIT values, in order to get to the actual buffer positions. + Coming back to the example, all we would need to do in this case, + is to increment the OFFSET of the tree's root, without any + traversal of the tree itself. As a consequence, the real values of BEGIN, END and LIMIT of some NODE need to be computed by incrementing them by the sum of NODE's -- 2.35.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #5: 0004-Use-a-bool-instead-of-a-bitfield.patch --] [-- Type: text/x-diff, Size: 744 bytes --] From 53238b6c16f4dc3dec8d60205e88f5f79200d187 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Thu, 6 Oct 2022 13:18:46 -0700 Subject: [PATCH 4/4] Use a bool instead of a bitfield * src/itree.c (struct interval_generator): use a bool instead of a bitfield, since space is not an issue. --- src/itree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/itree.c b/src/itree.c index 3098fe1cf4..79e39d6e2a 100644 --- a/src/itree.c +++ b/src/itree.c @@ -145,7 +145,7 @@ null_is_sane (void) ptrdiff_t end; uintmax_t otick; /* A copy of the tree's `otick`. */ enum interval_tree_order order; - bool_bf running : 1; + bool running; const char* file; int line; }; -- 2.35.1 ^ permalink raw reply related [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-25 22:38 noverlay branch Stefan Monnier ` (4 preceding siblings ...) 2022-10-06 20:41 ` Matt Armstrong @ 2022-10-07 16:51 ` Matt Armstrong 2022-10-07 18:28 ` Stefan Monnier 2022-10-23 4:49 ` Matt Armstrong 6 siblings, 1 reply; 71+ messages in thread From: Matt Armstrong @ 2022-10-07 16:51 UTC (permalink / raw) To: Stefan Monnier, emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: > I just updated the noverlay branch to the code in `master` (most of it > was mechanical except for the fact that since the last commit on that > branch we have gotten rid of Lisp_Misc and we moved to pdumper). > > I'm getting ready to install this in `master`, so I'd encourage people > to try this code as much as possible to try and weed out the most > glaring problems before it hits master. Stefan (and others), where are we at with this? Are there things that must happen before a merge? Is anybody able to use the noverlay branch for daily work? I'm running the noverlay branch now and was observing an eassume failure. I recompiled without optimization and with Address Sanitizer and ran Emacs under GDB, but haven't seen the crash since... No crashes so far, despite doing substantially the same things. I am hoping it is not an opt only bug. If I isolate anything concrete I'll file a bug. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-07 16:51 ` Matt Armstrong @ 2022-10-07 18:28 ` Stefan Monnier 2022-10-08 23:33 ` Matt Armstrong 0 siblings, 1 reply; 71+ messages in thread From: Stefan Monnier @ 2022-10-07 18:28 UTC (permalink / raw) To: Matt Armstrong; +Cc: emacs-devel Matt Armstrong [2022-10-07 09:51:32] wrote: > Stefan Monnier <monnier@iro.umontreal.ca> writes: >> I just updated the noverlay branch to the code in `master` (most of it >> was mechanical except for the fact that since the last commit on that >> branch we have gotten rid of Lisp_Misc and we moved to pdumper). >> >> I'm getting ready to install this in `master`, so I'd encourage people >> to try this code as much as possible to try and weed out the most >> glaring problems before it hits master. > > Stefan (and others), where are we at with this? Are there things that > must happen before a merge? I'm mainly waiting to hear feedback (including my own) about "I use it with no visible problem". > Is anybody able to use the noverlay branch for daily work? I do. > I'm running the noverlay branch now and was observing an eassume > failure. Do you have the file&line number at least? > I recompiled without optimization and with Address Sanitizer > and ran Emacs under GDB, but haven't seen the crash since... More important than that is the ENABLE_CHECKING (which you apparently was using already anyway) and running under a debugger: even if optimizations may throw GDB off a bit, it's usually good enough to make progress on the bug (sometimes by adding more assertions or printfs to make up (next time around) for the lack of info provided by GDB). Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-07 18:28 ` Stefan Monnier @ 2022-10-08 23:33 ` Matt Armstrong 2022-10-09 3:44 ` Matt Armstrong ` (2 more replies) 0 siblings, 3 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-08 23:33 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 1541 bytes --] Stefan Monnier <monnier@iro.umontreal.ca> writes: > Matt Armstrong [2022-10-07 09:51:32] wrote: >> Stefan Monnier <monnier@iro.umontreal.ca> writes: >>> I just updated the noverlay branch to the code in `master` (most of it >>> was mechanical except for the fact that since the last commit on that >>> branch we have gotten rid of Lisp_Misc and we moved to pdumper). >>> >>> I'm getting ready to install this in `master`, so I'd encourage people >>> to try this code as much as possible to try and weed out the most >>> glaring problems before it hits master. >> >> Stefan (and others), where are we at with this? Are there things that >> must happen before a merge? > > I'm mainly waiting to hear feedback (including my own) about "I use it > with no visible problem". I like that criteria. :) >> Is anybody able to use the noverlay branch for daily work? > > I do. > >> I'm running the noverlay branch now and was observing an eassume >> failure. > > Do you have the file&line number at least? Not the specific eassert because...reasons. Long story short: I can debug eassert failures now, but not then. In any case, I do have a new test for tests/src/buffer-tests.el that crashes feature/noverlay Emacs immediately, when ENABLE_CHECKING is on, in a spot in itree.c having to do with offset inheritance. Two patches, also on https://git.sr.ht/~matta/emacs/log/feature/noverlay as before. I'll work on fixing the crash next, but wanted to get the test in because it takes an...interesting approach...that may require discussion. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-Comment-change-explain-inheriting-dirty-offsets.patch --] [-- Type: text/x-diff, Size: 1510 bytes --] From 87204feaa4f50744701481f3aa051483647cf9da Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Sat, 8 Oct 2022 09:15:26 -0700 Subject: [PATCH 1/2] Comment change: explain inheriting "dirty" offsets ; * src/itree.c (interval_generator_next): explain why the code handles inheriting offsets from dirty nodes. --- src/itree.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/itree.c b/src/itree.c index de16af5b0c..1fc711b021 100644 --- a/src/itree.c +++ b/src/itree.c @@ -1086,9 +1086,17 @@ interval_tree_inherit_offset (uintmax_t otick, struct interval_node *node) node->right->offset += node->offset; node->offset = 0; } - /* FIXME: I wonder when/why this condition can be false, and more generally - why we'd want to propagate offsets that may not be fully up-to-date. */ - if (node->parent == ITREE_NULL || node->parent->otick == otick) + /* FIXME: I wonder when/why this condition can be false, and more + generally why we'd want to propagate offsets that may not be + fully up-to-date. --stef + + 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. We could fix this by + always inheriting offsets downward from the root for every insert + and remove. --matt + */ node->otick = otick; } -- 2.35.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-test-src-buffer-tests.el-test-overlay-randomly-new-t.patch --] [-- Type: text/x-diff, Size: 4982 bytes --] From 89ed5cbee03cce6c621af6570d2c4411e7586f9d Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Sat, 8 Oct 2022 09:28:29 -0700 Subject: [PATCH 2/2] ; * test/src/buffer-tests.el (test-overlay-randomly): new test. --- test/src/buffer-tests.el | 92 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/test/src/buffer-tests.el b/test/src/buffer-tests.el index a12d15bc79..46e57289a1 100644 --- a/test/src/buffer-tests.el +++ b/test/src/buffer-tests.el @@ -1508,6 +1508,98 @@ test-overlay-multibyte-transition-2 (ovshould nonempty-eob-end 4 5) (ovshould empty-eob 5 5))))) +(ert-deftest test-overlay-randomly () + "Exercise overlay code, but perform few assertions. + +This test works best when Emacs is configured with +--enable-checking=yes. This is a little bit like fuzz testing, +except this test has no way to reduce to a minimal failng test +case. Regardless, by exercising many corner cases bugs can be +found using Emacs' internal consistency assertions." + (let* ( + ;; The size and slack for the test buffer size. + (buffer-size-target 1000) + (buffer-size-slack 200) + + ;; Use up to 100 overlays. We need not use more to observe + ;; reasonable variation in the overlay data structures. + (overlay-count-limit 100) + + ;; This test maintains a vector of overlays. Each iteration + ;; may append or erase one overlay. + (overlays (make-vector overlay-count-limit nil)) + (overlay-count 0) + + ;; The test is either slowly growing or shrinking the overlay + ;; count. Deletions still occur while growing, and additions + ;; still occur while shrinking. The GROWING variable only + ;; controls the relative probability of doing one or the + ;; other. + (growing t) + + ;; Loop up to 100K times. + (iteration-count 0) + (iteration-target 100000)) + (with-temp-buffer + (while (< (buffer-size) buffer-size-target) + (insert "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem +accusantium doloremque laudantium, totam rem aperiam eaque ipsa, +quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt, explicabo. ")) + + (while (< iteration-count iteration-target) + (cl-incf iteration-count) + + ;; Toggle GROWING if we've reached a size boundary. The idea + ;; is to initially steadily increase the overlay count, then + ;; steadily decrease it, then repeat. + (when (and growing (= overlay-count overlay-count-limit)) + (message "now shrinking") + (setq growing nil)) + (when (and (not growing) (= overlay-count 0)) + (message "now growing") + (setq growing t)) + + ;; Create or delete a random overlay according to a + ;; probability chosen by GROWING. + (let ((create-overlay (>= (random 100) (if growing 40 60)))) + (cond + ;; Possibly create a new overlay in a random place in the + ;; buffer. We have two easy choices. We can choose the + ;; overlay BEGIN randomly, then choose its END among the + ;; valid remaining buffer posiitions. Or we could choose + ;; the overlay width randomly, then choose a valid BEGIN. + ;; We take the former approach, because the overlay data + ;; structure is ordered primarily by BEGIN. + ((and create-overlay (< overlay-count overlay-count-limit)) + (let* ((begin (random (buffer-size))) + (end (+ begin (random (- (buffer-size) begin)))) + (ov (make-overlay begin end nil + (= 0 (random 2)) (= 0 (random 2))))) + (aset overlays overlay-count ov) + (cl-incf overlay-count))) + ((and (not create-overlay) (> overlay-count 0)) + + ;; Possibly delete a random overlay. + (let* ((last-index (1- overlay-count)) + (index (random overlay-count)) + (ov (aref overlays index))) + (when (< index last-index) + (aset overlays index (aref overlays last-index))) + (aset overlays last-index nil) + (cl-decf overlay-count) + (delete-overlay ov))))) + + ;; Modify the buffer on occasion, which exercises the + ;; insert/remove gap logic in the overlay implementation. + (when (and (< (buffer-size) (+ buffer-size-target buffer-size-slack)) + (zerop (random 10))) + (goto-char (1+ (random (buffer-size)))) + (insert (+ ?a (random 26)))) + (when (and (> (buffer-size) (- buffer-size-target buffer-size-slack)) + (zerop (random 10))) + (goto-char (1+ (random (buffer-size)))) + (delete-char 1)))))) \f -- 2.35.1 ^ permalink raw reply related [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-08 23:33 ` Matt Armstrong @ 2022-10-09 3:44 ` Matt Armstrong 2022-10-09 4:12 ` Stefan Monnier 2022-10-09 23:47 ` Stefan Monnier 2 siblings, 0 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-09 3:44 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 1684 bytes --] Matt Armstrong <matt@rfc20.org> writes: > From 87204feaa4f50744701481f3aa051483647cf9da Mon Sep 17 00:00:00 2001 > From: Matt Armstrong <matt@rfc20.org> > Date: Sat, 8 Oct 2022 09:15:26 -0700 > Subject: [PATCH 1/2] Comment change: explain inheriting "dirty" offsets > > ; * src/itree.c (interval_generator_next): explain why the code > handles inheriting offsets from dirty nodes. > --- > src/itree.c | 14 +++++++++++--- > 1 file changed, 11 insertions(+), 3 deletions(-) > > diff --git a/src/itree.c b/src/itree.c > index de16af5b0c..1fc711b021 100644 > --- a/src/itree.c > +++ b/src/itree.c > @@ -1086,9 +1086,17 @@ interval_tree_inherit_offset (uintmax_t otick, struct interval_node *node) > node->right->offset += node->offset; > node->offset = 0; > } > - /* FIXME: I wonder when/why this condition can be false, and more generally > - why we'd want to propagate offsets that may not be fully up-to-date. */ > - if (node->parent == ITREE_NULL || node->parent->otick == otick) > + /* FIXME: I wonder when/why this condition can be false, and more > + generally why we'd want to propagate offsets that may not be > + fully up-to-date. --stef > + > + 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. We could fix this by > + always inheriting offsets downward from the root for every insert > + and remove. --matt > + */ > node->otick = otick; > } Correction to the above patch, where I inadvertently deleted a line of code: [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-Comment-change-explain-inheriting-dirty-offsets.patch --] [-- Type: text/x-diff, Size: 1507 bytes --] From 30f52202775155c1d301af3634d0122c3d7851f8 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Sat, 8 Oct 2022 09:15:26 -0700 Subject: [PATCH 1/3] Comment change: explain inheriting "dirty" offsets ; * src/itree.c (interval_generator_next): explain why the code handles inheriting offsets from dirty nodes. --- src/itree.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/itree.c b/src/itree.c index de16af5b0c..05851007f5 100644 --- a/src/itree.c +++ b/src/itree.c @@ -1086,8 +1086,17 @@ interval_tree_inherit_offset (uintmax_t otick, struct interval_node *node) node->right->offset += node->offset; node->offset = 0; } - /* FIXME: I wonder when/why this condition can be false, and more generally - why we'd want to propagate offsets that may not be fully up-to-date. */ + /* FIXME: I wonder when/why this condition can be false, and more + generally why we'd want to propagate offsets that may not be + fully up-to-date. --stef + + 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. We could fix this by + always inheriting offsets downward from the root for every insert + and remove. --matt + */ if (node->parent == ITREE_NULL || node->parent->otick == otick) node->otick = otick; } -- 2.35.1 ^ permalink raw reply related [flat|nested] 71+ messages in thread
* Re: noverlay branch 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-09 23:47 ` Stefan Monnier 2 siblings, 1 reply; 71+ messages in thread From: Stefan Monnier @ 2022-10-09 4:12 UTC (permalink / raw) To: Matt Armstrong; +Cc: emacs-devel > In any case, I do have a new test for tests/src/buffer-tests.el that > crashes feature/noverlay Emacs immediately, when ENABLE_CHECKING is on, > in a spot in itree.c having to do with offset inheritance. (Not?) great! > Two patches, also on https://git.sr.ht/~matta/emacs/log/feature/noverlay > as before. Merged (along with another that you apparently pushed since). > I'll work on fixing the crash next, but wanted to get the > test in because it takes an...interesting approach...that may require > discussion. I'm fine with the random testing approach (I'd be happy to see something like `propcheck` used in our testing rig). Currently, our test suite is not really setup to test Emacs crashes very well, so it's not ideal. Still: better than nothing. > @@ -1086,9 +1086,17 @@ interval_tree_inherit_offset (uintmax_t otick, struct interval_node *node) > node->right->offset += node->offset; > node->offset = 0; > } > - /* FIXME: I wonder when/why this condition can be false, and more generally > - why we'd want to propagate offsets that may not be fully up-to-date. */ > - if (node->parent == ITREE_NULL || node->parent->otick == otick) > + /* FIXME: I wonder when/why this condition can be false, and more > + generally why we'd want to propagate offsets that may not be > + fully up-to-date. --stef > + > + 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. We could fix this by > + always inheriting offsets downward from the root for every insert > + and remove. --matt > + */ > node->otick = otick; > } Indeed, I saw later in the remove code that we do propagate offsets without caring if they're up-to-date, and I think the logic behind that is sound. And indeed we can drop the test because the only property we only ever care about for an `otick` is whether it's equal to `tree->otick`, so if node->parent->otick != otick then performing the assignment is equivalent to not performing it. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-09 4:12 ` Stefan Monnier @ 2022-10-09 15:34 ` Stefan Monnier 2022-10-10 2:57 ` Matt Armstrong 0 siblings, 1 reply; 71+ messages in thread From: Stefan Monnier @ 2022-10-09 15:34 UTC (permalink / raw) To: Matt Armstrong; +Cc: emacs-devel >> node->otick = otick; > > Indeed, I saw later in the remove code that we do propagate offsets > without caring if they're up-to-date, and I think the logic behind that > is sound. And indeed we can drop the test because the only property we > only ever care about for an `otick` is whether it's equal to > `tree->otick`, so if node->parent->otick != otick then performing the > assignment is equivalent to not performing it. The above isn't quite right: I was thinking of `node->otick = parent->otick`. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-09 15:34 ` Stefan Monnier @ 2022-10-10 2:57 ` Matt Armstrong 2022-10-10 6:24 ` Eli Zaretskii 2022-10-10 14:44 ` Stefan Monnier 0 siblings, 2 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-10 2:57 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 1809 bytes --] Stefan Monnier <monnier@iro.umontreal.ca> writes: >>> node->otick = otick; >> >> Indeed, I saw later in the remove code that we do propagate offsets >> without caring if they're up-to-date, and I think the logic behind that >> is sound. And indeed we can drop the test because the only property we >> only ever care about for an `otick` is whether it's equal to >> `tree->otick`, so if node->parent->otick != otick then performing the >> assignment is equivalent to not performing it. > > The above isn't quite right: > I was thinking of `node->otick = parent->otick`. Hey Stefan, yep, I'll have to check out the changes you made today. 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. 2) There is no tree->otick, just tree->root->otick. That is what is incremented when offsets are added. 3) All downward tree traversal propagates offsets and otick. This strikes me as conceptually simpler. E.g. since the invariant is defined recursively it might allow for more local reasoning, clearer assertions, less places where "offset math" is needed, etc. But you've lived in this code a bit longer than me. What do you think? I have a commit halfway worked up so maybe I can show that if it works out... Anyway, thanks for fixing my "thinko" bug in the check_tree code -- I had the fix but should have sent it faster. I didn't actually intend for that to be commited yet (pushed it to sr.ht by accident), but maybe it helped you chase down the bugs you fixed today? I have two patches, mostly to add more checks to the invariant checking in check_tree. Tests on noverlay are passing again as of your commits today, which is great! On https://git.sr.ht/~matta/emacs/log/feature/noverlay but also below. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-test-src-buffer-tests.el-Remove-unecessary-message-c.patch --] [-- Type: text/x-diff, Size: 958 bytes --] From 246acbddbeb3e9a390fe78242259182af0c2cc18 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Sun, 9 Oct 2022 10:12:32 -0700 Subject: [PATCH 1/2] ; * test/src/buffer-tests.el: Remove unecessary `message' calls. --- test/src/buffer-tests.el | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/src/buffer-tests.el b/test/src/buffer-tests.el index 01780a15cc..9bccbdf2e8 100644 --- a/test/src/buffer-tests.el +++ b/test/src/buffer-tests.el @@ -1554,10 +1554,8 @@ test-overlay-randomly ;; is to initially steadily increase the overlay count, then ;; steadily decrease it, then repeat. (when (and growing (= overlay-count overlay-count-limit)) - (message "now shrinking") (setq growing nil)) (when (and (not growing) (= overlay-count 0)) - (message "now growing") (setq growing t)) ;; Create or delete a random overlay according to a -- 2.35.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-Add-.-configure-enable-checking-overlays.patch --] [-- Type: text/x-diff, Size: 9911 bytes --] From 1d40c60265c34caaf9d8f5ad0c66322e7281902d Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Sun, 9 Oct 2022 19:42:14 -0700 Subject: [PATCH 2/2] Add ./configure --enable-checking=overlays This enables expensive validation checks of overlay data structures. Some are too expensive to turn on by default with --enable-checking=all since they turn O(log N) algorithms into O(N) algorithms. * src/itree.c (ENABLE_OVERLAY_CHECKING): define to 0 if undefined. (struct check_subtree_result): new struct. (check_subtree): renamed from recurse_check_tree, modified to use check_subtree_result and check more invariants. (check_tree): use check_subtree_result. * configure.ac: add support for ENABLE_OVERLAY_CHECKING. --- configure.ac | 11 +++- src/itree.c | 164 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 148 insertions(+), 27 deletions(-) diff --git a/configure.ac b/configure.ac index 8ba52a492b..42a55c7572 100644 --- a/configure.ac +++ b/configure.ac @@ -582,7 +582,7 @@ AC_DEFUN enable only specific categories of checks. Categories are: all,yes,no. Flags are: stringbytes, stringoverrun, stringfreelist, - structs, glyphs])], + structs, glyphs, overlays])], [ac_checking_flags="${enableval}"],[]) IFS="${IFS= }"; ac_save_IFS="$IFS"; IFS="$IFS," CHECK_STRUCTS=false @@ -596,7 +596,8 @@ AC_DEFUN ac_gc_check_stringbytes= ; ac_gc_check_string_overrun= ; ac_gc_check_string_free_list= ; - ac_glyphs_debug= ;; + ac_glyphs_debug= ; + ac_enable_overlay_checking= ;; all) ac_enable_checking=1 ; CHECK_STRUCTS=true ac_gc_check_stringbytes=1 ; @@ -609,6 +610,7 @@ AC_DEFUN stringfreelist) ac_gc_check_string_free_list=1 ;; structs) CHECK_STRUCTS=true ;; glyphs) ac_glyphs_debug=1 ;; + overlays) ac_enable_overlay_checking=1 ;; *) AC_MSG_ERROR([unknown check category $check]) ;; esac done @@ -645,6 +647,11 @@ AC_DEFUN AC_DEFINE([GLYPH_DEBUG], [1], [Define this to enable glyphs debugging code.]) fi +if test x$ac_enable_overlay_checking != x ; then + AC_DEFINE([ENABLE_OVERLAY_CHECKING], [1], +[Define this temporarily to hunt a bug. If defined, expensive + invariant checking of the overlay data structures is enabled.]) +fi dnl The name of this option is unfortunate. It predates, and has no dnl relation to, the "sampling-based elisp profiler" added in 24.3. diff --git a/src/itree.c b/src/itree.c index bbab70dac7..31a7e01090 100644 --- a/src/itree.c +++ b/src/itree.c @@ -144,6 +144,10 @@ Copyright (C) 2017-2022 Free Software Foundation, Inc. static struct interval_generator* interval_generator_create (struct interval_tree *); static void interval_tree_insert (struct interval_tree *, struct interval_node *); +#ifndef ENABLE_OVERLAY_CHECKING +#define ENABLE_OVERLAY_CHECKING 0 +#endif + /* The sentinel node, the null node. */ struct interval_node itree_null; @@ -205,14 +209,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,12 +255,23 @@ 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); @@ -240,18 +285,44 @@ 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) { @@ -260,9 +331,52 @@ check_tree (struct interval_tree *tree) if (tree->root == ITREE_NULL) return true; - intmax_t size = 0; - recurse_check_tree (tree->root, tree->otick, 0, - PTRDIFF_MIN, PTRDIFF_MAX, &size); + /* Establish an upper bound on the height of the tree. + + A red-black tree with n nodes has height at least log2(n + 1) but + no more than 2 * log2(n + 1). A red-black tree with height h has + at least pow(2, h / 2) - 1 nodes but no more than pow(2, h) - 1. + + On 64 bit architectures a red black tree has at least 2 pointers, + 8 bytes each. A 64 bit architecture has a theoretical maximum of + 2 ^ 64 addressable bytes. + + Putting this together, the upper bound for the height of any + red-black tree on a 64 bit architecture is 120. + + 2 * log2((2 ^ 64 bytes) / (2 * 8) bytes + 1) => 120 + + In truth, the memory overhead for our tree nodes is more than two + pointers, but even if we say each has 1 KiB overhead the bound + only drops to 108, which is an unimportant difference here. + */ + const int max_height = 120; + + /* + When ENABLE_OVERLAY_CHECKING is true, check the invariants of the + entire tree. + + Otherwise limit invariant checking to at most 257 nodes. When so + limited some of the invariants cannot be asserted, but the + asymtotic run time of the tree algorithms is preserved, so it is + practical to enable them whenever ENABLE_CHECKING (without + ENABLE_OVERLAY_CHECKING) is defined. + + Note: a tree of height 8 can have at most 2^8-1 or 257 nodes. + */ + const int max_depth + = (ENABLE_OVERLAY_CHECKING || tree->size <= 257) ? max_height : 8; + + struct interval_node *node = tree->root; + struct check_subtree_result result + = check_subtree (node, tree->otick, max_depth, node->offset, + PTRDIFF_MIN, PTRDIFF_MAX); + if (ENABLE_OVERLAY_CHECKING) + eassert (result.complete); + if (result.complete) + eassert (result.size == tree->size); + + /* The only way this function fails is eassert(). */ return true; } -- 2.35.1 ^ permalink raw reply related [flat|nested] 71+ messages in thread
* Re: noverlay branch 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 1 sibling, 1 reply; 71+ messages in thread From: Eli Zaretskii @ 2022-10-10 6:24 UTC (permalink / raw) To: Matt Armstrong; +Cc: monnier, emacs-devel > From: Matt Armstrong <matt@rfc20.org> > Cc: emacs-devel@gnu.org > Date: Sun, 09 Oct 2022 19:57:43 -0700 > > Subject: [PATCH 2/2] Add ./configure --enable-checking=overlays > > This enables expensive validation checks of overlay data structures. > Some are too expensive to turn on by default with > --enable-checking=all since they turn O(log N) algorithms into O(N) > algorithms. > > * src/itree.c (ENABLE_OVERLAY_CHECKING): define to 0 if undefined. > (struct check_subtree_result): new struct. > (check_subtree): renamed from recurse_check_tree, modified to use > check_subtree_result and check more invariants. > (check_tree): use check_subtree_result. > * configure.ac: add support for ENABLE_OVERLAY_CHECKING. Unless the checks are _very_ expensive, I'd prefer not to introduce yet another "enable-checking" knob, and instead condition those just by ENABLE_CHECKING. This will allow these assertions to be active in more builds, and will enhance our checking of their conditions. Please also consider whether we need some checks to be invoked manually, e.g. when something goes wrong in a session that might be related to overlays. E.g., a command that dumps the overlay tree(s) to stderr could be perhaps useful in such cases. Thanks. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-10 6:24 ` Eli Zaretskii @ 2022-10-10 16:26 ` Matt Armstrong 0 siblings, 0 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-10 16:26 UTC (permalink / raw) To: Eli Zaretskii; +Cc: monnier, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > Unless the checks are _very_ expensive, I'd prefer not to introduce > yet another "enable-checking" knob, and instead condition those just > by ENABLE_CHECKING. Ok, withdrawn. I haven't experienced extreme slowness myself. The problem is theoretical at this point. Because the checks validate the entire overlay tree before and after primitive operations (insert, erase, etc.) they do have the potential to be worse than the old overlay implementation running with checks on (exponential performance, etc.). I left the --enable_checking=overlays diffs here sould we want them later: https://git.sr.ht/~matta/emacs/log/my/shelve/enable-checking-overlays > Please also consider whether we need some checks to be invoked > manually, e.g. when something goes wrong in a session that might be > related to overlays. E.g., a command that dumps the overlay tree(s) > to stderr could be perhaps useful in such cases. I believe there is code for a new lisp function that returns a representation of the tree. I have never used it, and I'm not sure if it is enabled. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-10 2:57 ` Matt Armstrong 2022-10-10 6:24 ` Eli Zaretskii @ 2022-10-10 14:44 ` Stefan Monnier 2022-10-11 3:46 ` Matt Armstrong 1 sibling, 1 reply; 71+ messages in thread From: Stefan Monnier @ 2022-10-10 14:44 UTC (permalink / raw) To: Matt Armstrong; +Cc: emacs-devel > 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? > 2) There is no tree->otick, just tree->root->otick. That is what is > incremented when offsets are added. Sounds good. > 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. 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`). > Anyway, thanks for fixing my "thinko" bug in the check_tree code -- I > had the fix but should have sent it faster. I didn't actually intend > for that to be commited yet (pushed it to sr.ht by accident), but maybe > it helped you chase down the bugs you fixed today? Yes, pointed out the problem in the tree insertion code, yes. > 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. 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). > +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. > - /* 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? > + 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. 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). > + { > + /* 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. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-10 14:44 ` Stefan Monnier @ 2022-10-11 3:46 ` Matt Armstrong 2022-10-11 4:09 ` Stefan Monnier 0 siblings, 1 reply; 71+ messages in thread From: Matt Armstrong @ 2022-10-11 3:46 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 6362 bytes --] Stefan, I read only Eli's reply this morning. Got to yours just now. Stefan Monnier <monnier@iro.umontreal.ca> 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. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-Improve-check_subtree.patch --] [-- Type: text/x-diff, Size: 8205 bytes --] From 67b9e89a5aa0052b1abdcd8971a0fa83f52e13a5 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> 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 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-Check-red-black-invariants-in-most-places.patch --] [-- Type: text/x-diff, Size: 6506 bytes --] From 51a8e375ebea2e6e05eed623bddfb323b8e408f0 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> 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 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #4: 0003-Simplify-itree_null-initialization.patch --] [-- Type: text/x-diff, Size: 3296 bytes --] From a154259bfacf7f1406794a952e80a8197b9a83fb Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> 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 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #5: 0004-Stop-reading-and-writing-the-itree_null.parent-field.patch --] [-- Type: text/x-diff, Size: 4086 bytes --] From da0387f0fe79f577fae6d5453c758f600e1ae495 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> 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; } \f -- 2.35.1 ^ permalink raw reply related [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-11 3:46 ` Matt Armstrong @ 2022-10-11 4:09 ` Stefan Monnier 2022-10-11 18:02 ` Matt Armstrong 0 siblings, 1 reply; 71+ messages in thread From: Stefan Monnier @ 2022-10-11 4:09 UTC (permalink / raw) To: Matt Armstrong; +Cc: emacs-devel Matt Armstrong [2022-10-10 20:46:43] wrote: > Stefan, I read only Eli's reply this morning. Got to yours just now. > > > Stefan Monnier <monnier@iro.umontreal.ca> 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. ?? In the code I have from feature/noverlay I see: eassert (node->parent == ITREE_NULL || node->parent->otick >= node->otick); in `interval_tree_inherit_offset`. >>> 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. I don't think propagating `otick` is very important during tree traversals. >> 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. Sorry, I fail to see the connection to btrees. >> This max_depth also sounds to me like over-engineering. > I'd like to keep max_depth. Sorry, not gonna happen. > 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. The purpose of ENABLE_CHECKING assertions is: - avoid errors remaining undetected. - document the code. If it crashes with a stack overflow, it still detects the problem, so `max_depth` is not necessary for that. Your checking code is nice and detailed, but it's becoming so large that it increases the maintenance burden. I want to make it shorter&simpler. > 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. Many people run their Emacs always with ENABLE_CHECKING, so the performance impact should be minimized. It's OK to check a node's local invariants in those places where you visit the node anyway. It's OK to check the overall state of the tree in those places where the code will visit all the nodes anyway, but it's not OK to visit significantly more nodes just for the check than what the normal code would do, especially if an error there could be detected sooner or later by cheaper checks elsewhere. > 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. Yes, it's very helpful *while working on the code*. But it's easy to sprinkle many more calls to `check_tree` as needed when you're debugging an error caught by the cheap checks. And when you do that you can temporarily pay the price of full tree traversals. > 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. [ FWIW, I'd like to get rid of the `tree->size` field, and thus rely on auto-growing more heavily. ] >>> + 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. Great, so we can remove the `result.complete` field. >> 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. But some of them are currently at places where they're unacceptable because they cost a lot more than the surrounding code. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-11 4:09 ` Stefan Monnier @ 2022-10-11 18:02 ` Matt Armstrong 2022-10-11 18:59 ` Stefan Monnier 0 siblings, 1 reply; 71+ messages in thread From: Matt Armstrong @ 2022-10-11 18:02 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: > Matt Armstrong [2022-10-10 20:46:43] wrote: > >> Not yet...until...just now. > > ?? > In the code I have from feature/noverlay I see: > > eassert (node->parent == ITREE_NULL || node->parent->otick >= node->otick); > > in `interval_tree_inherit_offset`. Oh, yeah. I was talking only about the `check_tree` function. >>>> 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. > > I don't think propagating `otick` is very important during > tree traversals. I agree. I can't think of a strong argument to do it. >>> 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. > > Sorry, I fail to see the connection to btrees. Just a performance conjecture. A b-tree is shallower, so your first idea above is more attractive. >>> This max_depth also sounds to me like over-engineering. >> I'd like to keep max_depth. > > Sorry, not gonna happen. [...] > Yes, it's very helpful *while working on the code*. But it's easy to > sprinkle many more calls to `check_tree` as needed when you're debugging > an error caught by the cheap checks. And when you do that you can > temporarily pay the price of full tree traversals. [...] > But some of them are currently at places where they're unacceptable > because they cost a lot more than the surrounding code. Great, thanks for those explanations. I am now a little better calibrated as far as the expected performance of ENABLE_CHECKING code. > [ FWIW, I'd like to get rid of the `tree->size` field, and thus rely on > auto-growing more heavily. ] I rather like the size field. It is one of the first things I look at when assessing a crash, since it is nice to know whether a tree is small or enormous. But, yeah, little else really needs it. I have a different idea about the stacks, though. Idea: use fixed size stacks, no auto-growing. 120 elements is all that is needed (the max possible depth of a 3-pointer red-black tree on 64 bit architectures). That is 1K memory overhead per traversal, which I think isn't an issue? At least, this is a reasonable choice in other systems I've worked in. Is it reasonable for Emacs? ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-11 18:02 ` Matt Armstrong @ 2022-10-11 18:59 ` Stefan Monnier 2022-10-12 5:18 ` Matt Armstrong 0 siblings, 1 reply; 71+ messages in thread From: Stefan Monnier @ 2022-10-11 18:59 UTC (permalink / raw) To: Matt Armstrong; +Cc: emacs-devel >>>> 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. >> Sorry, I fail to see the connection to btrees. > Just a performance conjecture. A b-tree is shallower, so your first > idea above is more attractive. I still fail to see the connection: changes in `otick` need to be propagated everywhere anyway, so the shape of the tree probably doesn't make much difference. [...time passe...] Oh, I think you're talking specifically about the complete removal of `otick` where a shallower tree would make that less costly. Sorry. I understand quickly, but only after a long explanation. >> [ FWIW, I'd like to get rid of the `tree->size` field, and thus rely on >> auto-growing more heavily. ] > I rather like the size field. I agree it has its benefit, which is why I haven't removed it yet: I'm not sure. > I have a different idea about the stacks, though. Idea: use fixed size > stacks, no auto-growing. 120 elements is all that is needed (the max > possible depth of a 3-pointer red-black tree on 64 bit architectures). > That is 1K memory overhead per traversal, which I think isn't an issue? > At least, this is a reasonable choice in other systems I've worked in. > Is it reasonable for Emacs? Maybe you're right. I think this decision is muddled by the (ab)use of stacks as collections of nodes at a few places (where we use `interval_stack_push`), where it's not limited to O(log N). Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-11 18:59 ` Stefan Monnier @ 2022-10-12 5:18 ` Matt Armstrong 2022-10-12 18:02 ` Stefan Monnier 0 siblings, 1 reply; 71+ messages in thread From: Matt Armstrong @ 2022-10-12 5:18 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 1565 bytes --] Stefan Monnier <monnier@iro.umontreal.ca> writes: > Oh, I think you're talking specifically about the complete removal of > `otick` where a shallower tree would make that less costly. > Sorry. I understand quickly, but only after a long explanation. That and the basic tree operations (find, insert, remove, etc.). You are right that there is enough essential updating going on in the tree that perhaps the btree isn't a win. I haven't thought about it deeply. >>> [ FWIW, I'd like to get rid of the `tree->size` field, and thus rely on >>> auto-growing more heavily. ] >> I rather like the size field. > > I agree it has its benefit, which is why I haven't removed it yet: > I'm not sure. > >> I have a different idea about the stacks, though. Idea: use fixed size >> stacks, no auto-growing. 120 elements is all that is needed (the max >> possible depth of a 3-pointer red-black tree on 64 bit architectures). >> That is 1K memory overhead per traversal, which I think isn't an issue? >> At least, this is a reasonable choice in other systems I've worked in. >> Is it reasonable for Emacs? > > Maybe you're right. I think this decision is muddled by the (ab)use of > stacks as collections of nodes at a few places (where we use > `interval_stack_push`), where it's not limited to O(log N). Could most places use a fixed C array (maybe in a simple struct), with fancy places still using the fancy thing? Anyway, today 3 tiny patches. Two improvements, one bug fix. Also on https://git.sr.ht/~matta/emacs/log/feature/noverlay. And now patches... [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-.clang-format-Add-ITREE_FOREACH.patch --] [-- Type: text/x-diff, Size: 714 bytes --] From 034d50415858b18b032b116804bfefc1be421bb3 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Tue, 11 Oct 2022 11:41:47 -0700 Subject: [PATCH 1/3] ; * .clang-format: Add ITREE_FOREACH. --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 44200a3995..ac9f95c88a 100644 --- a/.clang-format +++ b/.clang-format @@ -6,7 +6,7 @@ BreakBeforeBinaryOperators: All BreakBeforeBraces: GNU ColumnLimit: 70 ContinuationIndentWidth: 2 -ForEachMacros: [FOR_EACH_TAIL, FOR_EACH_TAIL_SAFE] +ForEachMacros: [FOR_EACH_TAIL, FOR_EACH_TAIL_SAFE, ITREE_FOREACH] IncludeCategories: - Regex: '^<config\.h>$' Priority: -1 -- 2.35.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-src-itree.c-check_tree-assert-that-the-tree-root-is-.patch --] [-- Type: text/x-diff, Size: 718 bytes --] From fda8723be640593a662d7ff9d4900b7f9e56423e Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Tue, 11 Oct 2022 20:32:08 -0700 Subject: [PATCH 2/3] ; * src/itree.c (check_tree): assert that the tree root is black --- src/itree.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/itree.c b/src/itree.c index ef623d0850..deef0335cf 100644 --- a/src/itree.c +++ b/src/itree.c @@ -307,6 +307,7 @@ check_tree (struct interval_tree *tree, if (tree->root == ITREE_NULL) return true; eassert (tree->root->parent == ITREE_NULL); + eassert (!check_red_black_invariants || !tree->root->red); struct interval_node *node = tree->root; struct check_subtree_result result -- 2.35.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #4: 0003-src-itree.c-check_subtree-fix-logical-error-in-easse.patch --] [-- Type: text/x-diff, Size: 852 bytes --] From a98497429fcb1369bfe2af9d57820eb94ae380dc Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Tue, 11 Oct 2022 20:19:16 -0700 Subject: [PATCH 3/3] ; * src/itree.c (check_subtree): fix logical error in eassert --- src/itree.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/itree.c b/src/itree.c index deef0335cf..cd6b0b35f8 100644 --- a/src/itree.c +++ b/src/itree.c @@ -277,7 +277,8 @@ check_subtree (struct interval_node *node, if (check_red_black_invariants) { eassert (left_result.black_height == right_result.black_height); - eassert (node->parent != ITREE_NULL || !node->red || !node->parent->red); + eassert (node->parent == ITREE_NULL || !node->red + || !node->parent->red); } result.size = 1 + left_result.size + right_result.size; -- 2.35.1 ^ permalink raw reply related [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-12 5:18 ` Matt Armstrong @ 2022-10-12 18:02 ` Stefan Monnier 2022-10-12 22:26 ` Matt Armstrong 0 siblings, 1 reply; 71+ messages in thread From: Stefan Monnier @ 2022-10-12 18:02 UTC (permalink / raw) To: Matt Armstrong; +Cc: emacs-devel > --- a/src/itree.c > +++ b/src/itree.c > @@ -307,6 +307,7 @@ check_tree (struct interval_tree *tree, > if (tree->root == ITREE_NULL) > return true; > eassert (tree->root->parent == ITREE_NULL); > + eassert (!check_red_black_invariants || !tree->root->red); > > struct interval_node *node = tree->root; > struct check_subtree_result result Does any part of the code care about that? I can't see anything that would break if this invariant is not satisfied (both in terms of correct behavior and in terms of performance). IOW it seems more like an accident than something important would should check. > { > eassert (left_result.black_height == right_result.black_height); > - eassert (node->parent != ITREE_NULL || !node->red || !node->parent->red); > + eassert (node->parent == ITREE_NULL || !node->red > + || !node->parent->red); > } Duh! Thanks, Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-12 18:02 ` Stefan Monnier @ 2022-10-12 22:26 ` Matt Armstrong 2022-10-13 4:03 ` Stefan Monnier 0 siblings, 1 reply; 71+ messages in thread From: Matt Armstrong @ 2022-10-12 22:26 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: >> --- a/src/itree.c >> +++ b/src/itree.c >> @@ -307,6 +307,7 @@ check_tree (struct interval_tree *tree, >> if (tree->root == ITREE_NULL) >> return true; >> eassert (tree->root->parent == ITREE_NULL); >> + eassert (!check_red_black_invariants || !tree->root->red); >> >> struct interval_node *node = tree->root; >> struct check_subtree_result result > > Does any part of the code care about that? I can't see anything that > would break if this invariant is not satisfied (both in terms of > correct behavior and in terms of performance). IOW it seems more like > an accident than something important would should check. That is a can of subtle worms. As a practical matter, `interval_tree_insert_fix` asserts it, and that was failing for me. I wanted it to fail earlier. TLDR: That the root be black isn't an essential proeprty of Red-Black trees. We could relax it, but that is uncommon practice and would slightly complicate `interval_tree_insert_fix`. Corman et. al. doesn't list it as one of the four "red-black properties", but read on. Harper, Sen and Tarjan's Rank Balanced Trees paper (http://sidsen.azurewebsites.net/papers/rb-trees-talg.pdf) reformulates Red-Black trees in terms of rank differences of a node and its parent. In their formulation, there is an equivalence between Red-Black "color" and the rank difference between a node and its parent. Since the root has no parent, they say the root has no color. So I think you could prove that the root's color doesn't matter -- it is inexpressible in an equivalent formulation of the same class of trees. But then you get to the implementation, where Corman et. al., subtly placed deep in their description of their "RB-Insert" function (at least in my old copy), says this: > We have made the important assumption that the root of the tree is > black -- a property we guarante in line 18 [...] Why, because, in my copy, they do this: if p[x] = left[p[p[x]]] which is ill defined if p[x] is a red root -- there is no p[p[x]]. They want a guarantee that p[p[x]] exists. They've already checked that p[x] is red, so they just say root can't be red to save a check that p[x] is not the root. (Let me chime in here that I *dislike* it when textbook authors do this kind of thing, especially in books aimed at undergrads. They prominently list four essential Red-Black tree properties, and promptly write code that relies on additional invariants they toss in to make their pseudo-code look nicer. I actually remember looking at this p[p[x]] and struggling to figure out how the four "red-black properties" made that safe, then finding that little note burried at the end of a paragraph in their voluminous text, and getting angry. It is almost as if they had no appreciation for their poor young readers minds being completely full just understanding the very basics. That was nearly three decades ago! A formative memory in the folly of being too clever.) Our code is the same in `interval_tree_inherit_offset`: if (node->parent == node->parent->parent->left) { If node->parent is a red root that'll crash even with sentinel nodes (after my change to give even them a truly NULL parent). At long last, I think we could change this in `interval_tree_insert_fix`: while (null_safe_is_red (node->parent)) { to this: while (null_safe_is_red (node->parent) && node->parent != tree->root) { ...and then we could allow red roots. I'm inclined to keep it the way it is, if only for reasons of inertia. Most future maintainers coming to Red-Black tree code will have the "Corman et. al." mindset. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-12 22:26 ` Matt Armstrong @ 2022-10-13 4:03 ` Stefan Monnier 0 siblings, 0 replies; 71+ messages in thread From: Stefan Monnier @ 2022-10-13 4:03 UTC (permalink / raw) To: Matt Armstrong; +Cc: emacs-devel > As a practical matter, `interval_tree_insert_fix` asserts it, and that > was failing for me. I wanted it to fail earlier. Ah, I see we depend on it in our implementation of insertion. OK. Then maybe it's best to keep this assertion in the insertion code or at least add a node explaining why we require it. > I'm inclined to keep it the way it is, if only for reasons of inertia. I have no trouble with it, indeed. It's just subtle enough to warrant a comment. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-08 23:33 ` Matt Armstrong 2022-10-09 3:44 ` Matt Armstrong 2022-10-09 4:12 ` Stefan Monnier @ 2022-10-09 23:47 ` Stefan Monnier 2022-10-10 0:05 ` Emanuel Berg 2022-10-10 16:33 ` Matt Armstrong 2 siblings, 2 replies; 71+ messages in thread From: Stefan Monnier @ 2022-10-09 23:47 UTC (permalink / raw) To: Matt Armstrong; +Cc: emacs-devel, Ihor Radchenko > In any case, I do have a new test for tests/src/buffer-tests.el that > crashes feature/noverlay Emacs immediately, when ENABLE_CHECKING is on, > in a spot in itree.c having to do with offset inheritance. I tightened up a few things and the code now passes the tests without crashing any more. Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 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 1 sibling, 1 reply; 71+ messages in thread From: Emanuel Berg @ 2022-10-10 0:05 UTC (permalink / raw) To: emacs-devel Stefan Monnier wrote: >> In any case, I do have a new test for >> tests/src/buffer-tests.el that crashes feature/noverlay >> Emacs immediately, when ENABLE_CHECKING is on, in a spot in >> itree.c having to do with offset inheritance. > > I tightened up a few things and the code now passes the > tests without crashing any more. I know only of the byte-compiler and checkdoc https://dataswamp.org/~incal/emacs-init/ide/pack-style.el If you have something more I want it please :) If we talk Elisp in general ... -- underground experts united https://dataswamp.org/~incal ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-10 0:05 ` Emanuel Berg @ 2022-10-10 16:27 ` Matt Armstrong 0 siblings, 0 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-10 16:27 UTC (permalink / raw) To: Emanuel Berg, emacs-devel Emanuel Berg <incal@dataswamp.org> writes: > Stefan Monnier wrote: > >>> In any case, I do have a new test for >>> tests/src/buffer-tests.el that crashes feature/noverlay >>> Emacs immediately, when ENABLE_CHECKING is on, in a spot in >>> itree.c having to do with offset inheritance. >> >> I tightened up a few things and the code now passes the >> tests without crashing any more. > > I know only of the byte-compiler and checkdoc > > https://dataswamp.org/~incal/emacs-init/ide/pack-style.el > > If you have something more I want it please :) > > If we talk Elisp in general ... Hi Emanuel, for what it is worth I could not figure out how your response here pertains to this conversation. Have you replied and quoted the correct messages? ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-09 23:47 ` Stefan Monnier 2022-10-10 0:05 ` Emanuel Berg @ 2022-10-10 16:33 ` Matt Armstrong 2022-10-10 18:32 ` Matt Armstrong 1 sibling, 1 reply; 71+ messages in thread From: Matt Armstrong @ 2022-10-10 16:33 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 827 bytes --] Stefan Monnier <monnier@iro.umontreal.ca> writes: >> In any case, I do have a new test for tests/src/buffer-tests.el that >> crashes feature/noverlay Emacs immediately, when ENABLE_CHECKING is on, >> in a spot in itree.c having to do with offset inheritance. > > I tightened up a few things and the code now passes the tests without > crashing any more. Stefan, I now have four commits queued up on https://git.sr.ht/~matta/emacs/log/feature/noverlay These supercede my last message, and reflect the withdrawal of the diffs that add a new "overlays" configure time flag, as requested by Eli. We're still checking the entire tree unconditionally, but the code still has the height limiting logic in case it is needed. It also doubles as a spot check of the fuction that computes the max height of the tree given its size. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-test-src-buffer-tests.el-Remove-unecessary-message-c.patch --] [-- Type: text/x-diff, Size: 958 bytes --] From 246acbddbeb3e9a390fe78242259182af0c2cc18 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Sun, 9 Oct 2022 10:12:32 -0700 Subject: [PATCH 1/4] ; * test/src/buffer-tests.el: Remove unecessary `message' calls. --- test/src/buffer-tests.el | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/src/buffer-tests.el b/test/src/buffer-tests.el index 01780a15cc..9bccbdf2e8 100644 --- a/test/src/buffer-tests.el +++ b/test/src/buffer-tests.el @@ -1554,10 +1554,8 @@ test-overlay-randomly ;; is to initially steadily increase the overlay count, then ;; steadily decrease it, then repeat. (when (and growing (= overlay-count overlay-count-limit)) - (message "now shrinking") (setq growing nil)) (when (and (not growing) (= overlay-count 0)) - (message "now growing") (setq growing t)) ;; Create or delete a random overlay according to a -- 2.35.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-Improve-check_subtree.patch --] [-- Type: text/x-diff, Size: 6916 bytes --] From 100faa45a3e64f82f0b16fccb511db2431815940 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Mon, 10 Oct 2022 08:48:41 -0700 Subject: [PATCH 2/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 | 136 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 111 insertions(+), 25 deletions(-) diff --git a/src/itree.c b/src/itree.c index bbab70dac7..60bc2b5c89 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,12 +251,23 @@ 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); @@ -240,29 +281,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; } -- 2.35.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #4: 0003-Check-red-black-invariants-in-most-places.patch --] [-- Type: text/x-diff, Size: 5270 bytes --] From f2c25af5217e8ab7e8c00ab3e0084bce2bdf93e8 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Mon, 10 Oct 2022 09:07:42 -0700 Subject: [PATCH 3/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 | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/itree.c b/src/itree.c index 60bc2b5c89..9dfac57930 100644 --- a/src/itree.c +++ b/src/itree.c @@ -222,9 +222,9 @@ itree_init (void) }; 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) +check_subtree (struct interval_node *node, bool allow_red_red, + 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, @@ -251,22 +251,14 @@ 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 (!allow_red_red && node->red) { /* 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->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); @@ -282,11 +274,11 @@ 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, allow_red_red, 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, allow_red_red, tree_otick, + max_depth - 1, offset, begin, max_begin); eassert (left_result.limit <= limit); eassert (right_result.limit <= limit); @@ -311,7 +303,8 @@ check_subtree (struct interval_node *node, uintmax_t tree_otick, return result; } -/* Validate invariants for TREE. +/* Validate invariants for TREE. If ALLOW_RED_RED, red nodes with red + children are considered valid. This runs in constant time when ENABLE_OVERLAY_CHECKING is 0 (i.e. Emacs is not configured with @@ -320,7 +313,7 @@ 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 allow_red_red) { eassert (tree != NULL); eassert (tree->size >= 0); @@ -343,8 +336,8 @@ 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, allow_red_red, tree->otick, max_height, + node->offset, PTRDIFF_MIN, PTRDIFF_MAX); eassert (result.complete); eassert (result.size == tree->size); @@ -352,6 +345,13 @@ check_tree (struct interval_tree *tree) return true; } +/* Syntactic sugar for check_tree(tree, false) */ +static bool +check_tree (struct interval_tree *tree) +{ + return check_tree_common (tree, /* allow_red_red= */ false); +} + /* +===================================================================================+ * | Stack * +===================================================================================+ */ @@ -616,7 +616,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_common (tree, /* allow_red_red= */ true)); interval_tree_insert_fix (tree, node); } -- 2.35.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #5: 0004-Simplify-itree_null-initialization.patch --] [-- Type: text/x-diff, Size: 3296 bytes --] From a737c1b8298adf5586eede9bf4dca238b302439d Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Mon, 10 Oct 2022 08:32:56 -0700 Subject: [PATCH 4/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 9dfac57930..8d1dd00319 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); } @@ -315,6 +333,8 @@ check_subtree (struct interval_node *node, bool allow_red_red, static bool check_tree_common (struct interval_tree *tree, bool allow_red_red) { + eassert (null_is_sane ()); + eassert (tree != NULL); eassert (tree->size >= 0); eassert ((tree->size == 0) == (tree->root == ITREE_NULL)); -- 2.35.1 ^ permalink raw reply related [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-10 16:33 ` Matt Armstrong @ 2022-10-10 18:32 ` Matt Armstrong 2022-10-11 16:06 ` Stefan Monnier 0 siblings, 1 reply; 71+ messages in thread From: Matt Armstrong @ 2022-10-10 18:32 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 813 bytes --] Matt Armstrong <matt@rfc20.org> writes: > Stefan Monnier <monnier@iro.umontreal.ca> writes: > >>> In any case, I do have a new test for tests/src/buffer-tests.el that >>> crashes feature/noverlay Emacs immediately, when ENABLE_CHECKING is on, >>> in a spot in itree.c having to do with offset inheritance. >> >> I tightened up a few things and the code now passes the tests without >> crashing any more. > > Stefan, I now have four commits queued up on > https://git.sr.ht/~matta/emacs/log/feature/noverlay ...now 5 commits on https://git.sr.ht/~matta/emacs/log/feature/noverlay The latest makes all fields of the global itree_null sentinel node read-only. Because this property is only by convention, fragile, and difficult to verify by reading code, I'm going to look at removing itree_node entirely next. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0005-Stop-reading-and-writing-the-itree_null.parent-field.patch --] [-- Type: text/x-diff, Size: 4086 bytes --] From b4bdbbcd47530ed80d941bd4bd6d57538ee33ca5 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Mon, 10 Oct 2022 10:45:05 -0700 Subject: [PATCH 5/5] 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 8d1dd00319..7b6a5039b3 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); @@ -720,7 +716,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; } @@ -739,7 +734,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 */ @@ -759,7 +753,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; } @@ -778,7 +771,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; } @@ -859,7 +851,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 @@ -1486,7 +1479,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; } \f -- 2.35.1 ^ permalink raw reply related [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-10 18:32 ` Matt Armstrong @ 2022-10-11 16:06 ` Stefan Monnier 2022-10-12 17:33 ` Matt Armstrong 0 siblings, 1 reply; 71+ messages in thread From: Stefan Monnier @ 2022-10-11 16:06 UTC (permalink / raw) To: Matt Armstrong; +Cc: emacs-devel > ...now 5 commits on https://git.sr.ht/~matta/emacs/log/feature/noverlay Merged, thanks, Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-11 16:06 ` Stefan Monnier @ 2022-10-12 17:33 ` Matt Armstrong 2022-10-13 3:59 ` Stefan Monnier 0 siblings, 1 reply; 71+ messages in thread From: Matt Armstrong @ 2022-10-12 17:33 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- Attachment #1: Type: text/plain, Size: 552 bytes --] Stefan Monnier <monnier@iro.umontreal.ca> writes: >> ...now 5 commits on https://git.sr.ht/~matta/emacs/log/feature/noverlay > > Merged, thanks, Thanks Stefan, FWIW I don't see the merges on feature/noverlay yet. Have you pushed? Attached is what it looks like to remove itree_node entirely. I like the result, as I personally find sentinel nodes confusing and slightly error prone. I like my new implementation `interval_tree_remove` better too. Lemme know what you think. It needs the 0003 patch also attached below or check_tree will crash. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0003-src-itree.c-check_subtree-fix-logical-error-in-easse.patch --] [-- Type: text/x-diff, Size: 852 bytes --] From a98497429fcb1369bfe2af9d57820eb94ae380dc Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Tue, 11 Oct 2022 20:19:16 -0700 Subject: [PATCH 3/4] ; * src/itree.c (check_subtree): fix logical error in eassert --- src/itree.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/itree.c b/src/itree.c index deef0335cf..cd6b0b35f8 100644 --- a/src/itree.c +++ b/src/itree.c @@ -277,7 +277,8 @@ check_subtree (struct interval_node *node, if (check_red_black_invariants) { eassert (left_result.black_height == right_result.black_height); - eassert (node->parent != ITREE_NULL || !node->red || !node->parent->red); + eassert (node->parent == ITREE_NULL || !node->red + || !node->parent->red); } result.size = 1 + left_result.size + right_result.size; -- 2.35.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0004-Delete-the-itree_null-sentinel-node-use-NULL-everywh.patch --] [-- Type: text/x-diff, Size: 19605 bytes --] From 5ee0b50cf248b13bd3a702208f2b4ef87fdd7723 Mon Sep 17 00:00:00 2001 From: Matt Armstrong <matt@rfc20.org> Date: Wed, 12 Oct 2022 10:06:03 -0700 Subject: [PATCH 4/4] 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 cd6b0b35f8..eacd4c6425 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); } @@ -300,8 +271,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)); @@ -550,7 +519,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 @@ -578,7 +547,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); @@ -621,8 +590,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 @@ -654,17 +627,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; @@ -672,8 +648,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; @@ -682,7 +658,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; @@ -701,7 +677,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; @@ -709,8 +685,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; @@ -719,7 +695,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; @@ -737,6 +713,7 @@ interval_tree_remove_fix (struct interval_tree *tree, } } + if (node != ITREE_NULL) node->red = false; } @@ -748,86 +725,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; } @@ -861,7 +822,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) { @@ -1172,6 +1132,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 @@ -1225,9 +1197,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 @@ -1332,7 +1301,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. */ @@ -1344,7 +1315,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 @@ -1374,7 +1345,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; @@ -1416,15 +1387,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)); @@ -1439,6 +1415,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 ^ permalink raw reply related [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-12 17:33 ` Matt Armstrong @ 2022-10-13 3:59 ` Stefan Monnier 2022-10-16 21:53 ` Matt Armstrong 0 siblings, 1 reply; 71+ messages in thread From: Stefan Monnier @ 2022-10-13 3:59 UTC (permalink / raw) To: Matt Armstrong; +Cc: emacs-devel > 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 commit fda8723be640593a662d7ff9d4900b7f9e56423e Author: Matt Armstrong <matt@rfc20.org> Date: Tue Oct 11 20:32:08 2022 -0700 ; * src/itree.c (check_tree): assert that the tree root is black commit 034d50415858b18b032b116804bfefc1be421bb3 Author: Matt Armstrong <matt@rfc20.org> Date: Tue Oct 11 11:41:47 2022 -0700 ; * .clang-format: Add ITREE_FOREACH. commit 12836db6e4e09378d41301b3d4e1fcff58132d3a Author: Stefan Monnier <monnier@iro.umontreal.ca> Date: Tue Oct 11 11:17:44 2022 -0400 itree.c (check_tree): Simplify * src/itree.c (struct check_subtree_result): Remove `complete`. (check_subtree): Remove `max_depth` arg (and adjust callers). Use 0 as black-depth of empty tree. Remove redundant `node->parent` check (already performed by the caller). (check_tree): Replace with `check_tree_common` (update all callers). Check the root's `parent` field. (check_tree_no_rb): Delete function, inlined in its sole caller. (interval_tree_remove): Add call to `check_tree` (without RB checks) before `interval_tree_remove_fix`. Move update of `size` field accordingly. commit da0387f0fe79f577fae6d5453c758f600e1ae495 Author: Matt Armstrong <matt@rfc20.org> Date: Mon Oct 10 10:45:05 2022 -0700 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. [...] -- Stefan ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-13 3:59 ` Stefan Monnier @ 2022-10-16 21:53 ` Matt Armstrong 0 siblings, 0 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-16 21:53 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel [-- 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 ^ permalink raw reply related [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-09-25 22:38 noverlay branch Stefan Monnier ` (5 preceding siblings ...) 2022-10-07 16:51 ` Matt Armstrong @ 2022-10-23 4:49 ` Matt Armstrong 2022-10-24 9:14 ` Stefan Kangas 2022-10-24 12:51 ` Eli Zaretskii 6 siblings, 2 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-23 4:49 UTC (permalink / raw) To: Stefan Monnier, emacs-devel Stefan Monnier <monnier@iro.umontreal.ca> writes: > I just updated the noverlay branch to the code in `master` (most of it > was mechanical except for the fact that since the last commit on that > branch we have gotten rid of Lisp_Misc and we moved to pdumper). TLDR: things are looking pretty stable and good on the feature/noverlay branch. If anyone here tested it a month ago and found it lacking, give it another whirl. Stefan, unless I have a flash of insight I'm done with my differential fuzz testing of the noverlay branch's ELisp functions that relate to overlays. I've sent you three patches (see bug#58703 and bug#58706) but with those fixes my test harness now runs hundreds of thousands of randomized operations against with no no difference detected between Emacs master and the feature/noverlay branch. Code at https://git.sr.ht/~matta/emacs/tree/my/overfuzz/item/test/manual/noverlay/overfuzz.el, though I haven't polished it up. There are two calls to 'overlays_in' in buffer.c that I couldn't cover from ELisp: 'disable_line_numbers_overlay_at_eob' and 'mouse_face_overlay_overlaps'. They're both display related. Trying to manually exercise 'disable_line_numbers_overlay_at_eob' I couldn't find any ELisp that set `display-line-numbers-disable' on an overlay, and only two that do it in a text property (vc/log-edit.el and something in helm), so even if there were a bug here I'm not sure anyone would discover it. I haven't looked at mouse_face_overlay_overlaps, but as I move my mouse around different things highlight as expected, and as I narrow the buffer things behave the same on feature/noverlay as they do on master (as far as I can tell). ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 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 1 sibling, 1 reply; 71+ messages in thread From: Stefan Kangas @ 2022-10-24 9:14 UTC (permalink / raw) To: Matt Armstrong, Stefan Monnier, emacs-devel Matt Armstrong <matt@rfc20.org> writes: > TLDR: things are looking pretty stable and good on the feature/noverlay > branch. If anyone here tested it a month ago and found it lacking, give > it another whirl. Would it be welcome to merge master into that feature branch? BTW, I can see two warnings, with gcc 12.2.0: In function ‘interval_tree_remove_fix’, inlined from ‘itree_remove’ at itree.c:1110:5: itree.c:923:15: warning: potential null pointer dereference [-Wnull-dereference] 923 | if (null_safe_is_black (other->left) /* 2.a */ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ itree.c:960:15: warning: potential null pointer dereference [-Wnull-dereference] 960 | if (null_safe_is_black (other->right) /* 2.b */ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ I'm now running feature/noverlay here (with master merged locally), and will report back if I run into any issues. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-24 9:14 ` Stefan Kangas @ 2022-10-24 16:21 ` Matt Armstrong 0 siblings, 0 replies; 71+ messages in thread From: Matt Armstrong @ 2022-10-24 16:21 UTC (permalink / raw) To: Stefan Kangas, Stefan Monnier, emacs-devel Stefan Kangas <stefankangas@gmail.com> writes: > Matt Armstrong <matt@rfc20.org> writes: > >> TLDR: things are looking pretty stable and good on the feature/noverlay >> branch. If anyone here tested it a month ago and found it lacking, give >> it another whirl. > > Would it be welcome to merge master into that feature branch? I'm leaving the merge decision up to Stefan and other committers. I'm happy to accept commit privs and help out with this, but I'm still pretty green. There are two things I think should be done before a merge: 1) Relax some of the 'echeck' calls in src/itree.c. They are too expensive and actually make --enable-checking builds much slower than they are on 'master' when there are non-trivial numbers of overlays. Stefan has added FIXME comments for most of these. 2) Run some benchmarks. My preliminary results seem to show that Emacs redisplay can be marginally slower when the overlay count is low (with what we'd consider "acceptable numbers" of overlays today), but much faster when things get out of hand. 3) Anything Stefan has in his head. :) > BTW, I can see two warnings, with gcc 12.2.0: > > In function ‘interval_tree_remove_fix’, > inlined from ‘itree_remove’ at itree.c:1110:5: > itree.c:923:15: warning: potential null pointer dereference [-Wnull-dereference] > 923 | if (null_safe_is_black (other->left) /* 2.a */ > | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > itree.c:960:15: warning: potential null pointer dereference [-Wnull-dereference] > 960 | if (null_safe_is_black (other->right) /* 2.b */ > | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Those warnings are false positives, though the invariant relies on red-black in variants and is too complex for a compiler to realize. Heck, I have to sit down and draw things out to re-prove it to myself every time. https://git.sr.ht/~matta/emacs/log/scratch/matta/for_stefan has a patch for it (which uses 'eassume'), or we could just check for NULL even though it should never happen. > I'm now running feature/noverlay here (with master merged locally), > and will report back if I run into any issues. I'd welcome a merge from master on feature/noverlay. I think it has been one month. ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-23 4:49 ` Matt Armstrong 2022-10-24 9:14 ` Stefan Kangas @ 2022-10-24 12:51 ` Eli Zaretskii 2022-10-25 20:57 ` Dmitry Gutov 1 sibling, 1 reply; 71+ messages in thread From: Eli Zaretskii @ 2022-10-24 12:51 UTC (permalink / raw) To: Matt Armstrong, Dmitry Gutov; +Cc: monnier, emacs-devel > From: Matt Armstrong <matt@rfc20.org> > Date: Sat, 22 Oct 2022 21:49:09 -0700 > > There are two calls to 'overlays_in' in buffer.c that I couldn't cover > from ELisp: 'disable_line_numbers_overlay_at_eob' and > 'mouse_face_overlay_overlaps'. They're both display related. > > Trying to manually exercise 'disable_line_numbers_overlay_at_eob' I > couldn't find any ELisp that set `display-line-numbers-disable' on an > overlay, and only two that do it in a text property (vc/log-edit.el and > something in helm), so even if there were a bug here I'm not sure anyone > would discover it. AFAIR, company-mode uses that overlay property. Dmitry, am I right? ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: noverlay branch 2022-10-24 12:51 ` Eli Zaretskii @ 2022-10-25 20:57 ` Dmitry Gutov 0 siblings, 0 replies; 71+ messages in thread From: Dmitry Gutov @ 2022-10-25 20:57 UTC (permalink / raw) To: Eli Zaretskii, Matt Armstrong; +Cc: monnier, emacs-devel Hi! On 24.10.2022 15:51, Eli Zaretskii wrote: >> From: Matt Armstrong<matt@rfc20.org> >> Date: Sat, 22 Oct 2022 21:49:09 -0700 >> >> There are two calls to 'overlays_in' in buffer.c that I couldn't cover >> from ELisp: 'disable_line_numbers_overlay_at_eob' and >> 'mouse_face_overlay_overlaps'. They're both display related. >> >> Trying to manually exercise 'disable_line_numbers_overlay_at_eob' I >> couldn't find any ELisp that set `display-line-numbers-disable' on an >> overlay, and only two that do it in a text property (vc/log-edit.el and >> something in helm), so even if there were a bug here I'm not sure anyone >> would discover it. > AFAIR, company-mode uses that overlay property. Dmitry, am I right? Doesn't look like it. I vaguely remember discussing it, but it seems we have solved the corresponding problem some other way. ^ permalink raw reply [flat|nested] 71+ messages in thread
end of thread, other threads:[~2022-10-25 20:57 UTC | newest] Thread overview: 71+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 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 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
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).