From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Alan Mackenzie Newsgroups: gmane.emacs.bugs Subject: bug#45248: 28.0.50; Emacs freezes with big C functions Date: Thu, 17 Dec 2020 15:08:34 +0000 Message-ID: References: <87a6ufen1w.fsf@gmail.com> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="27743"; mail-complaints-to="usenet@ciao.gmane.io" Cc: acm@muc.de, 45248@debbugs.gnu.org To: Ravine Var Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Thu Dec 17 16:11:08 2020 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kpuvs-00075s-D0 for geb-bug-gnu-emacs@m.gmane-mx.org; Thu, 17 Dec 2020 16:11:08 +0100 Original-Received: from localhost ([::1]:60530 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kpuvr-0004fG-EF for geb-bug-gnu-emacs@m.gmane-mx.org; Thu, 17 Dec 2020 10:11:07 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:36816) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kputr-0003O6-CT for bug-gnu-emacs@gnu.org; Thu, 17 Dec 2020 10:09:03 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]:53353) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1kputq-00021h-3W for bug-gnu-emacs@gnu.org; Thu, 17 Dec 2020 10:09:02 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1kputp-00056i-TJ for bug-gnu-emacs@gnu.org; Thu, 17 Dec 2020 10:09:01 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Alan Mackenzie Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Thu, 17 Dec 2020 15:09:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 45248 X-GNU-PR-Package: emacs Original-Received: via spool by 45248-submit@debbugs.gnu.org id=B45248.160821773419619 (code B ref 45248); Thu, 17 Dec 2020 15:09:01 +0000 Original-Received: (at 45248) by debbugs.gnu.org; 17 Dec 2020 15:08:54 +0000 Original-Received: from localhost ([127.0.0.1]:36666 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kputa-00056F-PJ for submit@debbugs.gnu.org; Thu, 17 Dec 2020 10:08:54 -0500 Original-Received: from colin.muc.de ([193.149.48.1]:65292 helo=mail.muc.de) by debbugs.gnu.org with smtp (Exim 4.84_2) (envelope-from ) id 1kputV-00055y-O8 for 45248@debbugs.gnu.org; Thu, 17 Dec 2020 10:08:45 -0500 Original-Received: (qmail 57766 invoked by uid 3782); 17 Dec 2020 15:08:34 -0000 Original-Received: from acm.muc.de (p4fe15d46.dip0.t-ipconnect.de [79.225.93.70]) by localhost.muc.de (tmda-ofmipd) with ESMTP; Thu, 17 Dec 2020 16:08:34 +0100 Original-Received: (qmail 26025 invoked by uid 1000); 17 Dec 2020 15:08:34 -0000 Content-Disposition: inline In-Reply-To: <87a6ufen1w.fsf@gmail.com> X-Delivery-Agent: TMDA/1.1.12 (Macallan) X-Primary-Address: acm@muc.de X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.io gmane.emacs.bugs:196258 Archived-At: 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 `= 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 `= 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).