Subject: Re: bug#10652: 24.0.93; font-lock very slow for C++

From: Alan Mackenzie <[email-masked]>
Date: Thu, 1 Mar 2012 19:36:22 +0000

Hello Yidong, hello Helmut.

On Sun, Feb 26, 2012 at 05:47:25PM +0800, Chong Yidong wrote:
> Helmut Eller <[email-masked]> writes:

> > Font lock is extremely slow in this file:

> > http://hg.openjdk.java.net/jdk7/jdk7/hotspot/raw-file/b92c45f2bc75/src/share/vm/runtime/globals.hpp

> > Download the file and open it with emacs -Q globals.hpp.  Then
> > scroll around in the file to see how slow it is.

> I got Emacs into an uninterruptible loop while scrolling through the
> buffer :-(

> Looks like a regression against Emacs 23's CC mode, which handles the
> file just fine.  Alan, could you investigate?  Thanks.

OK.  First point: this is the first time I've ever seen a file with a
3350 line macro.  ;-)  There are several places in CC Mode which assume
macros are small, so it's not too surprising that this file goes slowly.

I've elp'd the scrolling, and have the following fix as a first
approximation.  Please try it out, but it's not fully tested, so don't
use it in anger.

Just one thing: if you've already got CC Mode running when you load in
this version, please do

    (setq-default c-macro-names-with-semicolon nil)

before M-x c++-mode, to clear out a stale value.

There are more optimisations I'm looking at at the moment.  Here's the
patch:

diff -r 915250820ea6 cc-engine.el
--- a/cc-engine.el	Wed Feb 29 18:59:34 2012 +0000
+++ b/cc-engine.el	Thu Mar 01 19:23:24 2012 +0000
@@ -222,6 +222,38 @@
 	       (point))))
     c-macro-start))
 
