commit 72255c99925b7856c71f1655c53d68473d87f803 (HEAD, refs/remotes/origin/master) Author: João Távora Date: Sun Oct 19 23:35:13 2025 +0100 Flymake: fix daemon-mode + and window client fringe usage (bug#77313) To make Flymake's margin/fringe decision correct, four cases must be tested. (1) A daemon that starts flymake-mode on a buffer and then connecting a graphical client. Here fringes must be used. (2) Like 1, but with a tty client. Here margins must be used automatically. (3) A non-daemonic start of a graphical emacs. (4) A non-daemonic start of a tty emacs. The code was failing case 1. 1 and 2 are the trickiest to get right and currently can't be gotten right unless the margin auto-resize is done at buffer annotation time. Additionally, fix bugs in 'flymake-start's buffer visibility logic. Refactor duplicate code in 'flymake--highlight-line' and fix docstrings. * lisp/progmodes/flymake.el (flymake-autoresize-margins): Fix focstring. (flymake--bs-display): Rename from flymake--indicator-overlay-spec. Fix docstring. Return just the display spec. (flymake--suitably-fringed-p): New helper, instead of duplication. (flymake--resize-margins): Cut up into two functions. (flymake--restore-margins): Dedicated to restoring functions. (flymake--highlight-line): Simplify logic when flymake-indicator-type is 'auto'. (flymake-start): Fix visibility logic to account for daemon mode. Maybe resize margins here. (flymake-mode): Call restore margins. diff --git a/lisp/progmodes/flymake.el b/lisp/progmodes/flymake.el index b1d03f8e9ad..0585d128d31 100644 --- a/lisp/progmodes/flymake.el +++ b/lisp/progmodes/flymake.el @@ -229,9 +229,9 @@ ASCII equivalent if that character is not displayable by the terminal." (face :tag "Face")))) (defcustom flymake-autoresize-margins t - "If non-nil, automatically resize margin-width calling `flymake--resize-margins'. + "If non-nil, automatically resize margin-width. -Only relevant if `flymake-indicator-type' is set to margins." +Only relevant if `flymake-indicator-type' is set to `margins' or `auto'." :version "30.1" :type 'boolean) @@ -839,14 +839,21 @@ associated `flymake-category' return DEFAULT." (flymake--lookup-type-property type 'severity (warning-numeric-level :error))) -(defun flymake--indicator-overlay-spec (type) - "Return INDICATOR as propertized string to use in error indicators." +(defun flymake--suitably-fringed-p (&optional window) + "Tell if WINDOW is suitably fringed-up fro Flymake." + (cl-case flymake-fringe-indicator-position + (left-fringe (< 0 (nth 0 (window-fringes window)))) + (right-fringe (< 0 (nth 1 (window-fringes window)))))) + +(defun flymake--bs-display (type where) + "Return a `display' spec for an overlay's `before-string'. +The overlay will represent a diagnostic of type TYPE. WHERE is the +symbol `fringes' or the symbol `margins'." (let* ((indicator (flymake--lookup-type-property type - (cond ((eq flymake-indicator-type 'fringes) - 'flymake-bitmap) - ((eq flymake-indicator-type 'margins) - 'flymake-margin-string)) + (cl-case where + (fringes 'flymake-bitmap) + (margins 'flymake-margin-string)) (alist-get 'bitmap (alist-get type ; backward compat flymake-diagnostic-types-alist)))) (value (if (symbolp indicator) @@ -856,61 +863,48 @@ associated `flymake-category' return DEFAULT." value (list value))) (indicator-car (car valuelist))) - - (cond - ((and (symbolp indicator-car) - flymake-fringe-indicator-position) - (propertize "!" 'display - (cons flymake-fringe-indicator-position valuelist))) - ((and (stringp indicator-car) - flymake-margin-indicator-position) - (propertize "!" - 'display - `((margin ,flymake-margin-indicator-position) - ,(propertize indicator-car - 'face `(:inherit (,(cdr valuelist) default)) - 'mouse-face 'highlight - 'help-echo "Open Flymake diagnostics" - 'keymap (let ((map (make-sparse-keymap))) - (define-key - map `[,flymake-margin-indicator-position mouse-1] - #'flymake-show-buffer-diagnostics) - map)))))))) - -(defun flymake--resize-margins (&optional orig-width) - "Resize current window margins according to `flymake-margin-indicator-position'. -Return to original margin width if ORIG-WIDTH is non-nil." - (when (and (or (eq flymake-indicator-type 'margins) - (and (eq flymake-indicator-type 'auto) - (not (cl-case flymake-fringe-indicator-position - (left-fringe (< 0 (nth 0 (window-fringes)))) - (right-fringe (< 0 (nth 1 (window-fringes)))))))) - flymake-autoresize-margins) - (cond - ((and orig-width flymake--original-margin-width) - (if (eq flymake-margin-indicator-position 'left-margin) - (setq left-margin-width flymake--original-margin-width) - (setq right-margin-width flymake--original-margin-width))) - (t - (let* ((indicators - (mapcar (lambda (sym) - (let ((ind (get sym 'flymake-margin-string))) - (when (and (equal (car ind) "‼") - (not (char-displayable-p ?‼))) - (setq ind (cons "!!" (cdr ind))) - (put sym 'flymake-margin-string ind)) - (car ind))) - '(flymake-error flymake-warning flymake-note))) - (width (apply #'max (mapcar #'string-width indicators)))) - (if (eq flymake-margin-indicator-position 'left-margin) - (setq flymake--original-margin-width left-margin-width - left-margin-width width) - (setq flymake--original-margin-width right-margin-width - right-margin-width width))))) - ;; Apply margin to all windows available. - (mapc (lambda (x) - (set-window-buffer x (window-buffer x))) - (get-buffer-window-list nil nil 'visible)))) + (cond ((and (symbolp indicator-car) + flymake-fringe-indicator-position) + (cons flymake-fringe-indicator-position valuelist)) + ((and (stringp indicator-car) + flymake-margin-indicator-position) + `((margin ,flymake-margin-indicator-position) + ,(propertize + indicator-car + 'face `(:inherit (,(cdr valuelist) default)) + 'mouse-face 'highlight + 'help-echo "Open Flymake diagnostics" + 'keymap (let ((map (make-sparse-keymap))) + (define-key + map `[,flymake-margin-indicator-position mouse-1] + #'flymake-show-buffer-diagnostics) + map))))))) + +(defun flymake--restore-margins () + (when flymake--original-margin-width + (if (eq flymake-margin-indicator-position 'left-margin) + (setq left-margin-width flymake--original-margin-width) + (setq right-margin-width flymake--original-margin-width)))) + +(defun flymake--resize-margins () + (let* ((indicators + (mapcar (lambda (sym) + (let ((ind (get sym 'flymake-margin-string))) + (when (and (equal (car ind) "‼") + (not (char-displayable-p ?‼))) + (setq ind (cons "!!" (cdr ind))) + (put sym 'flymake-margin-string ind)) + (car ind))) + '(flymake-error flymake-warning flymake-note))) + (width (apply #'max (mapcar #'string-width indicators)))) + (if (eq flymake-margin-indicator-position 'left-margin) + (setq flymake--original-margin-width left-margin-width + left-margin-width width) + (setq flymake--original-margin-width right-margin-width + right-margin-width width))) + (mapc (lambda (x) + (set-window-buffer x (window-buffer x))) + (get-buffer-window-list nil nil 'visible))) (defun flymake--equal-diagnostic-p (a b) "Tell if A and B are equivalent `flymake--diag' objects." @@ -1021,27 +1015,14 @@ Return nil or the overlay created." (default-maybe 'face 'flymake-error) (default-maybe 'before-string - (if (eq flymake-indicator-type 'auto) - (let ((condition - `(< 0 (nth ,(cl-case flymake-fringe-indicator-position - (left-fringe 0) - (right-fringe 1)) - (window-fringes))))) - (propertize - "!" 'display - `((when ,condition - . ,(get-text-property - 0 'display - (let ((flymake-indicator-type 'fringes)) - (flymake--indicator-overlay-spec type)))) - (when (not ,condition) - . ,(get-text-property - 0 'display - (let ((flymake-indicator-type 'margins)) - (flymake--indicator-overlay-spec type))))))) - (flymake--indicator-overlay-spec type))) - ;; (default-maybe 'after-string - ;; (flymake--diag-text diagnostic)) + (propertize + "!" 'display + (if (eq flymake-indicator-type 'auto) + `((when (flymake--suitably-fringed-p) . + ,(flymake--bs-display type 'fringes)) + (when (not (flymake--suitably-fringed-p)) . + ,(flymake--bs-display type 'margins))) + (flymake--bs-display type flymake-indicator-type)))) (default-maybe 'help-echo (lambda (window _ov pos) (with-selected-window window @@ -1407,8 +1388,11 @@ Interactively, with a prefix arg, FORCE is t." deferred)) (buffer (current-buffer))) (cl-labels - ((start-post-command - () + ((visible-buffer-window () + (and (or (not (daemonp)) + (not (null (frame-parameter nil 'client)))) + (get-buffer-window (current-buffer)))) + (start-post-command () (remove-hook 'post-command-hook #'start-post-command nil) ;; The buffer may have disappeared already, e.g. because of @@ -1416,22 +1400,30 @@ Interactively, with a prefix arg, FORCE is t." (when (buffer-live-p buffer) (with-current-buffer buffer (flymake-start (remove 'post-command deferred) force)))) - (start-on-display - () + (start-on-display () (remove-hook 'window-configuration-change-hook #'start-on-display 'local) - (flymake-start (remove 'on-display deferred) force))) + ;; Double check that buffer is actually visible (bug#77313) + (if (visible-buffer-window) + (setq deferred (remove 'on-display deferred))) + (flymake-start deferred force))) (cond ((and (memq 'post-command deferred) this-command) (add-hook 'post-command-hook #'start-post-command 'append nil)) ((and (memq 'on-display deferred) - (not (get-buffer-window (current-buffer)))) + (not (visible-buffer-window))) (add-hook 'window-configuration-change-hook #'start-on-display 'append 'local)) (flymake-mode + ;; The buffer about to be annotated is visible. Check + ;; necessary conditions to auto-set margins here (bug#77313) + (let* ((w (and (eq flymake-indicator-type 'auto) + flymake-autoresize-margins + (visible-buffer-window)))) + (unless (flymake--suitably-fringed-p w) (flymake--resize-margins))) (setq flymake-check-start-time (float-time)) (let ((backend-args (and @@ -1535,8 +1527,9 @@ special *Flymake log* buffer." :group 'flymake :lighter (add-hook 'kill-buffer-hook 'flymake-kill-buffer-hook nil t) (add-hook 'eldoc-documentation-functions 'flymake-eldoc-function t t) - ;; AutoResize margins. - (flymake--resize-margins) + ;; Maybe auto-resize margins + (when (and (eq flymake-indicator-type 'margins) flymake-autoresize-margins) + (flymake--resize-margins)) ;; We can't just `clrhash' `flymake--state': there may be in ;; in-transit requests from other backends if `flymake-mode' was @@ -1554,8 +1547,8 @@ special *Flymake log* buffer." :group 'flymake :lighter ;;+(remove-hook 'find-file-hook (function flymake-find-file-hook) t) (remove-hook 'eldoc-documentation-functions 'flymake-eldoc-function t) - ;; return margin to original size - (flymake--resize-margins t) + ;; return any resized margin to original size + (flymake--restore-margins) (when flymake-timer (cancel-timer flymake-timer) commit 7df7a90e2cd0487fa48600047398766b2882bd79 Author: Juri Linkov Date: Sun Oct 19 20:56:30 2025 +0300 * lisp/progmodes/compile.el (compilation--set-up-margin): Fix. Call 'set-window-margins' only on the compilation buffer where the hook 'window-buffer-change-functions' is set buffer-locally (bug#79640). diff --git a/lisp/progmodes/compile.el b/lisp/progmodes/compile.el index 8cfa793cfc6..01152328577 100644 --- a/lisp/progmodes/compile.el +++ b/lisp/progmodes/compile.el @@ -3062,7 +3062,8 @@ Actual value is never used, only the text property.") (defun compilation--set-up-margin (w) "Setup the margin for \"=>\" in window W if it isn't already set up." - (set-window-margins w (+ (or (car (window-margins w)) 0) 2))) + (when (eq (window-buffer w) (current-buffer)) + (set-window-margins w (+ (or (car (window-margins w)) 0) 2)))) (defun compilation--tear-down-margin (w) "Remove the margin for \"=>\" if it is setup in window W." commit 3212bdc4642afe1d1d57e78de308a98449c924af Author: Juri Linkov Date: Sun Oct 19 20:41:12 2025 +0300 New value 'auto' of user option 'flymake-indicator-type' used by default * lisp/progmodes/flymake.el (flymake-indicator-type): Change the default value from 'fringes' to 'auto'. (flymake--resize-margins): Use margins when 'flymake-indicator-type' is 'auto', but fringes are no available. (flymake--highlight-line): Use display condition '(when CONDITION . SPEC)' that checks for 'window-fringes' at display time and switches between fringes and margins (bug#77313). diff --git a/etc/NEWS b/etc/NEWS index 126a17ceb8a..63d8ed74f85 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2792,6 +2792,10 @@ The tabulated listings produced by 'flymake-show-buffer-diagnostics' and 'flymake-show-project-diagnostics' now automatically adjust their column widths based on content, optimizing display space and readability. +*** New value 'auto' of user option 'flymake-indicator-type'. +This value set by default tries to use fringes if possible, +otherwise falls back to margins. + *** New user option 'elisp-flymake-byte-compile-executable'. This allows customizing the Emacs executable used for Flymake byte compilation in 'emacs-lisp-mode'. This option should be set when editing diff --git a/lisp/progmodes/flymake.el b/lisp/progmodes/flymake.el index 88ebf1ccd97..b1d03f8e9ad 100644 --- a/lisp/progmodes/flymake.el +++ b/lisp/progmodes/flymake.el @@ -185,21 +185,24 @@ See `flymake-error-bitmap' and `flymake-warning-bitmap'." (const right-fringe) (const :tag "No fringe indicators" nil))) -(defcustom flymake-indicator-type 'fringes +(defcustom flymake-indicator-type 'auto "Indicate which indicator type to use for display errors. The value can be nil (don't indicate errors but just highlight them), -the symbol `fringes' (use fringes) or the symbol `margins' (use -margins). +the symbol `fringes' (use fringes), the symbol `margins' (use margins), +or the symbol `auto' to automatically guess. Difference between fringes and margin is that fringes support displaying bitmaps on graphical displays and margins display text in a blank area from current buffer that works in both graphical and text displays. +When margins are selected, Flymake may need to resize them for each +buffer. See `flymake-autoresize-margins'. See Info node `Fringes' and Info node `(elisp)Display Margins'." :version "31.1" :type '(choice (const :tag "Use Fringes" fringes) (const :tag "Use Margins" margins) + (const :tag "Use fringes if possible, otherwise margins" auto) (const :tag "No indicators" nil))) (defcustom flymake-margin-indicators-string @@ -877,7 +880,11 @@ associated `flymake-category' return DEFAULT." (defun flymake--resize-margins (&optional orig-width) "Resize current window margins according to `flymake-margin-indicator-position'. Return to original margin width if ORIG-WIDTH is non-nil." - (when (and (eq flymake-indicator-type 'margins) + (when (and (or (eq flymake-indicator-type 'margins) + (and (eq flymake-indicator-type 'auto) + (not (cl-case flymake-fringe-indicator-position + (left-fringe (< 0 (nth 0 (window-fringes)))) + (right-fringe (< 0 (nth 1 (window-fringes)))))))) flymake-autoresize-margins) (cond ((and orig-width flymake--original-margin-width) @@ -1012,7 +1019,27 @@ Return nil or the overlay created." (overlay-put ov prop (flymake--lookup-type-property type prop value))))) (default-maybe 'face 'flymake-error) - (default-maybe 'before-string (flymake--indicator-overlay-spec type)) + (default-maybe + 'before-string + (if (eq flymake-indicator-type 'auto) + (let ((condition + `(< 0 (nth ,(cl-case flymake-fringe-indicator-position + (left-fringe 0) + (right-fringe 1)) + (window-fringes))))) + (propertize + "!" 'display + `((when ,condition + . ,(get-text-property + 0 'display + (let ((flymake-indicator-type 'fringes)) + (flymake--indicator-overlay-spec type)))) + (when (not ,condition) + . ,(get-text-property + 0 'display + (let ((flymake-indicator-type 'margins)) + (flymake--indicator-overlay-spec type))))))) + (flymake--indicator-overlay-spec type))) ;; (default-maybe 'after-string ;; (flymake--diag-text diagnostic)) (default-maybe 'help-echo commit 4faebea8222eeaf1b2abb7391c38874dc991918f Author: Sean Whitton Date: Sun Oct 19 16:41:11 2025 +0100 vc-git-registered: Be quieter when Git is not installed * lisp/vc/vc-git.el (vc-git-registered): If Git is not installed, don't echo a message about it. diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index 0c06bc298d0..1a4e10ea9f2 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -292,27 +292,34 @@ Good example of file name that needs this: \"test[56].xx\".") (defun vc-git-registered (file) "Check whether FILE is registered with git." (let ((dir (vc-git-root file))) - (when dir - (with-temp-buffer - (let* (process-file-side-effects - ;; Do not use the `file-name-directory' here: git-ls-files - ;; sometimes fails to return the correct status for relative - ;; path specs. - ;; See also: https://marc.info/?l=git&m=125787684318129&w=2 - (name (file-relative-name file dir)) - (str (with-demoted-errors "Error: %S" - (cd dir) - (vc-git--out-ok "ls-files" "-c" "-z" "--" name) - ;; If result is empty, use ls-tree to check for deleted - ;; file. - (when (eq (point-min) (point-max)) - (vc-git--out-ok "ls-tree" "--name-only" "-z" "HEAD" - "--" name)) - (buffer-string)))) - (and str - (> (length str) (length name)) - (string= (substring str 0 (1+ (length name))) - (concat name "\0")))))))) + (and dir + ;; If git(1) isn't installed then the `with-demoted-errors' + ;; below will mean we get an error message echoed about that + ;; fact with every `find-file'. That's noisy, and inconsistent + ;; with other backend's `vc-*-registered' functions which are + ;; quieter in the case that the VCS isn't installed. So check + ;; up here that git(1) is available. See also bug#18481. + (executable-find vc-git-program) + (with-temp-buffer + (let* (process-file-side-effects + ;; Do not use the `file-name-directory' here: git-ls-files + ;; sometimes fails to return the correct status for relative + ;; path specs. + ;; See also: https://marc.info/?l=git&m=125787684318129&w=2 + (name (file-relative-name file dir)) + (str (with-demoted-errors "Error: %S" + (cd dir) + (vc-git--out-ok "ls-files" "-c" "-z" "--" name) + ;; If result is empty, use ls-tree to check for deleted + ;; file. + (when (eq (point-min) (point-max)) + (vc-git--out-ok "ls-tree" "--name-only" "-z" "HEAD" + "--" name)) + (buffer-string)))) + (and str + (> (length str) (length name)) + (string= (substring str 0 (1+ (length name))) + (concat name "\0")))))))) (defvar vc-git--program-version nil) commit b0012cb8e1274df328e9e3ee8c98c01b87249a45 Author: Sean Whitton Date: Sun Oct 19 15:39:22 2025 +0100 vc-rename-file: Make it work regardless of default-directory * lisp/vc/vc.el (vc-rename-file): Make it work regardless of default-directory; now the only influence of default-directory should be resolving OLD and NEW if they are relative file names. * test/lisp/vc/vc-tests/vc-tests.el (vc-test--rename-file): Test the influence of default-directory on vc-rename-file. diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index 900eae3bbcb..bc43239354e 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -4204,8 +4204,8 @@ file names." ;;;###autoload (defun vc-rename-file (old new) - "Rename file OLD to NEW in both work area and repository. -If called interactively, read OLD and NEW, defaulting OLD to the + "Rename file OLD to NEW in both working tree and repository. +When called interactively, read OLD and NEW, defaulting OLD to the current buffer's file name if it's under version control." ;; FIXME: Support renaming whole directories. ;; The use of `vc-call' will need to change to something like @@ -4217,15 +4217,19 @@ current buffer's file name if it's under version control." ;; ;; as was done in `vc-revert-file'; see bug#43464. --spwhitton (interactive (list (read-file-name "VC rename file: " nil - (when (vc-backend buffer-file-name) - buffer-file-name) t) + (and (vc-backend buffer-file-name) + buffer-file-name) + t) (read-file-name "Rename to: "))) ;; in CL I would have said (setq new (merge-pathnames new old)) (let ((old-base (file-name-nondirectory old))) - (when (and (not (string= "" old-base)) - (string= "" (file-name-nondirectory new))) + (when (and (not (string-empty-p old-base)) + (string-empty-p (file-name-nondirectory new))) (setq new (concat new old-base)))) - (let ((oldbuf (get-file-buffer old))) + (cl-callf expand-file-name old) + (cl-callf expand-file-name new) + (let ((oldbuf (get-file-buffer old)) + (default-directory (file-name-directory old))) (when (and oldbuf (buffer-modified-p oldbuf)) (error "Please save files before moving them")) (when (get-file-buffer new) diff --git a/test/lisp/vc/vc-tests/vc-tests.el b/test/lisp/vc/vc-tests/vc-tests.el index b4192555efd..960bc83f357 100644 --- a/test/lisp/vc/vc-tests/vc-tests.el +++ b/test/lisp/vc/vc-tests/vc-tests.el @@ -570,7 +570,15 @@ This checks also `vc-backend' and `vc-responsible-backend'." (vc-register (list backend (list (file-name-nondirectory tmp-name)))) - (vc-rename-file tmp-name new-name) + ;; Test that `vc-rename-file' isn't affected by + ;; `default-directory' except for the meaning of OLD and + ;; NEW if they are relative file names. + (let ((tmp-name (file-relative-name tmp-name + temporary-file-directory)) + (new-name (file-relative-name new-name + temporary-file-directory)) + (default-directory temporary-file-directory)) + (vc-rename-file tmp-name new-name)) (should (not (file-exists-p tmp-name))) (should (file-exists-p new-name)) commit 04cb852c13e5dca1dedc90befc4f57b7b7b11ed6 Author: Mattias Engdegård Date: Sun Oct 19 15:14:20 2025 +0200 ; * src/bytecode.c (exec_byte_code): editing mistake (thanks Pip!) diff --git a/src/bytecode.c b/src/bytecode.c index 64d9e5d27c6..e3e36d9b315 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -858,7 +858,7 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, if (BYTE_CODE_SAFE && !(arg >= 0 && arg < bytestr_length)) emacs_abort (); const unsigned char *new_pc = bytestr_data + arg; - quitcounter += arg < 0; + quitcounter += new_pc < pc; if (!quitcounter) { quitcounter = 1; commit 346213f88373c50e09d620781c3dc51c21e92254 Author: Mattias Engdegård Date: Sun Oct 19 15:02:08 2025 +0200 Disable -Wclobbered for GCC in exec_byte_code (bug#79610) * src/bytecode.c (exec_byte_code): Disable the -Wclobbered warning for GCC locally as the old work-around doesn't appear to be effective for variables with explicitly assigned registers. This also allows us to remove that work-around which improves the code somewhat. diff --git a/src/bytecode.c b/src/bytecode.c index ad3844d0213..64d9e5d27c6 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -463,6 +463,14 @@ valid_sp (struct bc_thread_state *bc, Lisp_Object *sp) #define BC_REG_PC #endif +/* It seems difficult to avoid spurious -Wclobbered diagnostics from GCC + in exec_byte_code, so turn the warning off around that function. + See . */ +#if __GNUC__ && !__clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclobbered" +#endif + /* Execute the byte-code in FUN. ARGS_TEMPLATE is the function arity encoded as an integer (the one in FUN is ignored), and ARGS, of size NARGS, should be a vector of the actual arguments. The @@ -540,11 +548,6 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, for (ptrdiff_t i = nargs - rest; i < nonrest; i++) PUSH (Qnil); -#if GCC_LINT && __GNUC__ && !__clang__ - Lisp_Object *volatile saved_vectorp; - unsigned char const *volatile saved_bytestr_data; -#endif - while (true) { ptrdiff_t op; @@ -987,12 +990,6 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, Lisp_Object fun = fp->fun; Lisp_Object bytestr = AREF (fun, CLOSURE_CODE); Lisp_Object vector = AREF (fun, CLOSURE_CONSTANTS); -#if GCC_LINT && __GNUC__ && !__clang__ - /* These useless assignments pacify GCC 14.2.1 x86-64 - . */ - bytestr_data = saved_bytestr_data; - vectorp = saved_vectorp; -#endif bytestr_data = SDATA (bytestr); vectorp = XVECTOR (vector)->contents; if (BYTE_CODE_SAFE) @@ -1006,10 +1003,6 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, goto op_branch; } -#if GCC_LINT && __GNUC__ && !__clang__ - saved_vectorp = vectorp; - saved_bytestr_data = bytestr_data; -#endif NEXT; } @@ -1807,6 +1800,11 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, return result; } +#if __GNUC__ && !__clang__ +#pragma GCC diagnostic pop +#endif + + /* `args_template' has the same meaning as in exec_byte_code() above. */ Lisp_Object get_byte_code_arity (Lisp_Object args_template) commit 076307b16354d2968388db4c7485e09ad2a34cb7 Author: Mattias Engdegård Date: Thu Oct 16 17:01:24 2025 +0200 ; exec_byte_code: refactor branch case diff --git a/src/bytecode.c b/src/bytecode.c index c3fc3100e21..ad3844d0213 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -851,20 +851,20 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, CASE (Bgoto): arg = FETCH2; op_branch: - arg -= pc - bytestr_data; - if (BYTE_CODE_SAFE - && ! (bytestr_data - pc <= arg - && arg < bytestr_data + bytestr_length - pc)) - emacs_abort (); - quitcounter += arg < 0; - if (!quitcounter) - { - quitcounter = 1; - maybe_gc (); - maybe_quit (); - } - pc += arg; - NEXT; + { + if (BYTE_CODE_SAFE && !(arg >= 0 && arg < bytestr_length)) + emacs_abort (); + const unsigned char *new_pc = bytestr_data + arg; + quitcounter += arg < 0; + if (!quitcounter) + { + quitcounter = 1; + maybe_gc (); + maybe_quit (); + } + pc = new_pc; + NEXT; + } CASE (Bgotoifnonnil): arg = FETCH2; commit ae673ce20c22866ae4fbcae9a26afe8fad27ba54 Author: Mattias Engdegård Date: Wed Oct 8 18:03:08 2025 +0200 exec_byte_code: use fixed registers for top and pc (bug#79610) GCC seems to have difficulty allocating important global interpreter variables in registers; telling it which ones to use for 'top' and 'pc' makes a big difference and seems to ease pressure enough for it to deal with other variables as well. We do it for AMD64 and ARM64. Clang doesn't seem to need these directives. It does result in -Wclobbered warnings that seem difficult to silence. * src/bytecode.c (BC_REG_TOP, BC_REG_PC): New. (exec_byte_code): Use them. diff --git a/src/bytecode.c b/src/bytecode.c index e116936e55d..c3fc3100e21 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -449,6 +449,20 @@ valid_sp (struct bc_thread_state *bc, Lisp_Object *sp) return sp < (Lisp_Object *)fp && sp + 1 >= fp->saved_fp->next_stack; } +/* GCC seems to have difficulty putting important variables in + registers, so give it some heavy-handed assistance by specifying + which ones to use. Use callee-saved registers to reduce spill/fill. */ +#if __GNUC__ && !__clang__ && defined __x86_64__ +#define BC_REG_TOP asm ("rbx") +#define BC_REG_PC asm ("r12") +#elif __GNUC__ && !__clang__ && defined __aarch64__ +#define BC_REG_TOP asm ("x19") +#define BC_REG_PC asm ("x20") +#else +#define BC_REG_TOP +#define BC_REG_PC +#endif + /* Execute the byte-code in FUN. ARGS_TEMPLATE is the function arity encoded as an integer (the one in FUN is ignored), and ARGS, of size NARGS, should be a vector of the actual arguments. The @@ -466,8 +480,8 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, struct bc_thread_state *bc = ¤t_thread->bc; /* Values used for the first stack record when called from C. */ - Lisp_Object *top = NULL; - unsigned char const *pc = NULL; + register Lisp_Object *top BC_REG_TOP = NULL; + register unsigned char const *pc BC_REG_PC = NULL; Lisp_Object bytestr = AREF (fun, CLOSURE_CODE); commit fbc68bbc369b7d89b91763a687efbc0880c2420e Author: Mattias Engdegård Date: Tue Sep 23 15:01:20 2025 +0200 ; exec_byte_code: don't re-use op in FETCH2 diff --git a/src/bytecode.c b/src/bytecode.c index 6230897939e..e116936e55d 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -275,7 +275,7 @@ enum byte_code_op /* Fetch two bytes from the bytecode stream and make a 16-bit number out of them. */ -#define FETCH2 (op = FETCH, op | (FETCH << 8)) +#define FETCH2 (pc += 2, pc[-2] | pc[-1] << 8) /* Push X onto the execution stack. The expression X should not contain TOP, to avoid competing side effects. */ commit b72e352cb42cf66e8750f6e4f53331730f544589 Author: Mattias Engdegård Date: Mon Sep 22 18:33:01 2025 +0200 exec_byte_code: reduce use of semi-global 'op' (bug#79610) * src/bytecode.c (exec_byte_code): Re-type op from int to ptrdiff_t, which avoids some useless conversions. Reduce its use by using local variables for intra-block use, and another variable (arg) where it doesn't need to be alive across instruction dispatch. We also eliminate it where performance doesn't matter by re-fetching it from the instruction stream. All this should help the register allocator. diff --git a/src/bytecode.c b/src/bytecode.c index 424d00f20ec..6230897939e 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -533,7 +533,8 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, while (true) { - int op; + ptrdiff_t op; + ptrdiff_t arg; enum handlertype type; if (BYTE_CODE_SAFE && !valid_sp (bc, top)) @@ -601,7 +602,7 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, FIRST { CASE (Bvarref7): - op = FETCH2; + arg = FETCH2; goto varref; CASE (Bvarref): @@ -610,16 +611,16 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, CASE (Bvarref3): CASE (Bvarref4): CASE (Bvarref5): - op -= Bvarref; + arg = op - Bvarref; goto varref; /* This seems to be the most frequently executed byte-code among the Bvarref's, so avoid a goto here. */ CASE (Bvarref6): - op = FETCH; + arg = FETCH; varref: { - Lisp_Object v1 = vectorp[op], v2; + Lisp_Object v1 = vectorp[arg], v2; if (XBARE_SYMBOL (v1)->u.s.redirect != SYMBOL_PLAINVAL || (v2 = XBARE_SYMBOL (v1)->u.s.val.value, BASE_EQ (v2, Qunbound))) @@ -631,7 +632,7 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, CASE (Bgotoifnil): { Lisp_Object v1 = POP; - op = FETCH2; + arg = FETCH2; if (NILP (v1)) goto op_branch; NEXT; @@ -679,18 +680,18 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, CASE (Bvarset3): CASE (Bvarset4): CASE (Bvarset5): - op -= Bvarset; + arg = op - Bvarset; goto varset; CASE (Bvarset7): - op = FETCH2; + arg = FETCH2; goto varset; CASE (Bvarset6): - op = FETCH; + arg = FETCH; varset: { - Lisp_Object sym = vectorp[op]; + Lisp_Object sym = vectorp[arg]; Lisp_Object val = POP; if (XBARE_SYMBOL (sym)->u.s.redirect == SYMBOL_PLAINVAL && !XBARE_SYMBOL (sym)->u.s.trapped_write) @@ -710,11 +711,11 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, /* ------------------ */ CASE (Bvarbind6): - op = FETCH; + arg = FETCH; goto varbind; CASE (Bvarbind7): - op = FETCH2; + arg = FETCH2; goto varbind; CASE (Bvarbind): @@ -723,18 +724,18 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, CASE (Bvarbind3): CASE (Bvarbind4): CASE (Bvarbind5): - op -= Bvarbind; + arg = op - Bvarbind; varbind: /* Specbind can signal and thus GC. */ - specbind (vectorp[op], POP); + specbind (vectorp[arg], POP); NEXT; CASE (Bcall6): - op = FETCH; + arg = FETCH; goto docall; CASE (Bcall7): - op = FETCH2; + arg = FETCH2; goto docall; CASE (Bcall): @@ -743,10 +744,10 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, CASE (Bcall3): CASE (Bcall4): CASE (Bcall5): - op -= Bcall; + arg = op - Bcall; docall: { - DISCARD (op); + DISCARD (arg); #ifdef BYTE_CODE_METER if (byte_metering_on && SYMBOLP (TOP)) { @@ -770,7 +771,7 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, error ("Lisp nesting exceeds `max-lisp-eval-depth'"); } - ptrdiff_t call_nargs = op; + ptrdiff_t call_nargs = arg; Lisp_Object call_fun = TOP; Lisp_Object *call_args = &TOP + 1; @@ -815,11 +816,11 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, } CASE (Bunbind6): - op = FETCH; + arg = FETCH; goto dounbind; CASE (Bunbind7): - op = FETCH2; + arg = FETCH2; goto dounbind; CASE (Bunbind): @@ -828,44 +829,44 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, CASE (Bunbind3): CASE (Bunbind4): CASE (Bunbind5): - op -= Bunbind; + arg = op - Bunbind; dounbind: - unbind_to (specpdl_ref_add (SPECPDL_INDEX (), -op), Qnil); + unbind_to (specpdl_ref_add (SPECPDL_INDEX (), -arg), Qnil); NEXT; CASE (Bgoto): - op = FETCH2; + arg = FETCH2; op_branch: - op -= pc - bytestr_data; + arg -= pc - bytestr_data; if (BYTE_CODE_SAFE - && ! (bytestr_data - pc <= op - && op < bytestr_data + bytestr_length - pc)) + && ! (bytestr_data - pc <= arg + && arg < bytestr_data + bytestr_length - pc)) emacs_abort (); - quitcounter += op < 0; + quitcounter += arg < 0; if (!quitcounter) { quitcounter = 1; maybe_gc (); maybe_quit (); } - pc += op; + pc += arg; NEXT; CASE (Bgotoifnonnil): - op = FETCH2; + arg = FETCH2; if (!NILP (POP)) goto op_branch; NEXT; CASE (Bgotoifnilelsepop): - op = FETCH2; + arg = FETCH2; if (NILP (TOP)) goto op_branch; DISCARD (1); NEXT; CASE (Bgotoifnonnilelsepop): - op = FETCH2; + arg = FETCH2; if (!NILP (TOP)) goto op_branch; DISCARD (1); @@ -965,7 +966,7 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, struct handler *c = handlerlist; handlerlist = c->next; top = c->bytecode_top; - op = c->bytecode_dest; + arg = c->bytecode_dest; bc = ¤t_thread->bc; struct bc_frame *fp = bc->fp; @@ -1105,10 +1106,12 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, NEXT; CASE (BlistN): - op = FETCH; - DISCARD (op - 1); - TOP = Flist (op, &TOP); - NEXT; + { + ptrdiff_t n = FETCH; + DISCARD (n - 1); + TOP = Flist (n, &TOP); + NEXT; + } CASE (Blength): TOP = Flength (TOP); @@ -1224,10 +1227,12 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, NEXT; CASE (BconcatN): - op = FETCH; - DISCARD (op - 1); - TOP = Fconcat (op, &TOP); - NEXT; + { + ptrdiff_t n = FETCH; + DISCARD (n - 1); + TOP = Fconcat (n, &TOP); + NEXT; + } CASE (Bsub1): TOP = (FIXNUMP (TOP) && XFIXNUM (TOP) != MOST_NEGATIVE_FIXNUM @@ -1410,10 +1415,12 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, NEXT; CASE (BinsertN): - op = FETCH; - DISCARD (op - 1); - TOP = Finsert (op, &TOP); - NEXT; + { + ptrdiff_t n = FETCH; + DISCARD (n - 1); + TOP = Finsert (n, &TOP); + NEXT; + } CASE (Bpoint_max): PUSH (make_fixed_natnum (ZV)); @@ -1676,7 +1683,7 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, for that instead. */ /* CASE (Bstack_ref): */ error ("Invalid byte opcode: op=%d, ptr=%"pD"d", - op, pc - 1 - bytestr_data); + pc[-1], pc - 1 - bytestr_data); /* Handy byte-codes for lexical binding. */ CASE (Bstack_ref1): @@ -1715,14 +1722,16 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, NEXT; } CASE (BdiscardN): - op = FETCH; - if (op & 0x80) - { - op &= 0x7F; - top[-op] = TOP; - } - DISCARD (op); - NEXT; + { + ptrdiff_t n = FETCH; + if (n & 0x80) + { + n &= 0x7F; + top[-n] = TOP; + } + DISCARD (n); + NEXT; + } CASE (Bswitch): { @@ -1750,7 +1759,7 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, for (ptrdiff_t i = h->count - 1; i >= 0; i--) if (BASE_EQ (v1, HASH_KEY (h, i))) { - op = XFIXNUM (HASH_VALUE (h, i)); + arg = XFIXNUM (HASH_VALUE (h, i)); goto op_branch; } } @@ -1759,7 +1768,7 @@ exec_byte_code (Lisp_Object fun, ptrdiff_t args_template, ptrdiff_t i = hash_find (h, v1); if (i >= 0) { - op = XFIXNUM (HASH_VALUE (h, i)); + arg = XFIXNUM (HASH_VALUE (h, i)); goto op_branch; } } commit 8ed5b4378a81c2534139103e06cf285cfcf66f26 Author: Mattias Engdegård Date: Sun Oct 19 14:09:22 2025 +0200 ; ispell-tests-common.el: tidy file-matching regexp diff --git a/test/lisp/textmodes/ispell-tests/ispell-tests-common.el b/test/lisp/textmodes/ispell-tests/ispell-tests-common.el index 4bf3a79b769..8111b39f304 100644 --- a/test/lisp/textmodes/ispell-tests/ispell-tests-common.el +++ b/test/lisp/textmodes/ispell-tests/ispell-tests-common.el @@ -71,7 +71,7 @@ (ldir (buffer-substring b e)) (d (file-name-sans-extension (file-name-nondirectory - (car (directory-files ldir t ".+\\.aff$")))))) + (car (directory-files ldir t "\\.aff\\'")))))) d))) ((string-equal backend "aspell") (with-temp-buffer commit 2ae535c90bad56ebc8dc2ce521b0f0f4e1281c63 Author: Lockywolf Date: Sun Oct 19 12:14:58 2025 +0200 Fix ispell-tests on emacs CI * test/lisp/textmodes/ispell-resources/fake-aspell-new.bash Error with a broken dictionary. * test/lisp/textmodes/ispell-tests/ispell-tests-common.el (ispell-tests--constants/nonexistent-dictionary): Add. (ispell-tests--some-valid-dictionary): Give fallback dictionary. * test/lisp/textmodes/ispell-tests/ispell-tests.el (ispell/ispell-accept-buffer-local-defs/received-file): Use proper broken dictionary. diff --git a/test/lisp/textmodes/ispell-resources/fake-aspell-new.bash b/test/lisp/textmodes/ispell-resources/fake-aspell-new.bash index 0cc7ede8e38..5aa267a5888 100755 --- a/test/lisp/textmodes/ispell-resources/fake-aspell-new.bash +++ b/test/lisp/textmodes/ispell-resources/fake-aspell-new.bash @@ -17,6 +17,9 @@ if [[ "$HOME" == '' ]] ; then exit 3 fi +dictionary=UNSET +repl=UNSET + show_vv() { printf '%s\n' "@(#) International Ispell Version 3.1.20 (but really Aspell 0.60.0)" @@ -94,11 +97,24 @@ while :; do exit ;; -a) # imitate REPL - imitate_pipe - exit + repl=pipe ;; + -d) + if [ "$2" ]; then + dictionary=$2 + if [[ "$dictionary" == "2110001888290146229" ]] ; then exit 3 ; fi + shift + else + printf 'ERROR: "-d" requires an argument.' 1>&2 + exit 1 + fi + ;; + -d*) + dictionary=${1#-d} + if [[ "$dictionary" == "2110001888290146229" ]] ; then exit 3 ; fi + ;; -?*) - printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 + : ;; *) break @@ -106,6 +122,11 @@ while :; do shift done -printf 'Usage: aspell [options] \n' +if [[ "$repl" == "pipe" ]] ; then + imitate_pipe +else + printf 'Usage: aspell [options] \n' + exit 4 +fi #printf 'this place should be unreachable\n' >> /tmp/lwf_mock-aspell.log diff --git a/test/lisp/textmodes/ispell-tests/ispell-tests-common.el b/test/lisp/textmodes/ispell-tests/ispell-tests-common.el index 30a4336f094..4bf3a79b769 100644 --- a/test/lisp/textmodes/ispell-tests/ispell-tests-common.el +++ b/test/lisp/textmodes/ispell-tests/ispell-tests-common.el @@ -37,15 +37,14 @@ "Path to the mock backend.") -(let* ((backend-binaries (list "ispell" "aspell" "hunspell" "enchant-2" fake-aspell-path) - ) +(let* ((backend-binaries (list "ispell" "aspell" "hunspell" "enchant-2" fake-aspell-path)) (filter-binaries (seq-filter (lambda (b) (and (executable-find b) (equal 0 (with-temp-buffer - (call-process b nil t "-a"))))) + (call-process b nil t nil "-a"))))) backend-binaries))) (defun ispell-tests--some-backend-available-p () @@ -86,7 +85,8 @@ (forward-line 1) (let* ((s (buffer-substring (point) (line-end-position)))) (file-name-sans-extension - (file-name-nondirectory s))))))) + (file-name-nondirectory s))))) + (t "english"))) (eval-when-compile (require 'cl-macs)) @@ -140,6 +140,7 @@ its best." (defconst ispell-tests--constants/russian/correct "привет") (defconst ispell-tests--constants/russian/wrong "ыфаывфафыввпфыв") (defconst ispell-tests--constants/completion "waveguides") +(defconst ispell-tests--constants/nonexistent-dictionary "2110001888290146229") (provide 'ispell-tests-common) diff --git a/test/lisp/textmodes/ispell-tests/ispell-tests.el b/test/lisp/textmodes/ispell-tests/ispell-tests.el index 605b634e9a8..b43d32ae95d 100644 --- a/test/lisp/textmodes/ispell-tests/ispell-tests.el +++ b/test/lisp/textmodes/ispell-tests/ispell-tests.el @@ -836,14 +836,14 @@ hunspell. Hence skipping." (ert-deftest ispell/ispell-accept-buffer-local-defs/received-file () "Check that `ispell-accept-buffer-local-defs' is broken when a file has a nonexistent file-local dictionary. -We do not control this data, but this should make ispell.el failx." +We do not control this data, but this should make ispell.el fail." :expected-result :failed (with-environment-variables (("HOME" temporary-file-directory)) (with-temp-buffer (ispell-tests--letopt ((ispell-program-name (ispell-tests--some-backend))) - (let ((test-dictname (format "%s" (random)))) + (let ((test-dictname ispell-tests--constants/nonexistent-dictionary)) (insert (car ispell-tests--constants/english/correct-list) "\n\n\n" ispell-dictionary-keyword test-dictname "\n")