From: Alan Mackenzie <acm@muc.de>
To: Ravine Var <ravine.var@gmail.com>
Cc: acm@muc.de, 45248@debbugs.gnu.org
Subject: bug#45248: 28.0.50; Emacs freezes with big C functions
Date: Thu, 17 Dec 2020 15:08:34 +0000 [thread overview]
Message-ID: <X9t0cm6EgAszn4Hf@ACM> (raw)
In-Reply-To: <87a6ufen1w.fsf@gmail.com>
Hello, Ravine.
Thanks for reporting this bug, and for opening the bug report.
On Tue, Dec 15, 2020 at 09:29:12 +0530, Ravine Var wrote:
> Scrolling through large functions freezes Emacs. For example,
> the function proto_register_rrc(void) extracted from Wireshark
> shows the issue.
> https://gitlab.com/wireshark/wireshark/-/raw/master/epan/dissectors/packet-rrc.c
> Profile report with emacs -Q:
> https://gist.github.com/ravine-var/9136ed2608a08f65111bdc2fc531ccf5
> In GNU Emacs 28.0.50 (build 1, x86_64-pc-linux-gnu, cairo version 1.17.4)
> of 2020-12-15 built on ryzen-mach
> Repository revision: fd4297b25a61b33340ef312355748512e702bc2c
> Repository branch: master
> Windowing system distributor 'The X.Org Foundation', version 11.0.12010000
> System Description: Arch Linux
[ .... ]
As Eli surmised, the problem is the very large static struct in the
function.
The solution appears to be to cache positions within structs, so that
the function searching back over brace lists needn't do so repeatedly in
the same piece of buffer.
I'm hoping that the following patch which implements this cache will
solve the problem. Would you please apply it to your copy of the master
branch, byte compile it, and try it out on various CC Mode buffers.
Then please let me know how well it has fixed the bug. Thanks!
diff -r e43499573c2d cc-engine.el
--- a/cc-engine.el Tue Dec 15 11:12:15 2020 +0000
+++ b/cc-engine.el Thu Dec 17 14:57:35 2020 +0000
@@ -3574,8 +3574,9 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Defuns which analyze the buffer, yet don't change `c-state-cache'.
(defun c-get-fallback-scan-pos (here)
- ;; Return a start position for building `c-state-cache' from
- ;; scratch. This will be at the top level, 2 defuns back.
+ ;; Return a start position for building `c-state-cache' from scratch. This
+ ;; will be at the top level, 2 defuns back. Return nil if we don't find
+ ;; these defun starts a reasonable way back.
(save-excursion
(save-restriction
(when (> here (* 10 c-state-cache-too-far))
@@ -11627,6 +11628,195 @@
(or (looking-at c-brace-list-key)
(progn (goto-char here) nil))))
+(defun c-laomib-loop (lim)
+ ;; The "expensive" loop from `c-looking-at-or-maybe-in-bracelist'. Move
+ ;; backwards over comma separated sexps as far as possible, but no further
+ ;; than LIM, which may be nil, meaning no limit. Return the final value of
+ ;; `braceassignp', which is t if we encountered "= {", usually nil
+ ;; otherwise.
+ (let ((braceassignp 'dontknow)
+ (class-key
+ ;; Pike can have class definitions anywhere, so we must
+ ;; check for the class key here.
+ (and (c-major-mode-is 'pike-mode)
+ c-decl-block-key)))
+ (while (eq braceassignp 'dontknow)
+ (cond ((eq (char-after) ?\;)
+ (setq braceassignp nil))
+ ((and class-key
+ (looking-at class-key))
+ (setq braceassignp nil))
+ ((and c-has-compound-literals
+ (looking-at c-return-key))
+ (setq braceassignp t)
+ nil)
+ ((eq (char-after) ?=)
+ ;; We've seen a =, but must check earlier tokens so
+ ;; that it isn't something that should be ignored.
+ (setq braceassignp 'maybe)
+ (while (and (eq braceassignp 'maybe)
+ (zerop (c-backward-token-2 1 t lim)))
+ (setq braceassignp
+ (cond
+ ;; Check for operator =
+ ((and c-opt-op-identifier-prefix
+ (looking-at c-opt-op-identifier-prefix))
+ nil)
+ ;; Check for `<opchar>= in Pike.
+ ((and (c-major-mode-is 'pike-mode)
+ (or (eq (char-after) ?`)
+ ;; Special case for Pikes
+ ;; `[]=, since '[' is not in
+ ;; the punctuation class.
+ (and (eq (char-after) ?\[)
+ (eq (char-before) ?`))))
+ nil)
+ ((looking-at "\\s.") 'maybe)
+ ;; make sure we're not in a C++ template
+ ;; argument assignment
+ ((and
+ (c-major-mode-is 'c++-mode)
+ (save-excursion
+ (let ((here (point))
+ (pos< (progn
+ (skip-chars-backward "^<>")
+ (point))))
+ (and (eq (char-before) ?<)
+ (not (c-crosses-statement-barrier-p
+ pos< here))
+ (not (c-in-literal))
+ ))))
+ nil)
+ (t t)))))
+ ((and
+ (c-major-mode-is 'c++-mode)
+ (eq (char-after) ?\[)
+ ;; Be careful of "operator []"
+ (not (save-excursion
+ (c-backward-token-2 1 nil lim)
+ (looking-at c-opt-op-identifier-prefix))))
+ (setq braceassignp t)
+ nil))
+ (when (eq braceassignp 'dontknow)
+ (cond ((and
+ (not (eq (char-after) ?,))
+ (save-excursion
+ (c-backward-syntactic-ws)
+ (eq (char-before) ?})))
+ (setq braceassignp nil))
+ ((/= (c-backward-token-2 1 t lim) 0)
+ (if (save-excursion
+ (and c-has-compound-literals
+ (eq (c-backward-token-2 1 nil lim) 0)
+ (eq (char-after) ?\()))
+ (setq braceassignp t)
+ (setq braceassignp nil))))))
+ braceassignp))
+
+;; The following variable is a cache of up to four entries, each entry of
+;; which is a list representing a call to c-laomib-loop. It contains the
+;; following elements:
+;; 0: `lim' argument - used as an alist key, never nil.
+;; 1: Position in buffer where the scan started.
+;; 2: Position in buffer where the scan ended.
+;; 3: Result of the call to `c-laomib-loop'.
+(defvar c-laomib-cache nil)
+(make-variable-buffer-local 'c-laomib-cache)
+
+(defun c-laomib-get-cache (containing-sexp)
+ ;; Get an element from `c-laomib-cache' matching CONTAINING-SEXP.
+ ;; Return that element or nil if one wasn't found.
+ (let ((elt (assq containing-sexp c-laomib-cache)))
+ (when elt
+ ;; Move the fetched `elt' to the front of the cache.
+ (setq c-laomib-cache (delq elt c-laomib-cache))
+ (push elt c-laomib-cache)
+ elt)))
+
+(defun c-laomib-put-cache (lim start end result)
+ ;; Insert a new element into `c-laomib-cache', removing another element to
+ ;; make room, if necessary. The four parameters LIM, START, END, RESULT are
+ ;; the components of the new element (see comment for `c-laomib-cache').
+ ;; The return value is of no significance.
+ (when lim
+ (let ((old-elt (assq lim c-laomib-cache))
+ ;; (elt (cons containing-sexp (cons start nil)))
+ (new-elt (list lim start end result))
+ big-ptr
+ (cur-ptr c-laomib-cache)
+ togo togo-ptr (size 0) cur-size
+ )
+ (if old-elt (setq c-laomib-cache (delq old-elt c-laomib-cache)))
+
+ (while (>= (length c-laomib-cache) 4)
+ ;; We delete the least recently used elt which doesn't enclose START,
+ ;; or..
+ (dolist (elt c-laomib-cache)
+ (if (or (<= start (cadr elt))
+ (> start (car (cddr elt))))
+ (setq togo elt)))
+
+ ;; ... delete the least recently used elt which isn't the biggest.
+ (when (not togo)
+ (while (cdr cur-ptr)
+ (setq cur-size (- (nth 2 (cadr cur-ptr)) (car (cadr cur-ptr))))
+ (when (> cur-size size)
+ (setq size cur-size
+ big-ptr cur-ptr))
+ (setq cur-ptr (cdr cur-ptr)))
+ (setq togo (if (cddr big-ptr)
+ (car (last big-ptr))
+ (car big-ptr))))
+
+ (setq c-laomib-cache (delq togo c-laomib-cache)))
+
+ (push new-elt c-laomib-cache))))
+
+(defun c-laomib-fix-elt (lwm elt paren-state)
+ ;; Correct a c-laomib-cache entry ELT with respect to buffer changes, either
+ ;; doing nothing, signalling it is to be deleted, or replacing its start
+ ;; point with one lower in the buffer than LWM. PAREN-STATE is the paren
+ ;; state at LWM. Return the corrected entry, or nil (if it needs deleting).
+ ;; Note that corrections are made by `setcar'ing the original structure,
+ ;; which thus remains intact.
+ (cond
+ ((or (not lwm) (> lwm (cadr elt)))
+ elt)
+ ((<= lwm (nth 2 elt))
+ nil)
+ (t
+ (let (cur-brace)
+ ;; Search for the last brace in `paren-state' before (car `lim'). This
+ ;; brace will become our new 2nd element of `elt'.
+ (while
+ ;; Search one brace level per iteration.
+ (and paren-state
+ (progn
+ ;; (setq cur-brace (c-laomib-next-BRACE paren-state))
+ (while
+ ;; Go past non-brace levels, one per iteration.
+ (and paren-state
+ (not (eq (char-after
+ (c-state-cache-top-lparen paren-state))
+ ?{)))
+ (setq paren-state (cdr paren-state)))
+ (cadr paren-state))
+ (> (c-state-cache-top-lparen (cdr paren-state)) (car elt)))
+ (setq paren-state (cdr paren-state)))
+ (when (cadr paren-state)
+ (setcar (cdr elt) (c-state-cache-top-lparen paren-state))
+ elt)))))
+
+(defun c-laomib-invalidate-cache (beg _end)
+ ;; Called from late in c-before-change. Amend `c-laomib-cache' to remove
+ ;; details pertaining to the buffer after position BEG.
+ (save-excursion
+ (goto-char beg)
+ (let ((paren-state (c-parse-state)))
+ (dolist (elt c-laomib-cache)
+ (when (not (c-laomib-fix-elt beg elt paren-state))
+ (setq c-laomib-cache (delq elt c-laomib-cache)))))))
+
(defun c-looking-at-or-maybe-in-bracelist (&optional containing-sexp lim)
;; Point is at an open brace. If this starts a brace list, return a list
;; whose car is the buffer position of the start of the construct which
@@ -11647,11 +11837,6 @@
;; Here, "brace list" does not include the body of an enum.
(save-excursion
(let ((start (point))
- (class-key
- ;; Pike can have class definitions anywhere, so we must
- ;; check for the class key here.
- (and (c-major-mode-is 'pike-mode)
- c-decl-block-key))
(braceassignp 'dontknow)
inexpr-brace-list bufpos macro-start res pos after-type-id-pos
in-paren parens-before-brace)
@@ -11736,79 +11921,29 @@
(t
(goto-char pos)
- ;; Checks to do on all sexps before the brace, up to the
- ;; beginning of the statement.
- (while (eq braceassignp 'dontknow)
- (cond ((eq (char-after) ?\;)
- (setq braceassignp nil))
- ((and class-key
- (looking-at class-key))
- (setq braceassignp nil))
- ((and c-has-compound-literals
- (looking-at c-return-key))
- (setq braceassignp t)
- nil)
- ((eq (char-after) ?=)
- ;; We've seen a =, but must check earlier tokens so
- ;; that it isn't something that should be ignored.
- (setq braceassignp 'maybe)
- (while (and (eq braceassignp 'maybe)
- (zerop (c-backward-token-2 1 t lim)))
- (setq braceassignp
- (cond
- ;; Check for operator =
- ((and c-opt-op-identifier-prefix
- (looking-at c-opt-op-identifier-prefix))
- nil)
- ;; Check for `<opchar>= in Pike.
- ((and (c-major-mode-is 'pike-mode)
- (or (eq (char-after) ?`)
- ;; Special case for Pikes
- ;; `[]=, since '[' is not in
- ;; the punctuation class.
- (and (eq (char-after) ?\[)
- (eq (char-before) ?`))))
- nil)
- ((looking-at "\\s.") 'maybe)
- ;; make sure we're not in a C++ template
- ;; argument assignment
- ((and
- (c-major-mode-is 'c++-mode)
- (save-excursion
- (let ((here (point))
- (pos< (progn
- (skip-chars-backward "^<>")
- (point))))
- (and (eq (char-before) ?<)
- (not (c-crosses-statement-barrier-p
- pos< here))
- (not (c-in-literal))
- ))))
- nil)
- (t t)))))
- ((and
- (c-major-mode-is 'c++-mode)
- (eq (char-after) ?\[)
- ;; Be careful of "operator []"
- (not (save-excursion
- (c-backward-token-2 1 nil lim)
- (looking-at c-opt-op-identifier-prefix))))
- (setq braceassignp t)
- nil))
- (when (eq braceassignp 'dontknow)
- (cond ((and
- (not (eq (char-after) ?,))
- (save-excursion
- (c-backward-syntactic-ws)
- (eq (char-before) ?})))
- (setq braceassignp nil))
- ((/= (c-backward-token-2 1 t lim) 0)
- (if (save-excursion
- (and c-has-compound-literals
- (eq (c-backward-token-2 1 nil lim) 0)
- (eq (char-after) ?\()))
- (setq braceassignp t)
- (setq braceassignp nil))))))
+ (when (eq braceassignp 'dontknow)
+ (let* ((cache-entry (c-laomib-get-cache containing-sexp))
+ (lim2 (or (cadr cache-entry) lim))
+ sub-bassign-p)
+ (if cache-entry
+ (cond
+ ((<= (point) (cadr cache-entry))
+ (goto-char (nth 2 cache-entry))
+ (setq braceassignp (nth 3 cache-entry)))
+ ((> (point) (cadr cache-entry))
+ (setq sub-bassign-p (c-laomib-loop lim2))
+ (if (<= (point) (cadr cache-entry))
+ (progn
+ (c-laomib-put-cache containing-sexp
+ start (nth 2 cache-entry)
+ sub-bassign-p)
+ (setq braceassignp (nth 3 cache-entry))
+ (goto-char (nth 2 cache-entry)))
+ (setq braceassignp sub-bassign-p)))
+ (t))
+
+ (setq braceassignp (c-laomib-loop lim))
+ (c-laomib-put-cache lim start (point) braceassignp))))
(cond
(braceassignp
diff -r e43499573c2d cc-mode.el
--- a/cc-mode.el Tue Dec 15 11:12:15 2020 +0000
+++ b/cc-mode.el Thu Dec 17 14:57:35 2020 +0000
@@ -627,6 +627,8 @@
(make-local-variable 'fill-paragraph-function)
(setq fill-paragraph-function 'c-fill-paragraph)
+ ;; Initialize the cache for `c-looking-at-or-maybe-in-bracelist'.
+ (setq c-laomib-cache nil)
;; Initialize the three literal sub-caches.
(c-truncate-lit-pos-cache 1)
;; Initialize the cache of brace pairs, and opening braces/brackets/parens.
@@ -2034,7 +2036,9 @@
(if c-get-state-before-change-functions
(mapc (lambda (fn)
(funcall fn beg end))
- c-get-state-before-change-functions))))
+ c-get-state-before-change-functions))
+
+ (c-laomib-invalidate-cache beg end)))
(c-clear-string-fences))))
(c-truncate-lit-pos-cache beg)
;; The following must be done here rather than in `c-after-change'
@@ -2183,7 +2187,8 @@
old-pos
(new-pos pos)
capture-opener
- bod-lim bo-decl)
+ bod-lim bo-decl
+ paren-state containing-brace)
(goto-char (c-point 'bol new-pos))
(unless lit-start
(setq bod-lim (c-determine-limit 500))
@@ -2202,12 +2207,16 @@
(setq old-pos (point))
(let (pseudo)
(while
- (progn
- (c-syntactic-skip-backward "^;{}" bod-lim t)
- (and (eq (char-before) ?})
- (save-excursion
- (backward-char)
- (setq pseudo (c-cheap-inside-bracelist-p (c-parse-state))))))
+ (and
+ ;; N.B. `c-syntactic-skip-backward' doesn't check (> (point)
+ ;; lim) and can loop if that's not the case.
+ (> (point) bod-lim)
+ (progn
+ (c-syntactic-skip-backward "^;{}" bod-lim t)
+ (and (eq (char-before) ?})
+ (save-excursion
+ (backward-char)
+ (setq pseudo (c-cheap-inside-bracelist-p (c-parse-state)))))))
(goto-char pseudo))
t)
(> (point) bod-lim)
@@ -2240,7 +2249,14 @@
(and (eq (char-before) ?{)
(save-excursion
(backward-char)
- (consp (c-looking-at-or-maybe-in-bracelist))))
+ (setq paren-state (c-parse-state))
+ (while
+ (and
+ (setq containing-brace
+ (c-pull-open-brace paren-state))
+ (not (eq (char-after containing-brace) ?{))))
+ (consp (c-looking-at-or-maybe-in-bracelist
+ containing-brace containing-brace))))
)))
(not (bobp)))
(backward-char)) ; back over (, [, <.
--
Alan Mackenzie (Nuremberg, Germany).
next prev parent reply other threads:[~2020-12-17 15:08 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-12-15 3:59 bug#45248: 28.0.50; Emacs freezes with big C functions Ravine Var
2020-12-15 16:05 ` Eli Zaretskii
2020-12-17 15:08 ` Alan Mackenzie [this message]
2020-12-18 11:17 ` Ravine Var
2020-12-18 17:29 ` Alan Mackenzie
2020-12-19 4:28 ` Ravine Var
2020-12-22 20:40 ` Alan Mackenzie
2020-12-23 3:18 ` Ravine Var
2020-12-24 11:50 ` Alan Mackenzie
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=X9t0cm6EgAszn4Hf@ACM \
--to=acm@muc.de \
--cc=45248@debbugs.gnu.org \
--cc=ravine.var@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).