+;; One element macro cache to cope with continual movement within very large
+;; CPP macros.
+(defvar c-macro-cache nil)
+(make-variable-buffer-local 'c-macro-cache)
+;; Nil or cons of the bounds of the most recent CPP form probed by
+;; `c-beginning-of-macro', `c-end-of-macro' or `c-syntactic-end-of-macro'.
+;; The cdr will be nil if we know only the start of the CPP form.
+(defvar c-macro-cache-start-pos nil)
+(make-variable-buffer-local 'c-macro-cache-start-pos)
+;; The starting position from where we determined `c-macro-cache'.
+(defvar c-macro-cache-syntactic nil)
+(make-variable-buffer-local 'c-macro-cache-syntactic)
+;; non-nil iff `c-macro-cache' has both elements set AND the cdr is at a
+;; syntactic end of macro, not merely an apparent one.
+
+(defun c-invalidate-macro-cache (beg end)
+  ;; Called from a before-change function.  If the change region is before or
+  ;; in the macro characterised by `c-macro-cache' etc., nullify it
+  ;; appropriately.  BEG and END are the standard before-change-functions
+  ;; parameters.  END isn't used.
+  (cond
+   ((null c-macro-cache))
+   ((< beg (car c-macro-cache))
+    (setq c-macro-cache nil
+	  c-macro-cache-start-pos nil
+	  c-macro-cache-syntactic nil))
+   ((and (cdr c-macro-cache)
+	 (< beg (cdr c-macro-cache)))
+    (setcdr c-macro-cache nil)
+    (setq c-macro-cache-start-pos beg
+	  c-macro-cache-syntactic nil))))
+
 (defun c-beginning-of-macro (&optional lim)
   "Go to the beginning of a preprocessor directive.
 Leave point at the beginning of the directive and return t if in one,
@@ -229,19 +261,36 @@
 
 Note that this function might do hidden buffer changes.	 See the
 comment at the start of cc-engine.el for more info."
-  (when c-opt-cpp-prefix
-    (let ((here (point)))
-      (save-restriction
-	(if lim (narrow-to-region lim (point-max)))
-	(beginning-of-line)
-	(while (eq (char-before (1- (point))) ?\\)
-	  (forward-line -1))
-	(back-to-indentation)
-	(if (and (<= (point) here)
-		 (looking-at c-opt-cpp-start))
-	    t
-	  (goto-char here)
-	  nil)))))
+  (let ((here (point)))
+    (when c-opt-cpp-prefix
+      (if (and (car c-macro-cache)
+	       (>= (point) (car c-macro-cache))
+	       (or (and (cdr c-macro-cache)
+			(<= (point) (cdr c-macro-cache)))
+		   (<= (point) c-macro-cache-start-pos)))
+	  (unless (< (car c-macro-cache) (or lim (point-min)))
+	    (progn (goto-char (max (or lim (point-min)) (car c-macro-cache)))
+		   (setq c-macro-cache-start-pos
+			 (max c-macro-cache-start-pos here))
+		   t))
+	(setq c-macro-cache nil
+	      c-macro-cache-start-pos nil
+	      c-macro-cache-syntactic nil)
+
+	(save-restriction
+	  (if lim (narrow-to-region lim (point-max)))
+	  (beginning-of-line)
+	  (while (eq (char-before (1- (point))) ?\\)
+	    (forward-line -1))
+	  (back-to-indentation)
+	  (if (and (<= (point) here)
+		   (looking-at c-opt-cpp-start))
+	      (progn
+		(setq c-macro-cache (cons (point) nil)
+		      c-macro-cache-start-pos here)
+		t)
+	    (goto-char here)
+	    nil))))))
 
 (defun c-end-of-macro ()
   "Go to the end of a preprocessor directive.
@@ -251,12 +300,24 @@
 
 Note that this function might do hidden buffer changes.	 See the
 comment at the start of cc-engine.el for more info."
-  (while (progn
-	   (end-of-line)
-	   (when (and (eq (char-before) ?\\)
-		      (not (eobp)))
-	     (forward-char)
-	     t))))
+   (if (and (cdr c-macro-cache)
+	    (<= (point) (cdr c-macro-cache))
+	    (>= (point) (car c-macro-cache)))
+       (goto-char (cdr c-macro-cache))
+     (unless (and (car c-macro-cache)
+		  (<= (point) c-macro-cache-start-pos)
+		  (>= (point) (car c-macro-cache)))
+       (setq c-macro-cache nil
+	     c-macro-cache-start-pos nil
+	     c-macro-cache-syntactic nil))
+     (while (progn
+	      (end-of-line)
+	      (when (and (eq (char-before) ?\\)
+			 (not (eobp)))
+		(forward-char)
+		t)))
+     (when (car c-macro-cache)
+       (setcdr c-macro-cache (point)))))
 
 (defun c-syntactic-end-of-macro ()
   ;; Go to the end of a CPP directive, or a "safe" pos just before.
@@ -271,12 +332,15 @@
   ;; at the start of cc-engine.el for more info.
   (let* ((here (point))
 	 (there (progn (c-end-of-macro) (point)))
-	 (s (parse-partial-sexp here there)))
-    (while (and (or (nth 3 s)	 ; in a string
-		    (nth 4 s))	 ; in a comment (maybe at end of line comment)
-		(> there here))	 ; No infinite loops, please.
-      (setq there (1- (nth 8 s)))
-      (setq s (parse-partial-sexp here there)))
+	 s)
+    (unless c-macro-cache-syntactic
+      (setq s (parse-partial-sexp here there))
+      (while (and (or (nth 3 s)	 ; in a string
+		      (nth 4 s)) ; in a comment (maybe at end of line comment)
+		  (> there here))	; No infinite loops, please.
+	(setq there (1- (nth 8 s)))
+	(setq s (parse-partial-sexp here there)))
+      (setq c-macro-cache-syntactic (car c-macro-cache)))
     (point)))
 
 (defun c-forward-over-cpp-define-id ()
diff -r 915250820ea6 cc-fonts.el
--- a/cc-fonts.el	Wed Feb 29 18:59:34 2012 +0000
+++ b/cc-fonts.el	Thu Mar 01 19:23:24 2012 +0000
@@ -409,7 +409,8 @@
 	       (cc-eval-when-compile
 		 (boundp 'parse-sexp-lookup-properties)))
 	      (BOD-limit
-	       (c-determine-limit 1000)))
+	       (c-determine-limit 500 ;; 1000
+				  )))
 	  (goto-char
 	   (let ((here (point)))
 	     (if (eq (car (c-beginning-of-decl-1 BOD-limit)) 'same)
diff -r 915250820ea6 cc-langs.el
--- a/cc-langs.el	Wed Feb 29 18:59:34 2012 +0000
+++ b/cc-langs.el	Thu Mar 01 19:23:24 2012 +0000
@@ -444,8 +444,10 @@
   ;; For documentation see the following c-lang-defvar of the same name.
   ;; The value here may be a list of functions or a single function.
   t nil
-  c++ '(c-extend-region-for-CPP c-before-change-check-<>-operators)
-  (c objc) 'c-extend-region-for-CPP
+  c++ '(c-extend-region-for-CPP
+	c-before-change-check-<>-operators
+	c-invalidate-macro-cache)
+  (c objc) '(c-extend-region-for-CPP c-invalidate-macro-cache)
   ;; java 'c-before-change-check-<>-operators
   awk 'c-awk-record-region-clear-NL)
 (c-lang-defvar c-get-state-before-change-functions
diff -r 915250820ea6 cc-vars.el
--- a/cc-vars.el	Wed Feb 29 18:59:34 2012 +0000
+++ b/cc-vars.el	Thu Mar 01 19:23:24 2012 +0000
@@ -1653,7 +1653,8 @@
 		    c-macro-names-with-semicolon))))))
     
 (defvar c-macro-names-with-semicolon
-  '("Q_OBJECT" "Q_PROPERTY" "Q_DECLARE" "Q_ENUMS")
+;  '("Q_OBJECT" "Q_PROPERTY" "Q_DECLARE" "Q_ENUMS")
+  nil
   "List of #defined symbols whose expansion ends with a semicolon.
 Alternatively it can be a string, a regular expression which
 matches all such symbols.

-- 
Alan Mackenzie (Nuremberg, Germany).