From 04fb1664a8ee3c20ed8a231ce5c9bb05a145f8e0 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Tue, 9 Feb 2021 12:02:25 -0500 Subject: * lisp/emacs-lisp/macroexp.el: Break cycle with bytecomp/byte-opt The recent change in macroexp triggered a cyclic dependency error during eager macroexpansion when neither `bytecomp` nor `byte-opt` had been byte-compiled yet. This fixes it by moving the offending function to macroexp.el. * lisp/emacs-lisp/macroexp.el (macroexp--unfold-lambda): Move from byte-opt.el and rename. (macroexp--expand-all): Use it. * lisp/emacs-lisp/byte-opt.el (byte-compile-unfold-lambda): Move to macroexp.el. (byte-compile-inline-expand, byte-optimize-form-code-walker): * lisp/emacs-lisp/bytecomp.el (byte-compile-form): Use `macroexp--unfold-lambda` instead. --- lisp/emacs-lisp/byte-opt.el | 72 ++------------------------------------------- 1 file changed, 2 insertions(+), 70 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index abbe2a2e63f..e67077639c2 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -289,7 +289,7 @@ (byte-compile-preprocess (byte-compile--reify-function fn)))))) (if (eq (car-safe newfn) 'function) - (byte-compile-unfold-lambda `(,(cadr newfn) ,@(cdr form))) + (macroexp--unfold-lambda `(,(cadr newfn) ,@(cdr form))) ;; This can happen because of macroexp-warn-and-return &co. (byte-compile-warn "Inlining closure %S failed" name) @@ -297,74 +297,6 @@ (_ ;; Give up on inlining. form)))) - -;; ((lambda ...) ...) -(defun byte-compile-unfold-lambda (form &optional name) - ;; In lexical-binding mode, let and functions don't bind vars in the same way - ;; (let obey special-variable-p, but functions don't). But luckily, this - ;; doesn't matter here, because function's behavior is underspecified so it - ;; can safely be turned into a `let', even though the reverse is not true. - (or name (setq name "anonymous lambda")) - (let* ((lambda (car form)) - (values (cdr form)) - (arglist (nth 1 lambda)) - (body (cdr (cdr lambda))) - optionalp restp - bindings) - (if (and (stringp (car body)) (cdr body)) - (setq body (cdr body))) - (if (and (consp (car body)) (eq 'interactive (car (car body)))) - (setq body (cdr body))) - ;; FIXME: The checks below do not belong in an optimization phase. - (while arglist - (cond ((eq (car arglist) '&optional) - ;; ok, I'll let this slide because funcall_lambda() does... - ;; (if optionalp (error "multiple &optional keywords in %s" name)) - (if restp (error "&optional found after &rest in %s" name)) - (if (null (cdr arglist)) - (error "nothing after &optional in %s" name)) - (setq optionalp t)) - ((eq (car arglist) '&rest) - ;; ...but it is by no stretch of the imagination a reasonable - ;; thing that funcall_lambda() allows (&rest x y) and - ;; (&rest x &optional y) in arglists. - (if (null (cdr arglist)) - (error "nothing after &rest in %s" name)) - (if (cdr (cdr arglist)) - (error "multiple vars after &rest in %s" name)) - (setq restp t)) - (restp - (setq bindings (cons (list (car arglist) - (and values (cons 'list values))) - bindings) - values nil)) - ((and (not optionalp) (null values)) - (byte-compile-warn "attempt to open-code `%s' with too few arguments" name) - (setq arglist nil values 'too-few)) - (t - (setq bindings (cons (list (car arglist) (car values)) - bindings) - values (cdr values)))) - (setq arglist (cdr arglist))) - (if values - (progn - (or (eq values 'too-few) - (byte-compile-warn - "attempt to open-code `%s' with too many arguments" name)) - form) - - ;; The following leads to infinite recursion when loading a - ;; file containing `(defsubst f () (f))', and then trying to - ;; byte-compile that file. - ;(setq body (mapcar 'byte-optimize-form body))) - - (let ((newform - (if bindings - (cons 'let (cons (nreverse bindings) body)) - (cons 'progn body)))) - (byte-compile-log " %s\t==>\t%s" form newform) - newform)))) - ;;; implementing source-level optimizers @@ -604,7 +536,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") form) (`((lambda . ,_) . ,_) - (let ((newform (byte-compile-unfold-lambda form))) + (let ((newform (macroexp--unfold-lambda form))) (if (eq newform form) ;; Some error occurred, avoid infinite recursion. form -- cgit v1.2.3 From 6fd8548b1620aadd2c9e4efddd899b87d023913b Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Tue, 9 Feb 2021 12:10:07 -0500 Subject: * lisp/emacs-lisp/byte-opt.el (byte-optimize--pcase): New macro (byte-optimize-form-code-walker): Use it. --- lisp/emacs-lisp/byte-opt.el | 70 ++++++++++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 20 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index e67077639c2..4fa2c75a889 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -348,6 +348,40 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (symbolp (cadr expr))) (keywordp expr))) +(defmacro byte-optimize--pcase (exp &rest cases) + ;; When we do + ;; + ;; (pcase EXP + ;; (`(if ,exp ,then ,else) (DO-TEST)) + ;; (`(plus ,e2 ,e2) (DO-ADD)) + ;; (`(times ,e2 ,e2) (DO-MULT)) + ;; ...) + ;; + ;; we usually don't want to fall back to the default case if + ;; the value of EXP is of a form like `(if E1 E2)' or `(plus E1)' + ;; or `(times E1 E2 E3)', instead we either want to signal an error + ;; that EXP has an unexpected shape, or we want to carry on as if + ;; it had the right shape (ignore the extra data and pretend the missing + ;; data is nil) because it should simply never happen. + ;; + ;; The macro below implements the second option by rewriting patterns + ;; like `(if ,exp ,then ,else)' + ;; to `(if . (or `(,exp ,then ,else) pcase--dontcare))'. + ;; + ;; The resulting macroexpansion is also significantly cleaner/smaller/faster. + (declare (indent 1) (debug (form &rest (pcase-PAT body)))) + `(pcase ,exp + . ,(mapcar (lambda (case) + `(,(pcase (car case) + ((and `(,'\` (,_ . (,'\, ,_))) pat) pat) + (`(,'\` (,head . ,tail)) + (list '\` + (cons head + (list '\, `(or ,(list '\` tail) pcase--dontcare))))) + (pat pat)) + . ,(cdr case))) + cases))) + (defun byte-optimize-form-code-walker (form for-effect) ;; ;; For normal function calls, We can just mapcar the optimizer the cdr. But @@ -360,7 +394,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") ;; have no place in an optimizer: the corresponding tests should be ;; performed in `macroexpand-all', or in `cconv', or in `bytecomp'. (let ((fn (car-safe form))) - (pcase form + (byte-optimize--pcase form ((pred (not consp)) (cond ((and for-effect @@ -370,7 +404,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") nil) ((symbolp form) (let ((lexvar (assq form byte-optimize--lexvars))) - (if (cddr lexvar) ; Value available? + (if (cddr lexvar) ; Value available? (if (assq form byte-optimize--vars-outside-loop) ;; Cannot substitute; mark for retention to avoid the ;; variable being eliminated. @@ -390,27 +424,27 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (not for-effect) form)) (`(,(or 'let 'let*) . ,rest) - (cons fn (byte-optimize-let-form fn rest for-effect))) + (cons fn (byte-optimize-let-form fn rest for-effect))) (`(cond . ,clauses) ;; The condition in the first clause is always executed, but ;; right now we treat all of them as conditional for simplicity. (let ((byte-optimize--vars-outside-condition byte-optimize--lexvars)) (cons fn (mapcar (lambda (clause) - (if (consp clause) - (cons - (byte-optimize-form (car clause) nil) - (byte-optimize-body (cdr clause) for-effect)) - (byte-compile-warn "malformed cond form: `%s'" - (prin1-to-string clause)) - clause)) - clauses)))) + (if (consp clause) + (cons + (byte-optimize-form (car clause) nil) + (byte-optimize-body (cdr clause) for-effect)) + (byte-compile-warn "malformed cond form: `%s'" + (prin1-to-string clause)) + clause)) + clauses)))) (`(progn . ,exps) ;; As an extra added bonus, this simplifies (progn ) --> . (if (cdr exps) (macroexp-progn (byte-optimize-body exps for-effect)) (byte-optimize-form (car exps) for-effect))) - (`(prog1 . ,(or `(,exp . ,exps) pcase--dontcare)) + (`(prog1 ,exp . ,exps) (if exps `(prog1 ,(byte-optimize-form exp for-effect) . ,(byte-optimize-body exps t)) @@ -435,8 +469,6 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (then-opt (byte-optimize-form then for-effect)) (else-opt (byte-optimize-body else for-effect))) `(if ,test-opt ,then-opt . ,else-opt))) - (`(if . ,_) - (byte-compile-warn "too few arguments for `if'")) (`(,(or 'and 'or) . ,exps) ; Remember, and/or are control structures. ;; FIXME: We have to traverse the expressions in left-to-right @@ -474,8 +506,6 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (body (byte-optimize-body exps t))) `(while ,condition . ,body))) - (`(while . ,_) - (byte-compile-warn "too few arguments for `while'")) (`(interactive . ,_) (byte-compile-warn "misplaced interactive spec: `%s'" @@ -487,9 +517,9 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") ;; all the subexpressions and compiling them separately. form) - (`(condition-case . ,(or `(,var ,exp . ,clauses) pcase--dontcare)) + (`(condition-case ,var ,exp . ,clauses) (let ((byte-optimize--vars-outside-condition byte-optimize--lexvars)) - `(condition-case ,var ;Not evaluated. + `(condition-case ,var ;Not evaluated. ,(byte-optimize-form exp for-effect) ,@(mapcar (lambda (clause) `(,(car clause) @@ -513,7 +543,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") `(unwind-protect ,bodyform . ,(byte-optimize-body exps t)))))) - (`(catch . ,(or `(,tag . ,exps) pcase--dontcare)) + (`(catch ,tag . ,exps) (let ((byte-optimize--vars-outside-condition byte-optimize--lexvars)) `(catch ,(byte-optimize-form tag nil) . ,(byte-optimize-body exps for-effect)))) @@ -566,7 +596,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (setcdr (cdr lexvar) (and (byte-optimize--substitutable-p value) (list value)))) - (setcar (cdr lexvar) t)) ; Mark variable to be kept. + (setcar (cdr lexvar) t)) ; Mark variable to be kept. (push var var-expr-list) (push value var-expr-list)) (setq args (cddr args))) -- cgit v1.2.3 From f3ae26cb2ae581a84bbaa15a47e9917a799a5682 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Wed, 10 Feb 2021 14:26:49 +0100 Subject: Fix local defvar scoping error (bug#46387) This bug was introduced by the lexical variable constant propagation mechanism. It was discovered by Michael Heerdegen. * lisp/emacs-lisp/byte-opt.el (byte-optimize-let-form) (byte-optimize-body): Let the effects of a local defvar declaration be scoped by let and let*, not any arbitrary Lisp expression body (such as progn). * test/lisp/emacs-lisp/bytecomp-tests.el (bytecomp-tests--get-vars) (bytecomp-local-defvar): New test. --- lisp/emacs-lisp/byte-opt.el | 4 ++-- test/lisp/emacs-lisp/bytecomp-tests.el | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 4fa2c75a889..8851f0ef32d 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -698,7 +698,8 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (append new-lexvars byte-optimize--lexvars)) ;; Walk the body expressions, which may mutate some of the records, ;; and generate new bindings that exclude unused variables. - (let* ((opt-body (byte-optimize-body (cdr form) for-effect)) + (let* ((byte-optimize--dynamic-vars byte-optimize--dynamic-vars) + (opt-body (byte-optimize-body (cdr form) for-effect)) (bindings nil)) (dolist (var let-vars) ;; VAR is (NAME EXPR [KEEP [VALUE]]) @@ -730,7 +731,6 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") ;; all-for-effect is true. returns a new list of forms. (let ((rest forms) (result nil) - (byte-optimize--dynamic-vars byte-optimize--dynamic-vars) fe new) (while rest (setq fe (or all-for-effect (cdr rest))) diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el b/test/lisp/emacs-lisp/bytecomp-tests.el index bc623d3efca..0b70c11b298 100644 --- a/test/lisp/emacs-lisp/bytecomp-tests.el +++ b/test/lisp/emacs-lisp/bytecomp-tests.el @@ -1168,6 +1168,37 @@ mountpoint (Bug#44631)." (with-demoted-errors "Error cleaning up directory: %s" (delete-directory directory :recursive))))) +(defun bytecomp-tests--get-vars () + (list (ignore-errors (symbol-value 'bytecomp-tests--var1)) + (ignore-errors (symbol-value 'bytecomp-tests--var2)))) + +(ert-deftest bytecomp-local-defvar () + "Check that local `defvar' declarations work correctly, both +interpreted and compiled." + (let ((lexical-binding t)) + (let ((fun '(lambda () + (defvar bytecomp-tests--var1) + (let ((bytecomp-tests--var1 'a) ; dynamic + (bytecomp-tests--var2 'b)) ; still lexical + (ignore bytecomp-tests--var2) ; avoid warning + (bytecomp-tests--get-vars))))) + (should (listp fun)) ; Guard against overzealous refactoring! + (should (equal (funcall (eval fun t)) '(a nil))) + (should (equal (funcall (byte-compile fun)) '(a nil))) + ) + + ;; `progn' does not constitute a lexical scope for `defvar' (bug#46387). + (let ((fun '(lambda () + (progn + (defvar bytecomp-tests--var1) + (defvar bytecomp-tests--var2)) + (let ((bytecomp-tests--var1 'c) + (bytecomp-tests--var2 'd)) + (bytecomp-tests--get-vars))))) + (should (listp fun)) + (should (equal (funcall (eval fun t)) '(c d))) + (should (equal (funcall (byte-compile fun)) '(c d)))))) + ;; Local Variables: ;; no-byte-compile: t ;; End: -- cgit v1.2.3 From ea29908c1870417eba98f27525a6f2f571d65396 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Thu, 11 Feb 2021 17:34:17 +0100 Subject: Avoid traversing dead `if` branches in bytecode optimiser There is no point in traversing conditional branches that are statically known never to be executed. This saves some optimisation effort, but more importantly prevents variable assignments and references in those branches from blocking effective constant propagation. Also attempt to traverse as much as possible in an unconditional context, which enables constant-propagation through (linear) assignments. * lisp/emacs-lisp/byte-opt.el (byte-optimize-form): Rewrite the (tail) recursion into an explicit loop. Normalise a return value of (quote nil) to nil, for easier subsequent optimisations. * lisp/emacs-lisp/byte-opt.el (byte-optimize-form-code-walker): Don't traverse dead `if` branches. Use unconditional traversion context when possible. --- lisp/emacs-lisp/byte-opt.el | 64 ++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 32 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 8851f0ef32d..fec3407782e 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -458,16 +458,22 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (cons fn (byte-optimize-body exps for-effect))) (`(if ,test ,then . ,else) + ;; FIXME: We are conservative here: any variable changed in the + ;; THEN branch will be barred from substitution in the ELSE + ;; branch, despite the branches being mutually exclusive. + ;; The test is always executed. (let* ((test-opt (byte-optimize-form test nil)) - ;; The THEN and ELSE branches are executed conditionally. - ;; - ;; FIXME: We are conservative here: any variable changed in the - ;; THEN branch will be barred from substitution in the ELSE - ;; branch, despite the branches being mutually exclusive. - (byte-optimize--vars-outside-condition byte-optimize--lexvars) - (then-opt (byte-optimize-form then for-effect)) - (else-opt (byte-optimize-body else for-effect))) + (const (macroexp-const-p test-opt)) + ;; The branches are traversed unconditionally when possible. + (byte-optimize--vars-outside-condition + (if const + byte-optimize--vars-outside-condition + byte-optimize--lexvars)) + ;; Avoid traversing dead branches. + (then-opt (and test-opt (byte-optimize-form then for-effect))) + (else-opt (and (not (and test-opt const)) + (byte-optimize-body else for-effect)))) `(if ,test-opt ,then-opt . ,else-opt))) (`(,(or 'and 'or) . ,exps) ; Remember, and/or are control structures. @@ -638,30 +644,24 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (defun byte-optimize-form (form &optional for-effect) "The source-level pass of the optimizer." - ;; - ;; First, optimize all sub-forms of this one. - (setq form (byte-optimize-form-code-walker form for-effect)) - ;; - ;; after optimizing all subforms, optimize this form until it doesn't - ;; optimize any further. This means that some forms will be passed through - ;; the optimizer many times, but that's necessary to make the for-effect - ;; processing do as much as possible. - ;; - (let (opt new) - (if (and (consp form) - (symbolp (car form)) - (or ;; (and for-effect - ;; ;; We don't have any of these yet, but we might. - ;; (setq opt (get (car form) - ;; 'byte-for-effect-optimizer))) - (setq opt (function-get (car form) 'byte-optimizer))) - (not (eq form (setq new (funcall opt form))))) - (progn -;; (if (equal form new) (error "bogus optimizer -- %s" opt)) - (byte-compile-log " %s\t==>\t%s" form new) - (setq new (byte-optimize-form new for-effect)) - new) - form))) + (while + (progn + ;; First, optimize all sub-forms of this one. + (setq form (byte-optimize-form-code-walker form for-effect)) + + ;; If a form-specific optimiser is available, run it and start over + ;; until a fixpoint has been reached. + (and (consp form) + (symbolp (car form)) + (let ((opt (function-get (car form) 'byte-optimizer))) + (and opt + (let ((old form) + (new (funcall opt form))) + (byte-compile-log " %s\t==>\t%s" old new) + (setq form new) + (not (eq new old)))))))) + ;; Normalise (quote nil) to nil, for a single representation of constant nil. + (and (not (equal form '(quote nil))) form)) (defun byte-optimize-let-form (head form for-effect) ;; Recursively enter the optimizer for the bindings and body -- cgit v1.2.3 From 5a11e9185c0416df8fa3a15bb0d60b6ba6827869 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Fri, 12 Feb 2021 19:41:07 +0100 Subject: byte-opt.el: More concise expression * lisp/emacs-lisp/byte-opt.el (byte-optimize-form-code-walker): Refactor `setq` clause. --- lisp/emacs-lisp/byte-opt.el | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index fec3407782e..c383e0285b9 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -593,16 +593,15 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (lexvar (assq var byte-optimize--lexvars)) (value (byte-optimize-form expr nil))) (when lexvar - ;; If it's bound outside conditional, invalidate. - (if (assq var byte-optimize--vars-outside-condition) - ;; We are in conditional code and the variable was - ;; bound outside: cancel substitutions. - (setcdr (cdr lexvar) nil) - ;; Set a new value (if substitutable). - (setcdr (cdr lexvar) - (and (byte-optimize--substitutable-p value) - (list value)))) - (setcar (cdr lexvar) t)) ; Mark variable to be kept. + ;; Set a new value or inhibit further substitution. + (setcdr (cdr lexvar) + (and + ;; Inhibit if bound outside conditional code. + (not (assq var byte-optimize--vars-outside-condition)) + ;; The new value must be substitutable. + (byte-optimize--substitutable-p value) + (list value))) + (setcar (cdr lexvar) t)) ; Mark variable to be kept. (push var var-expr-list) (push value var-expr-list)) (setq args (cddr args))) -- cgit v1.2.3 From 9518926220943d5c405e03d7352343341e07ba83 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Fri, 12 Feb 2021 19:43:41 +0100 Subject: Simplify expression in byte-code decompiler * lisp/emacs-lisp/byte-opt.el (byte-decompile-bytecode-1): Replace roundabout expression with what it essentially does. --- lisp/emacs-lisp/byte-opt.el | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index c383e0285b9..e0feb95a461 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -1562,10 +1562,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") ;; so we create a copy of it, and replace the addresses with ;; TAGs. (let ((orig-table last-constant)) - (cl-loop for e across constvec - when (eq e last-constant) - do (setq last-constant (copy-hash-table e)) - and return nil) + (setq last-constant (copy-hash-table last-constant)) ;; Replace all addresses with TAGs. (maphash #'(lambda (value offset) (let ((match (assq offset tags))) -- cgit v1.2.3 From 825aed11d267f7879ca8915eb2b0d154e0beb2d4 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Sat, 20 Feb 2021 13:44:19 +0100 Subject: Add the `always' function * doc/lispref/functions.texi (Calling Functions): Document it. * lisp/subr.el (always): New function. * lisp/emacs-lisp/byte-opt.el (side-effect-free-fns): Mark it as side effect free. --- doc/lispref/functions.texi | 4 ++++ etc/NEWS | 4 ++++ lisp/emacs-lisp/byte-opt.el | 2 +- lisp/subr.el | 9 ++++++++- 4 files changed, 17 insertions(+), 2 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi index 1e3da8e3a5d..2a9b57f19f3 100644 --- a/doc/lispref/functions.texi +++ b/doc/lispref/functions.texi @@ -861,6 +861,10 @@ This function returns @var{argument} and has no side effects. @defun ignore &rest arguments This function ignores any @var{arguments} and returns @code{nil}. +@end defun + +@defun always &rest arguments +This function ignores any @var{arguments} and returns @code{t}. @end defun Some functions are user-visible @dfn{commands}, which can be called diff --git a/etc/NEWS b/etc/NEWS index ee8a68a259d..c0c292aebc8 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2305,6 +2305,10 @@ back in Emacs 23.1. The affected functions are: 'make-obsolete', * Lisp Changes in Emacs 28.1 ++++ +** New function 'always'. +This is identical to 'ignore', but returns t instead. + +++ ** New forms to declare how completion should happen has been added. '(declare (completion PREDICATE))' can be used as a general predicate diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index e0feb95a461..9f0ba232a4b 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -1348,7 +1348,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") window-total-height window-total-width window-use-time window-vscroll window-width zerop)) (side-effect-and-error-free-fns - '(arrayp atom + '(always arrayp atom bignump bobp bolp bool-vector-p buffer-end buffer-list buffer-size buffer-string bufferp car-safe case-table-p cdr-safe char-or-string-p characterp diff --git a/lisp/subr.el b/lisp/subr.el index 490aec93f19..f9bb1bb3ad1 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -373,10 +373,17 @@ PREFIX is a string, and defaults to \"g\"." (defun ignore (&rest _arguments) "Do nothing and return nil. -This function accepts any number of ARGUMENTS, but ignores them." +This function accepts any number of ARGUMENTS, but ignores them. +Also see `always'." (interactive) nil) +(defun always (&rest _arguments) + "Do nothing and return t. +This function accepts any number of ARGUMENTS, but ignores them. +Also see `ignore'." + t) + ;; Signal a compile-error if the first arg is missing. (defun error (&rest args) "Signal an error, making a message by passing ARGS to `format-message'. -- cgit v1.2.3 From f8ab343eb93741209953e0d314b7c133bee91dda Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Sun, 28 Feb 2021 19:02:15 +0100 Subject: Declare more string predicates as pure * lisp/emacs-lisp/byte-opt.el (pure-fns): Treat string>, string-greaterp, string-empty-p, string-blank-p, string-prefix-p and string-suffix-p as pure functions in the compiler. --- lisp/emacs-lisp/byte-opt.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 9f0ba232a4b..b51ba801552 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -1413,7 +1413,8 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") copysign isnan ldexp float logb floor ceiling round truncate ffloor fceiling fround ftruncate - string= string-equal string< string-lessp + string= string-equal string< string-lessp string> string-greaterp + string-empty-p string-blank-p string-prefix-p string-suffix-p string-search consp atom listp nlistp proper-list-p sequencep arrayp vectorp stringp bool-vector-p hash-table-p -- cgit v1.2.3 From 77ec25122c9a7855657de564101c9fe763b91013 Mon Sep 17 00:00:00 2001 From: Pip Cet Date: Thu, 4 Mar 2021 21:06:13 +0000 Subject: Don't ignore lexically-bound variables in a defvar (bug#46912) * lisp/emacs-lisp/byte-opt.el (byte-optimize-form-code-walker): Walk the value form of a defvar. --- lisp/emacs-lisp/byte-opt.el | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index b51ba801552..b3325816c5c 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -607,9 +607,12 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (setq args (cddr args))) (cons fn (nreverse var-expr-list)))) - (`(defvar ,(and (pred symbolp) name) . ,_) - (push name byte-optimize--dynamic-vars) - form) + (`(defvar ,(and (pred symbolp) name) . ,rest) + (let ((optimized-rest (and rest + (cons (byte-optimize-form (car rest) nil) + (cdr rest))))) + (push name byte-optimize--dynamic-vars) + `(defvar ,name . ,optimized-rest))) (`(,(pred byte-code-function-p) . ,exps) (cons fn (mapcar #'byte-optimize-form exps))) -- cgit v1.2.3 From 7add3309035394340b9d75d12c7e5412a3c96690 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Wed, 10 Mar 2021 14:08:41 +0100 Subject: Mark string predicates side-effect-free * lisp/emacs-lisp/byte-opt.el (side-effect-free-fns): Add string>, string-greaterp, string-empty-p, string-prefix-p, string-suffix-p and string-blank-p, all recently marked pure. --- lisp/emacs-lisp/byte-opt.el | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index b3325816c5c..db8d825cfec 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -1327,6 +1327,8 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") radians-to-degrees rassq rassoc read-from-string regexp-opt regexp-quote region-beginning region-end reverse round sin sqrt string string< string= string-equal string-lessp + string> string-greaterp string-empty-p + string-prefix-p string-suffix-p string-blank-p string-search string-to-char string-to-number string-to-syntax substring sxhash sxhash-equal sxhash-eq sxhash-eql -- cgit v1.2.3 From 6810635bdd109d3df5b6b946e8c9eb11035b579c Mon Sep 17 00:00:00 2001 From: Eli Zaretskii Date: Mon, 15 Mar 2021 19:24:20 +0200 Subject: * lisp/emacs-lisp/byte-opt.el: Fix native re-compilation (bug#47161). --- lisp/emacs-lisp/byte-opt.el | 1 + 1 file changed, 1 insertion(+) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index db8d825cfec..436f5e48ae1 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -2350,6 +2350,7 @@ If FOR-EFFECT is non-nil, the return value is assumed to be of no importance." ;; (eval-when-compile (or (byte-code-function-p (symbol-function 'byte-optimize-form)) + (subr-native-elisp-p (symbol-function 'byte-optimize-form)) (assq 'byte-code (symbol-function 'byte-optimize-form)) (let ((byte-optimize nil) (byte-compile-warnings nil)) -- cgit v1.2.3 From 59342f689eaa4839b0fc15351ae48b4f1074a6fc Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Fri, 9 Apr 2021 18:59:09 +0200 Subject: Fix condition-case optimiser bug * lisp/emacs-lisp/byte-opt.el (byte-optimize-form-code-walker): Don't perform incorrect optimisations when a condition-case variable shadows another lexical variable. * test/lisp/emacs-lisp/bytecomp-tests.el (bytecomp-tests--test-cases): New test case. --- lisp/emacs-lisp/byte-opt.el | 10 ++++++++-- test/lisp/emacs-lisp/bytecomp-tests.el | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index db8d825cfec..e5265375314 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -528,8 +528,14 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") `(condition-case ,var ;Not evaluated. ,(byte-optimize-form exp for-effect) ,@(mapcar (lambda (clause) - `(,(car clause) - ,@(byte-optimize-body (cdr clause) for-effect))) + (let ((byte-optimize--lexvars + (and lexical-binding + (if var + (cons (list var t) + byte-optimize--lexvars) + byte-optimize--lexvars)))) + (cons (car clause) + (byte-optimize-body (cdr clause) for-effect)))) clauses)))) (`(unwind-protect ,exp . ,exps) diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el b/test/lisp/emacs-lisp/bytecomp-tests.el index 1953878d6f5..94e33a7770e 100644 --- a/test/lisp/emacs-lisp/bytecomp-tests.el +++ b/test/lisp/emacs-lisp/bytecomp-tests.el @@ -431,6 +431,12 @@ (let ((x 2)) (list (or (bytecomp-test-identity 'a) (setq x 3)) x)) + + (let* ((x 1) + (y (condition-case x + (/ 1 0) + (arith-error x)))) + (list x y)) ) "List of expressions for cross-testing interpreted and compiled code.") -- cgit v1.2.3 From a7cc19e5ff0425289616135ab6b4674baf02c12b Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Wed, 21 Apr 2021 17:27:14 +0200 Subject: Don't erroneously declare `mark` as error-free * lisp/emacs-lisp/byte-opt.el (side-effect-free-fns) (side-effect-and-error-free-fns): `mark` is side-effect-free but not error-free. --- lisp/emacs-lisp/byte-opt.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index e5265375314..43e9395967a 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -1324,7 +1324,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") line-beginning-position line-end-position local-variable-if-set-p local-variable-p locale-info log log10 logand logb logcount logior lognot logxor lsh - make-byte-code make-list make-string make-symbol marker-buffer max + make-byte-code make-list make-string make-symbol mark marker-buffer max member memq memql min minibuffer-selected-window minibuffer-window mod multibyte-char-to-unibyte next-window nth nthcdr number-to-string parse-colon-path plist-get plist-member @@ -1374,7 +1374,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") invocation-directory invocation-name keymapp keywordp list listp - make-marker mark mark-marker markerp max-char + make-marker mark-marker markerp max-char memory-limit mouse-movement-p natnump nlistp not null number-or-marker-p numberp -- cgit v1.2.3 From 01bd4d1a824816fba34571623a65c9c1541c27e5 Mon Sep 17 00:00:00 2001 From: Philipp Stephani Date: Thu, 6 May 2021 19:13:00 +0200 Subject: Optimize calls to 'eql', 'memql' and similar for fixnums. It's good practice to compare integers using 'eql' because two bignum objects representing the same integer might not be 'eq'. However, 'eql' is slower and doesn't have its own byte code. Therefore, replace it with 'eq' if one argument is guaranteed to be a fixnum on all platforms. * lisp/emacs-lisp/byte-opt.el (byte-optimize--fixnump): New helper function. (byte-optimize-equal, byte-optimize-member, byte-optimize-assoc): Use it to optimize 'eql' etc. to 'eq' if it will always compare fixnums. --- lisp/emacs-lisp/byte-opt.el | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 33b4d4b3c87..28b53d05890 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -951,12 +951,20 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") "Whether EXPR is a constant symbol." (and (macroexp-const-p expr) (symbolp (eval expr)))) +(defun byte-optimize--fixnump (o) + "Return whether O is guaranteed to be a fixnum in all Emacsen. +See Info node `(elisp) Integer Basics'." + (and (fixnump o) (<= -536870912 o 536870911))) + (defun byte-optimize-equal (form) - ;; Replace `equal' or `eql' with `eq' if at least one arg is a symbol. + ;; Replace `equal' or `eql' with `eq' if at least one arg is a + ;; symbol or fixnum. (byte-optimize-binary-predicate (if (= (length (cdr form)) 2) (if (or (byte-optimize--constant-symbol-p (nth 1 form)) - (byte-optimize--constant-symbol-p (nth 2 form))) + (byte-optimize--constant-symbol-p (nth 2 form)) + (byte-optimize--fixnump (nth 1 form)) + (byte-optimize--fixnump (nth 2 form))) (cons 'eq (cdr form)) form) ;; Arity errors reported elsewhere. @@ -964,14 +972,19 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (defun byte-optimize-member (form) ;; Replace `member' or `memql' with `memq' if the first arg is a symbol, - ;; or the second arg is a list of symbols. + ;; or the second arg is a list of symbols. Same with fixnums. (if (= (length (cdr form)) 2) (if (or (byte-optimize--constant-symbol-p (nth 1 form)) + (byte-optimize--fixnump (nth 1 form)) (let ((arg2 (nth 2 form))) (and (macroexp-const-p arg2) (let ((listval (eval arg2))) (and (listp listval) - (not (memq nil (mapcar #'symbolp listval)))))))) + (not (memq nil (mapcar + (lambda (o) + (or (symbolp o) + (byte-optimize--fixnump o))) + listval)))))))) (cons 'memq (cdr form)) form) ;; Arity errors reported elsewhere. @@ -979,11 +992,12 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (defun byte-optimize-assoc (form) ;; Replace 2-argument `assoc' with `assq', `rassoc' with `rassq', - ;; if the first arg is a symbol. + ;; if the first arg is a symbol or fixnum. (cond ((/= (length form) 3) form) - ((byte-optimize--constant-symbol-p (nth 1 form)) + ((or (byte-optimize--constant-symbol-p (nth 1 form)) + (byte-optimize--fixnump (nth 1 form))) (cons (if (eq (car form) 'assoc) 'assq 'rassq) (cdr form))) (t (byte-optimize-constant-args form)))) -- cgit v1.2.3 From 354ecaf12b2de9836ebb0e8fd812f453a39e837c Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Tue, 25 May 2021 13:38:05 -0400 Subject: * lisp/emacs-lisp/byte-opt.el: Make the build more reproducible (byte-compile-inline-expand): When inlining code from another file, always inline the byte-code version of the function. (byte-optimize--pcase): Simplify edebug spec. --- lisp/emacs-lisp/byte-opt.el | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 28b53d05890..99b5319ab3d 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -268,32 +268,18 @@ ;; The byte-code will be really inlined in byte-compile-unfold-bcf. `(,fn ,@(cdr form))) ((or `(lambda . ,_) `(closure . ,_)) - (if (not (or (eq fn localfn) ;From the same file => same mode. - (eq (car fn) ;Same mode. - (if lexical-binding 'closure 'lambda)))) - ;; While byte-compile-unfold-bcf can inline dynbind byte-code into - ;; letbind byte-code (or any other combination for that matter), we - ;; can only inline dynbind source into dynbind source or letbind - ;; source into letbind source. - (progn - ;; We can of course byte-compile the inlined function - ;; first, and then inline its byte-code. - (byte-compile name) - `(,(symbol-function name) ,@(cdr form))) - (let ((newfn (if (eq fn localfn) - ;; If `fn' is from the same file, it has already - ;; been preprocessed! - `(function ,fn) - ;; Try and process it "in its original environment". - (let ((byte-compile-bound-variables nil)) - (byte-compile-preprocess - (byte-compile--reify-function fn)))))) - (if (eq (car-safe newfn) 'function) - (macroexp--unfold-lambda `(,(cadr newfn) ,@(cdr form))) - ;; This can happen because of macroexp-warn-and-return &co. - (byte-compile-warn - "Inlining closure %S failed" name) - form)))) + (if (eq fn localfn) ;From the same file => same mode. + (macroexp--unfold-lambda `(,fn ,@(cdr form))) + ;; While byte-compile-unfold-bcf can inline dynbind byte-code into + ;; letbind byte-code (or any other combination for that matter), we + ;; can only inline dynbind source into dynbind source or letbind + ;; source into letbind source. + ;; We can of course byte-compile the inlined function + ;; first, and then inline its byte-code. This also has the advantage + ;; that the final code does not depend on the order of compilation + ;; of ELisp files, making the build more reproducible. + (byte-compile name) + `(,(symbol-function name) ,@(cdr form)))) (_ ;; Give up on inlining. form)))) @@ -369,7 +355,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") ;; to `(if . (or `(,exp ,then ,else) pcase--dontcare))'. ;; ;; The resulting macroexpansion is also significantly cleaner/smaller/faster. - (declare (indent 1) (debug (form &rest (pcase-PAT body)))) + (declare (indent 1) (debug pcase)) `(pcase ,exp . ,(mapcar (lambda (case) `(,(pcase (car case) -- cgit v1.2.3 From 40d2970f4360bd942ffc3f86db9ff1499a5a5393 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Thu, 27 May 2021 14:03:14 +0200 Subject: Don't propagate lexical variables into inlined functions Functions compiled when inlined (thus from inside the optimiser) mustn't retain the lexical environment of the caller or there will be tears. See discussion at https://lists.gnu.org/archive/html/emacs-devel/2021-05/msg01227.html . Bug found by Stefan Monnier. * lisp/emacs-lisp/byte-opt.el (byte-compile-inline-expand): Bind byte-optimize--lexvars to nil when re-entering the compiler recursively. * test/lisp/emacs-lisp/bytecomp-resources/bc-test-alpha.el: * test/lisp/emacs-lisp/bytecomp-resources/bc-test-beta.el: New files. * test/lisp/emacs-lisp/bytecomp-tests.el (bytecomp-defsubst): New test. --- lisp/emacs-lisp/byte-opt.el | 5 ++++- .../emacs-lisp/bytecomp-resources/bc-test-alpha.el | 9 +++++++++ .../lisp/emacs-lisp/bytecomp-resources/bc-test-beta.el | 6 ++++++ test/lisp/emacs-lisp/bytecomp-tests.el | 18 ++++++++++++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 test/lisp/emacs-lisp/bytecomp-resources/bc-test-alpha.el create mode 100644 test/lisp/emacs-lisp/bytecomp-resources/bc-test-beta.el (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 99b5319ab3d..842697c7245 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -278,7 +278,10 @@ ;; first, and then inline its byte-code. This also has the advantage ;; that the final code does not depend on the order of compilation ;; of ELisp files, making the build more reproducible. - (byte-compile name) + ;; Since we are called from inside the optimiser, we need to make + ;; sure not to propagate lexvar values. + (dlet ((byte-optimize--lexvars nil)) + (byte-compile name)) `(,(symbol-function name) ,@(cdr form)))) (_ ;; Give up on inlining. diff --git a/test/lisp/emacs-lisp/bytecomp-resources/bc-test-alpha.el b/test/lisp/emacs-lisp/bytecomp-resources/bc-test-alpha.el new file mode 100644 index 00000000000..6997d91b26a --- /dev/null +++ b/test/lisp/emacs-lisp/bytecomp-resources/bc-test-alpha.el @@ -0,0 +1,9 @@ +;;; -*- lexical-binding: t -*- + +(require 'bc-test-beta) + +(defun bc-test-alpha-f (x) + (let ((y nil)) + (list y (bc-test-beta-f x)))) + +(provide 'bc-test-alpha) diff --git a/test/lisp/emacs-lisp/bytecomp-resources/bc-test-beta.el b/test/lisp/emacs-lisp/bytecomp-resources/bc-test-beta.el new file mode 100644 index 00000000000..9205a13d7d5 --- /dev/null +++ b/test/lisp/emacs-lisp/bytecomp-resources/bc-test-beta.el @@ -0,0 +1,6 @@ +;;; -*- lexical-binding: t -*- + +(defsubst bc-test-beta-f (y) + y) + +(provide 'bc-test-beta) diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el b/test/lisp/emacs-lisp/bytecomp-tests.el index c9ab3ec1f1b..33413f5a002 100644 --- a/test/lisp/emacs-lisp/bytecomp-tests.el +++ b/test/lisp/emacs-lisp/bytecomp-tests.el @@ -1312,6 +1312,24 @@ compiled correctly." (funcall f 3)) 4))) +(declare-function bc-test-alpha-f (ert-resource-file "bc-test-alpha.el")) + +(ert-deftest bytecomp-defsubst () + ;; Check that lexical variables don't leak into inlined code. See + ;; https://lists.gnu.org/archive/html/emacs-devel/2021-05/msg01227.html + + ;; First, remove any trace of the functions and package defined: + (fmakunbound 'bc-test-alpha-f) + (fmakunbound 'bc-test-beta-f) + (setq features (delq 'bc-test-beta features)) + ;; Byte-compile one file that uses a function from another file that isn't + ;; compiled. + (let ((file (ert-resource-file "bc-test-alpha.el")) + (load-path (cons (ert-resource-directory) load-path))) + (byte-compile-file file) + (load-file (concat file "c")) + (should (equal (bc-test-alpha-f 'a) '(nil a))))) + ;; Local Variables: ;; no-byte-compile: t ;; End: -- cgit v1.2.3 From 24c96577465db02ddbe5df1be160c5fbe60b66fd Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Thu, 27 May 2021 17:31:57 -0400 Subject: * lisp/emacs-lisp/byte-opt.el (byte-compile-inline-expand): Silence warnings (byte-optimize--lexvars): Move before first use instead of using `dlet` on that first use. --- lisp/emacs-lisp/byte-opt.el | 46 +++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 842697c7245..10a50da4628 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -225,6 +225,14 @@ (byte-compile-log-lap-1 ,format-string ,@args))) +(defvar byte-optimize--lexvars nil + "Lexical variables in scope, in reverse order of declaration. +Each element is on the form (NAME KEEP [VALUE]), where: + NAME is the variable name, + KEEP is a boolean indicating whether the binding must be retained, + VALUE, if present, is a substitutable expression. +Earlier variables shadow later ones with the same name.") + ;;; byte-compile optimizers to support inlining (put 'inline 'byte-optimizer #'byte-optimize-inline-handler) @@ -268,19 +276,29 @@ ;; The byte-code will be really inlined in byte-compile-unfold-bcf. `(,fn ,@(cdr form))) ((or `(lambda . ,_) `(closure . ,_)) - (if (eq fn localfn) ;From the same file => same mode. + ;; While byte-compile-unfold-bcf can inline dynbind byte-code into + ;; letbind byte-code (or any other combination for that matter), we + ;; can only inline dynbind source into dynbind source or letbind + ;; source into letbind source. + ;; When the function comes from another file, we byte-compile + ;; the inlined function first, and then inline its byte-code. + ;; This also has the advantage that the final code does not + ;; depend on the order of compilation of ELisp files, making + ;; the build more reproducible. + (if (eq fn localfn) + ;; From the same file => same mode. (macroexp--unfold-lambda `(,fn ,@(cdr form))) - ;; While byte-compile-unfold-bcf can inline dynbind byte-code into - ;; letbind byte-code (or any other combination for that matter), we - ;; can only inline dynbind source into dynbind source or letbind - ;; source into letbind source. - ;; We can of course byte-compile the inlined function - ;; first, and then inline its byte-code. This also has the advantage - ;; that the final code does not depend on the order of compilation - ;; of ELisp files, making the build more reproducible. ;; Since we are called from inside the optimiser, we need to make ;; sure not to propagate lexvar values. - (dlet ((byte-optimize--lexvars nil)) + (let ((byte-optimize--lexvars nil) + ;; Silence all compilation warnings: the useful ones should + ;; be displayed when the function's source file will be + ;; compiled anyway, but more importantly we would otherwise + ;; emit spurious warnings here because we don't have the full + ;; context, such as `declare-functions' placed earlier in the + ;; source file's code or `with-suppressed-warnings' that + ;; surrounded the `defsubst'. + (byte-compile-warnings nil)) (byte-compile name)) `(,(symbol-function name) ,@(cdr form)))) @@ -297,14 +315,6 @@ This does usually not indicate a problem and makes the compiler very chatty, but can be useful for debugging.") -(defvar byte-optimize--lexvars nil - "Lexical variables in scope, in reverse order of declaration. -Each element is on the form (NAME KEEP [VALUE]), where: - NAME is the variable name, - KEEP is a boolean indicating whether the binding must be retained, - VALUE, if present, is a substitutable expression. -Earlier variables shadow later ones with the same name.") - (defvar byte-optimize--vars-outside-condition nil "Alist of variables lexically bound outside conditionally executed code. Variables here are sensitive to mutation inside the conditional code, -- cgit v1.2.3 From a517b77ffe8ed8cdfeec1a9b5258fd16b2446214 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Thu, 3 Jun 2021 21:15:11 +0200 Subject: Optimise (cons X nil) to (list X) * lisp/emacs-lisp/byte-opt.el (byte-optimize-cons): New function. --- lisp/emacs-lisp/byte-opt.el | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 10a50da4628..99e84e23ad8 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -1269,6 +1269,14 @@ See Info node `(elisp) Integer Basics'." form) form)) +(put 'cons 'byte-optimizer #'byte-optimize-cons) +(defun byte-optimize-cons (form) + ;; (cons X nil) => (list X) + (if (and (= (safe-length form) 3) + (null (nth 2 form))) + `(list ,(nth 1 form)) + form)) + ;; Fixme: delete-char -> delete-region (byte-coded) ;; optimize string-as-unibyte, string-as-multibyte, string-make-unibyte, ;; string-make-multibyte for constant args. -- cgit v1.2.3 From 6b41d7da9543786647218fe89224809cc51d25d3 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Thu, 3 Jun 2021 21:20:57 +0200 Subject: Constant-propagate (function SYMBOL) * lisp/emacs-lisp/byte-opt.el (byte-optimize--substitutable-p): Consider #'SYMBOL a constant for compile-time propagation purposes. --- lisp/emacs-lisp/byte-opt.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 99e84e23ad8..2fff0bd4a5f 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -343,7 +343,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (numberp expr) (stringp expr) (and (consp expr) - (eq (car expr) 'quote) + (memq (car expr) '(quote function)) (symbolp (cadr expr))) (keywordp expr))) -- cgit v1.2.3 From 46d7d44894843bf30e9bc84473195c6ab892b752 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Tue, 20 Jul 2021 15:46:32 +0200 Subject: Strength-reduce (eq X nil) to (not X) * lisp/emacs-lisp/byte-opt.el (byte-optimize-eq): New optimisation, which results in better test and branch code generation where it applies. --- lisp/emacs-lisp/byte-opt.el | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 2fff0bd4a5f..7ed04b32e97 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -969,6 +969,12 @@ See Info node `(elisp) Integer Basics'." ;; Arity errors reported elsewhere. form))) +(defun byte-optimize-eq (form) + (byte-optimize-binary-predicate + (pcase (cdr form) + ((or `(,x nil) `(nil ,x)) `(not ,x)) + (_ form)))) + (defun byte-optimize-member (form) ;; Replace `member' or `memql' with `memq' if the first arg is a symbol, ;; or the second arg is a list of symbols. Same with fixnums. @@ -1056,7 +1062,7 @@ See Info node `(elisp) Integer Basics'." (put 'min 'byte-optimizer #'byte-optimize-min-max) (put '= 'byte-optimizer #'byte-optimize-binary-predicate) -(put 'eq 'byte-optimizer #'byte-optimize-binary-predicate) +(put 'eq 'byte-optimizer #'byte-optimize-eq) (put 'eql 'byte-optimizer #'byte-optimize-equal) (put 'equal 'byte-optimizer #'byte-optimize-equal) (put 'string= 'byte-optimizer #'byte-optimize-binary-predicate) -- cgit v1.2.3 From f9d74408148e50eb287d6bf784cddbdc239d9669 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Tue, 20 Jul 2021 19:32:11 +0200 Subject: ; * lisp/emacs-lisp/byte-opt.el (byte-optimize-eq): Fix last change. --- lisp/emacs-lisp/byte-opt.el | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 7ed04b32e97..c9c0ac0045e 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -970,10 +970,9 @@ See Info node `(elisp) Integer Basics'." form))) (defun byte-optimize-eq (form) - (byte-optimize-binary-predicate - (pcase (cdr form) - ((or `(,x nil) `(nil ,x)) `(not ,x)) - (_ form)))) + (pcase (cdr form) + ((or `(,x nil) `(nil ,x)) `(not ,x)) + (_ (byte-optimize-binary-predicate form)))) (defun byte-optimize-member (form) ;; Replace `member' or `memql' with `memq' if the first arg is a symbol, -- cgit v1.2.3 From 070c80ee06664c90fb9c96a1b9c89f7b844ae712 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Wed, 21 Jul 2021 10:54:43 +0200 Subject: Fix mistake in `quote` optimiser Found by Pip Cet. * lisp/emacs-lisp/byte-opt.el (byte-optimize-quote): Fix mistake that made this optimiser ineffective at removing quoting of nil, t, and keywords. The only obvious consequence is that we no longer need... (byte-optimize-form): ...a 'nil => nil normalising step here; remove. (byte-optimize-form-code-walker): Make the compiler warn about (quote). --- lisp/emacs-lisp/byte-opt.el | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index c9c0ac0045e..341643c7d16 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -414,7 +414,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") form))) (t form))) (`(quote . ,v) - (if (cdr v) + (if (or (not v) (cdr v)) (byte-compile-warn "malformed quote form: `%s'" (prin1-to-string form))) ;; Map (quote nil) to nil to simplify optimizer logic. @@ -667,8 +667,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (byte-compile-log " %s\t==>\t%s" old new) (setq form new) (not (eq new old)))))))) - ;; Normalise (quote nil) to nil, for a single representation of constant nil. - (and (not (equal form '(quote nil))) form)) + form) (defun byte-optimize-let-form (head form for-effect) ;; Recursively enter the optimizer for the bindings and body @@ -1077,7 +1076,7 @@ See Info node `(elisp) Integer Basics'." (defun byte-optimize-quote (form) (if (or (consp (nth 1 form)) (and (symbolp (nth 1 form)) - (not (macroexp--const-symbol-p form)))) + (not (macroexp--const-symbol-p (nth 1 form))))) form (nth 1 form))) -- cgit v1.2.3 From 109ca1bd00b56ba66b123b505d8c2187fded0ef7 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Thu, 22 Jul 2021 15:00:17 +0200 Subject: Warn about arity errors in inlining calls (bug#12299) Wrong number of arguments in inlining function calls (to `defsubst` or explicitly using `inline`) did not result in warnings, or in very cryptic ones. * lisp/emacs-lisp/byte-opt.el (byte-compile-inline-expand): Add calls to `byte-compile--check-arity-bytecode`. * lisp/emacs-lisp/bytecomp.el (byte-compile-emit-callargs-warn) (byte-compile--check-arity-bytecode): New functions. (byte-compile-callargs-warn): Use factored-out function. * test/lisp/emacs-lisp/bytecomp-resources/warn-callargs-defsubst.el: * test/lisp/emacs-lisp/bytecomp-tests.el ("warn-callargs-defsubst.el"): New test case. --- lisp/emacs-lisp/byte-opt.el | 5 ++- lisp/emacs-lisp/bytecomp.el | 37 ++++++++++++++++------ .../bytecomp-resources/warn-callargs-defsubst.el | 5 +++ test/lisp/emacs-lisp/bytecomp-tests.el | 3 ++ 4 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 test/lisp/emacs-lisp/bytecomp-resources/warn-callargs-defsubst.el (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 341643c7d16..ad9f827171a 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -274,6 +274,7 @@ Earlier variables shadow later ones with the same name.") ((pred byte-code-function-p) ;; (message "Inlining byte-code for %S!" name) ;; The byte-code will be really inlined in byte-compile-unfold-bcf. + (byte-compile--check-arity-bytecode form fn) `(,fn ,@(cdr form))) ((or `(lambda . ,_) `(closure . ,_)) ;; While byte-compile-unfold-bcf can inline dynbind byte-code into @@ -300,7 +301,9 @@ Earlier variables shadow later ones with the same name.") ;; surrounded the `defsubst'. (byte-compile-warnings nil)) (byte-compile name)) - `(,(symbol-function name) ,@(cdr form)))) + (let ((bc (symbol-function name))) + (byte-compile--check-arity-bytecode form bc) + `(,bc ,@(cdr form))))) (_ ;; Give up on inlining. form)))) diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index 2968f1af5df..f6150069e81 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el @@ -1477,6 +1477,30 @@ when printing the error message." (push (list f byte-compile-last-position nargs) byte-compile-unresolved-functions))))) +(defun byte-compile-emit-callargs-warn (name actual-args min-args max-args) + (byte-compile-set-symbol-position name) + (byte-compile-warn + "%s called with %d argument%s, but %s %s" + name actual-args + (if (= 1 actual-args) "" "s") + (if (< actual-args min-args) + "requires" + "accepts only") + (byte-compile-arglist-signature-string (cons min-args max-args)))) + +(defun byte-compile--check-arity-bytecode (form bytecode) + "Check that the call in FORM matches that allowed by BYTECODE." + (when (and (byte-code-function-p bytecode) + (byte-compile-warning-enabled-p 'callargs)) + (let* ((actual-args (length (cdr form))) + (arity (func-arity bytecode)) + (min-args (car arity)) + (max-args (and (numberp (cdr arity)) (cdr arity)))) + (when (or (< actual-args min-args) + (and max-args (> actual-args max-args))) + (byte-compile-emit-callargs-warn + (car form) actual-args min-args max-args))))) + ;; Warn if the form is calling a function with the wrong number of arguments. (defun byte-compile-callargs-warn (form) (let* ((def (or (byte-compile-fdefinition (car form) nil) @@ -1491,16 +1515,9 @@ when printing the error message." (setcdr sig nil)) (if sig (when (or (< ncall (car sig)) - (and (cdr sig) (> ncall (cdr sig)))) - (byte-compile-set-symbol-position (car form)) - (byte-compile-warn - "%s called with %d argument%s, but %s %s" - (car form) ncall - (if (= 1 ncall) "" "s") - (if (< ncall (car sig)) - "requires" - "accepts only") - (byte-compile-arglist-signature-string sig)))) + (and (cdr sig) (> ncall (cdr sig)))) + (byte-compile-emit-callargs-warn + (car form) ncall (car sig) (cdr sig)))) (byte-compile-format-warn form) (byte-compile-function-warn (car form) (length (cdr form)) def))) diff --git a/test/lisp/emacs-lisp/bytecomp-resources/warn-callargs-defsubst.el b/test/lisp/emacs-lisp/bytecomp-resources/warn-callargs-defsubst.el new file mode 100644 index 00000000000..3a29128cf3a --- /dev/null +++ b/test/lisp/emacs-lisp/bytecomp-resources/warn-callargs-defsubst.el @@ -0,0 +1,5 @@ +;;; -*- lexical-binding: t -*- +(defsubst warn-callargs-defsubst-f1 (_x) + nil) +(defun warn-callargs-defsubst-f2 () + (warn-callargs-defsubst-f1 1 2)) diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el b/test/lisp/emacs-lisp/bytecomp-tests.el index 33413f5a002..7c40f7ebca3 100644 --- a/test/lisp/emacs-lisp/bytecomp-tests.el +++ b/test/lisp/emacs-lisp/bytecomp-tests.el @@ -700,6 +700,9 @@ byte-compiled. Run with dynamic binding." (bytecomp--define-warning-file-test "warn-callargs.el" "with 2 arguments, but accepts only 1") +(bytecomp--define-warning-file-test "warn-callargs-defsubst.el" + "with 2 arguments, but accepts only 1") + (bytecomp--define-warning-file-test "warn-defcustom-nogroup.el" "fails to specify containing group") -- cgit v1.2.3 From 566e29f78ccee4fcf0421576c0306860c8afae0f Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Wed, 28 Jul 2021 21:12:27 +0200 Subject: Single source optimiser entry point Make the optimiser aware of lexical arguments. Otherwise we cannot know for sure whether a variable is lexical or dynamic during traversal. * lisp/emacs-lisp/byte-opt.el (byte-optimize-one-form): New optimiser entry point, replacing the recursive byte-optimize-form. * lisp/emacs-lisp/bytecomp.el (byte-optimize-one-form): Autoload. (byte-compile-keep-pending, byte-compile-top-level): Use byte-optimize-one-form. --- lisp/emacs-lisp/byte-opt.el | 9 ++++++++- lisp/emacs-lisp/bytecomp.el | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index ad9f827171a..4117533cda5 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -652,8 +652,15 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (byte-optimize-constant-args form) form)))))) -(defun byte-optimize-form (form &optional for-effect) +(defun byte-optimize-one-form (form &optional for-effect) "The source-level pass of the optimizer." + ;; Make optimiser aware of lexical arguments. + (let ((byte-optimize--lexvars + (mapcar (lambda (v) (list (car v) t)) + byte-compile--lexical-environment))) + (byte-optimize-form form for-effect))) + +(defun byte-optimize-form (form &optional for-effect) (while (progn ;; First, optimize all sub-forms of this one. diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index a6e7e03cb0a..7bd642d2b23 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el @@ -192,7 +192,7 @@ otherwise adds \".elc\"." (autoload 'byte-compile-inline-expand "byte-opt") ;; This is the entry point to the lapcode optimizer pass1. -(autoload 'byte-optimize-form "byte-opt") +(autoload 'byte-optimize-one-form "byte-opt") ;; This is the entry point to the lapcode optimizer pass2. (autoload 'byte-optimize-lapcode "byte-opt") @@ -2455,7 +2455,7 @@ list that represents a doc string reference. (defun byte-compile-keep-pending (form &optional handler) (if (memq byte-optimize '(t source)) - (setq form (byte-optimize-form form t))) + (setq form (byte-optimize-one-form form t))) (if handler (let ((byte-compile--for-effect t)) ;; To avoid consing up monstrously large forms at load time, we split @@ -3155,7 +3155,7 @@ for symbols generated by the byte compiler itself." (byte-compile-output nil) (byte-compile-jump-tables nil)) (if (memq byte-optimize '(t source)) - (setq form (byte-optimize-form form byte-compile--for-effect))) + (setq form (byte-optimize-one-form form byte-compile--for-effect))) (while (and (eq (car-safe form) 'progn) (null (cdr (cdr form)))) (setq form (nth 1 form))) ;; Set up things for a lexically-bound function. -- cgit v1.2.3 From 9a6333811441a32e49bfd33c14f77680402cd639 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Wed, 28 Jul 2021 17:31:44 +0200 Subject: Elide lexical variables in for-effect context in source optimiser * lisp/emacs-lisp/byte-opt.el (byte-optimize-form-code-walker): Remove for-effect uses of lexical variables. We previously relied on this being done by the lapcode peephole optimiser but at source level it enables more optimisation opportunities. Keywords are elided for the same reason. --- lisp/emacs-lisp/byte-opt.el | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 4117533cda5..58a08eb3cdb 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -402,19 +402,24 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") ((and for-effect (or byte-compile-delete-errors (not (symbolp form)) - (eq form t))) + (eq form t) + (keywordp form))) nil) ((symbolp form) (let ((lexvar (assq form byte-optimize--lexvars))) - (if (cddr lexvar) ; Value available? - (if (assq form byte-optimize--vars-outside-loop) - ;; Cannot substitute; mark for retention to avoid the - ;; variable being eliminated. - (progn - (setcar (cdr lexvar) t) - form) - (caddr lexvar)) ; variable value to use - form))) + (cond + ((not lexvar) form) + (for-effect nil) + ((cddr lexvar) ; Value available? + (if (assq form byte-optimize--vars-outside-loop) + ;; Cannot substitute; mark for retention to avoid the + ;; variable being eliminated. + (progn + (setcar (cdr lexvar) t) + form) + ;; variable value to use + (caddr lexvar))) + (t form)))) (t form))) (`(quote . ,v) (if (or (not v) (cdr v)) -- cgit v1.2.3 From dc9e2a1749c892cdf52a01414bee97e9a2245ca5 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Thu, 29 Jul 2021 10:07:26 +0200 Subject: Optimise prog1 better Rewrite (prog1 CONST FORMS...) => (progn FORMS... CONST) where CONST is a compile-time constant, because putting the value last allows the lapcode peephole pass to do important improvements like branch elimination. Also use progn instead of prog1 for `ignore`. * lisp/emacs-lisp/byte-opt.el (byte-optimize-form-code-walker): New `prog1` and `ignore` transforms. --- lisp/emacs-lisp/byte-opt.el | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 58a08eb3cdb..b6052d82061 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -452,10 +452,13 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (macroexp-progn (byte-optimize-body exps for-effect)) (byte-optimize-form (car exps) for-effect))) (`(prog1 ,exp . ,exps) - (if exps - `(prog1 ,(byte-optimize-form exp for-effect) - . ,(byte-optimize-body exps t)) - (byte-optimize-form exp for-effect))) + (let ((exp-opt (byte-optimize-form exp for-effect))) + (if exps + (let ((exps-opt (byte-optimize-body exps t))) + (if (macroexp-const-p exp-opt) + `(progn ,@exps-opt ,exp-opt) + `(prog1 ,exp-opt ,@exps-opt))) + exp-opt))) (`(,(or `save-excursion `save-restriction `save-current-buffer) . ,exps) ;; Those subrs which have an implicit progn; it's not quite good @@ -572,7 +575,7 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") ;; computed for effect. We want to avoid the warnings ;; that might occur if they were treated that way. ;; However, don't actually bother calling `ignore'. - `(prog1 nil . ,(mapcar #'byte-optimize-form exps))) + `(progn ,@(mapcar #'byte-optimize-form exps) nil)) ;; Needed as long as we run byte-optimize-form after cconv. (`(internal-make-closure . ,_) -- cgit v1.2.3 From ab9c06449df4c4c58d586573003de419199cc1be Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Thu, 29 Jul 2021 17:20:41 +0200 Subject: Move warnings about bad let-bindings from source optimiser to cconv * lisp/emacs-lisp/byte-opt.el (byte-optimize-let-form): Move warnings... * lisp/emacs-lisp/cconv.el (cconv-convert): ...here, which is an overall better place (closer to the front-end). --- lisp/emacs-lisp/byte-opt.el | 15 +++++---------- lisp/emacs-lisp/cconv.el | 7 +++++++ 2 files changed, 12 insertions(+), 10 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index b6052d82061..c9dfa69aeb2 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -697,16 +697,11 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (let-vars nil)) (dolist (binding (car form)) (let (name expr) - (cond ((consp binding) - (setq name (car binding)) - (unless (symbolp name) - (byte-compile-warn "let-bind nonvariable: `%S'" name)) - (setq expr (byte-optimize-form (cadr binding) nil))) - ((symbolp binding) - (setq name binding)) - (t (byte-compile-warn "malformed let binding: `%S'" binding))) - (let* ( - (value (and (byte-optimize--substitutable-p expr) + (if (atom binding) + (setq name binding) + (setq name (car binding)) + (setq expr (byte-optimize-form (cadr binding) nil))) + (let* ((value (and (byte-optimize--substitutable-p expr) (list expr))) (lexical (not (or (and (symbolp name) (special-variable-p name)) diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el index ea0b09805ea..e0795975c9b 100644 --- a/lisp/emacs-lisp/cconv.el +++ b/lisp/emacs-lisp/cconv.el @@ -358,6 +358,13 @@ places where they originally did not directly appear." letsym binder)) (setq value (cadr binder)) (car binder))) + (_ (cond + ((not (symbolp var)) + (byte-compile-warn "attempt to let-bind nonvariable `%S'" + var)) + ((or (booleanp var) (keywordp var)) + (byte-compile-warn "attempt to let-bind constant `%S'" + var)))) (new-val (pcase (cconv--var-classification binder form) ;; Check if var is a candidate for lambda lifting. -- cgit v1.2.3 From 52a55e11deb7822c67a8d7e6f2544b8f41d25a4e Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Thu, 29 Jul 2021 15:35:55 +0200 Subject: Optimise let and let* whose body is constant or the last variable Simplify (let ((X1 E1) ... (Xn En)) Xn) => (progn E1 ... En) and (let* ((X1 E1) ... (Xn En)) Xn) => (let* ((X1 E1) ... (Xn-1 En-1)) En) and similarly the case where the body is a constant, extending a previous optimisation that only applied to the constant nil. This reduces the number of bound variables, shortens the code, and enables further optimisations. * lisp/emacs-lisp/byte-opt.el (byte-optimize-letX): Rewrite using `pcase` and add the aforementioned transformations. * test/lisp/emacs-lisp/bytecomp-tests.el (bytecomp-tests--test-cases): Add test cases. --- lisp/emacs-lisp/byte-opt.el | 37 +++++++++++++++++++++++----------- test/lisp/emacs-lisp/bytecomp-tests.el | 18 +++++++++++++++++ 2 files changed, 43 insertions(+), 12 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index c9dfa69aeb2..d444d7006eb 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -1250,18 +1250,31 @@ See Info node `(elisp) Integer Basics'." (put 'let 'byte-optimizer #'byte-optimize-letX) (put 'let* 'byte-optimizer #'byte-optimize-letX) (defun byte-optimize-letX (form) - (cond ((null (nth 1 form)) - ;; No bindings - (cons 'progn (cdr (cdr form)))) - ((or (nth 2 form) (nthcdr 3 form)) - form) - ;; The body is nil - ((eq (car form) 'let) - (append '(progn) (mapcar 'car-safe (mapcar 'cdr-safe (nth 1 form))) - '(nil))) - (t - (let ((binds (reverse (nth 1 form)))) - (list 'let* (reverse (cdr binds)) (nth 1 (car binds)) nil))))) + (pcase form + ;; No bindings. + (`(,_ () . ,body) + `(progn . ,body)) + + ;; Body is empty or just contains a constant. + (`(,head ,bindings . ,(or '() `(,(and const (pred macroexp-const-p))))) + (if (eq head 'let) + `(progn ,@(mapcar (lambda (binding) + (and (consp binding) (cadr binding))) + bindings) + ,const) + `(let* ,(butlast bindings) ,(cadar (last bindings)) ,const))) + + ;; Body is last variable. + (`(,head ,bindings ,(and var (pred symbolp) (pred (not keywordp)) + (pred (not booleanp)) + (guard (eq var (caar (last bindings)))))) + (if (eq head 'let) + `(progn ,@(mapcar (lambda (binding) + (and (consp binding) (cadr binding))) + bindings)) + `(let* ,(butlast bindings) ,(cadar (last bindings))))) + + (_ form))) (put 'nth 'byte-optimizer #'byte-optimize-nth) diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el b/test/lisp/emacs-lisp/bytecomp-tests.el index ee0f931c192..5aa853c7212 100644 --- a/test/lisp/emacs-lisp/bytecomp-tests.el +++ b/test/lisp/emacs-lisp/bytecomp-tests.el @@ -509,6 +509,24 @@ ((member x '("b" "c")) 2) ((not x) 3))) '("a" "b" "c" "d" nil)) + + ;; `let' and `let*' optimisations with body being constant or variable + (let* (a + (b (progn (setq a (cons 1 a)) 2)) + (c (1+ b)) + (d (list a c))) + d) + (let ((a nil)) + (let ((b (progn (setq a (cons 1 a)) 2)) + (c (progn (setq a (cons 3 a)))) + (d (list a))) + d)) + (let* ((_a 1) + (_b 2)) + 'z) + (let ((_a 1) + (_b 2)) + 'z) ) "List of expressions for cross-testing interpreted and compiled code.") -- cgit v1.2.3 From f472dd8ea5d94f0231bb7bf23552895c3ab19689 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Fri, 30 Jul 2021 12:22:01 +0200 Subject: Simplify lexical let-optimisations Ensure in cconv that let-bindings have the normal form (VAR EXPR) where VAR is a valid variable name, so that we don't need to keep re-checking this all the time in the optimiser. * lisp/emacs-lisp/byte-opt.el (byte-optimize-enable-variable-constprop) (byte-optimize-warn-eliminated-variable): Remove; these were mainly used for debugging. * lisp/emacs-lisp/byte-opt.el (byte-optimize-let-form): Assume normalised let-bindings (with lexical-binding). Stop using the variables removed above. * lisp/emacs-lisp/cconv.el (cconv-convert): Ensure normalised let-bindings. Malformed bindings are dropped after warning. remove byte-optimize-warn-eliminated-variable --- lisp/emacs-lisp/byte-opt.el | 46 +++++-------- lisp/emacs-lisp/cconv.el | 163 ++++++++++++++++++++++---------------------- 2 files changed, 99 insertions(+), 110 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index d444d7006eb..142f206428e 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -310,14 +310,6 @@ Earlier variables shadow later ones with the same name.") ;;; implementing source-level optimizers -(defconst byte-optimize-enable-variable-constprop t - "If non-nil, enable constant propagation through local variables.") - -(defconst byte-optimize-warn-eliminated-variable nil - "Whether to warn when a variable is optimised away entirely. -This does usually not indicate a problem and makes the compiler -very chatty, but can be useful for debugging.") - (defvar byte-optimize--vars-outside-condition nil "Alist of variables lexically bound outside conditionally executed code. Variables here are sensitive to mutation inside the conditional code, @@ -691,28 +683,24 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") ;; Recursively enter the optimizer for the bindings and body ;; of a let or let*. This for depth-firstness: forms that ;; are more deeply nested are optimized first. - (if (and lexical-binding byte-optimize-enable-variable-constprop) + (if lexical-binding (let* ((byte-optimize--lexvars byte-optimize--lexvars) (new-lexvars nil) (let-vars nil)) (dolist (binding (car form)) - (let (name expr) - (if (atom binding) - (setq name binding) - (setq name (car binding)) - (setq expr (byte-optimize-form (cadr binding) nil))) - (let* ((value (and (byte-optimize--substitutable-p expr) - (list expr))) - (lexical (not (or (and (symbolp name) - (special-variable-p name)) - (memq name byte-compile-bound-variables) - (memq name byte-optimize--dynamic-vars)))) - (lexinfo (and lexical (cons name (cons nil value))))) - (push (cons name (cons expr (cdr lexinfo))) let-vars) - (when lexinfo - (push lexinfo (if (eq head 'let*) - byte-optimize--lexvars - new-lexvars)))))) + (let* ((name (car binding)) + (expr (byte-optimize-form (cadr binding) nil)) + (value (and (byte-optimize--substitutable-p expr) + (list expr))) + (lexical (not (or (special-variable-p name) + (memq name byte-compile-bound-variables) + (memq name byte-optimize--dynamic-vars)))) + (lexinfo (and lexical (cons name (cons nil value))))) + (push (cons name (cons expr (cdr lexinfo))) let-vars) + (when lexinfo + (push lexinfo (if (eq head 'let*) + byte-optimize--lexvars + new-lexvars))))) (setq byte-optimize--lexvars (append new-lexvars byte-optimize--lexvars)) ;; Walk the body expressions, which may mutate some of the records, @@ -722,10 +710,8 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (bindings nil)) (dolist (var let-vars) ;; VAR is (NAME EXPR [KEEP [VALUE]]) - (if (and (nthcdr 3 var) (not (nth 2 var))) - ;; Value present and not marked to be kept: eliminate. - (when byte-optimize-warn-eliminated-variable - (byte-compile-warn "eliminating local variable %S" (car var))) + (when (or (not (nthcdr 3 var)) (nth 2 var)) + ;; Value not present, or variable marked to be kept. (push (list (nth 0 var) (nth 1 var)) bindings))) (cons bindings opt-body))) diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el index e0795975c9b..3abbf716875 100644 --- a/lisp/emacs-lisp/cconv.el +++ b/lisp/emacs-lisp/cconv.el @@ -357,88 +357,91 @@ places where they originally did not directly appear." "Malformed `%S' binding: %S" letsym binder)) (setq value (cadr binder)) - (car binder))) - (_ (cond - ((not (symbolp var)) - (byte-compile-warn "attempt to let-bind nonvariable `%S'" - var)) - ((or (booleanp var) (keywordp var)) - (byte-compile-warn "attempt to let-bind constant `%S'" - var)))) - (new-val - (pcase (cconv--var-classification binder form) - ;; Check if var is a candidate for lambda lifting. - ((and :lambda-candidate - (guard - (progn - (cl-assert (and (eq (car value) 'function) - (eq (car (cadr value)) 'lambda))) - (cl-assert (equal (cddr (cadr value)) - (caar cconv-freevars-alist))) - ;; Peek at the freevars to decide whether to λ-lift. - (let* ((fvs (cdr (car cconv-freevars-alist))) - (fun (cadr value)) - (funargs (cadr fun)) - (funcvars (append fvs funargs))) + (car binder)))) + (cond + ;; Ignore bindings without a valid name. + ((not (symbolp var)) + (byte-compile-warn "attempt to let-bind nonvariable `%S'" var)) + ((or (booleanp var) (keywordp var)) + (byte-compile-warn "attempt to let-bind constant `%S'" var)) + (t + (let ((new-val + (pcase (cconv--var-classification binder form) + ;; Check if var is a candidate for lambda lifting. + ((and :lambda-candidate + (guard + (progn + (cl-assert (and (eq (car value) 'function) + (eq (car (cadr value)) 'lambda))) + (cl-assert (equal (cddr (cadr value)) + (caar cconv-freevars-alist))) + ;; Peek at the freevars to decide whether + ;; to λ-lift. + (let* ((fvs (cdr (car cconv-freevars-alist))) + (fun (cadr value)) + (funargs (cadr fun)) + (funcvars (append fvs funargs))) ; lambda lifting condition - (and fvs (>= cconv-liftwhen - (length funcvars))))))) + (and fvs (>= cconv-liftwhen + (length funcvars))))))) ; Lift. - (let* ((fvs (cdr (pop cconv-freevars-alist))) - (fun (cadr value)) - (funargs (cadr fun)) - (funcvars (append fvs funargs)) - (funcbody (cddr fun)) - (funcbody-env ())) - (push `(,var . (apply-partially ,var . ,fvs)) new-env) - (dolist (fv fvs) - (cl-pushnew fv new-extend) - (if (and (eq 'car-safe (car-safe (cdr (assq fv env)))) - (not (memq fv funargs))) - (push `(,fv . (car-safe ,fv)) funcbody-env))) - `(function (lambda ,funcvars . - ,(cconv--convert-funcbody - funargs funcbody funcbody-env value))))) - - ;; Check if it needs to be turned into a "ref-cell". - (:captured+mutated - ;; Declared variable is mutated and captured. - (push `(,var . (car-safe ,var)) new-env) - `(list ,(cconv-convert value env extend))) - - ;; Check if it needs to be turned into a "ref-cell". - (:unused - ;; Declared variable is unused. - (if (assq var new-env) (push `(,var) new-env)) ;FIXME:Needed? - (let ((newval - `(ignore ,(cconv-convert value env extend))) - (msg (cconv--warn-unused-msg var "variable"))) - (if (null msg) newval - (macroexp--warn-wrap msg newval 'lexical)))) - - ;; Normal default case. - (_ - (if (assq var new-env) (push `(,var) new-env)) - (cconv-convert value env extend))))) - - (when (and (eq letsym 'let*) (memq var new-extend)) - ;; One of the lambda-lifted vars is shadowed, so add - ;; a reference to the outside binding and arrange to use - ;; that reference. - (let ((closedsym (make-symbol (format "closed-%s" var)))) - (setq new-env (cconv--remap-llv new-env var closedsym)) - (setq new-extend (cons closedsym (remq var new-extend))) - (push `(,closedsym ,var) binders-new))) - - ;; We push the element after redefined free variables are - ;; processed. This is important to avoid the bug when free - ;; variable and the function have the same name. - (push (list var new-val) binders-new) - - (when (eq letsym 'let*) - (setq env new-env) - (setq extend new-extend)) - )) ; end of dolist over binders + (let* ((fvs (cdr (pop cconv-freevars-alist))) + (fun (cadr value)) + (funargs (cadr fun)) + (funcvars (append fvs funargs)) + (funcbody (cddr fun)) + (funcbody-env ())) + (push `(,var . (apply-partially ,var . ,fvs)) new-env) + (dolist (fv fvs) + (cl-pushnew fv new-extend) + (if (and (eq 'car-safe (car-safe + (cdr (assq fv env)))) + (not (memq fv funargs))) + (push `(,fv . (car-safe ,fv)) funcbody-env))) + `(function (lambda ,funcvars . + ,(cconv--convert-funcbody + funargs funcbody funcbody-env value))))) + + ;; Check if it needs to be turned into a "ref-cell". + (:captured+mutated + ;; Declared variable is mutated and captured. + (push `(,var . (car-safe ,var)) new-env) + `(list ,(cconv-convert value env extend))) + + ;; Check if it needs to be turned into a "ref-cell". + (:unused + ;; Declared variable is unused. + (if (assq var new-env) + (push `(,var) new-env)) ;FIXME:Needed? + (let ((newval + `(ignore ,(cconv-convert value env extend))) + (msg (cconv--warn-unused-msg var "variable"))) + (if (null msg) newval + (macroexp--warn-wrap msg newval 'lexical)))) + + ;; Normal default case. + (_ + (if (assq var new-env) (push `(,var) new-env)) + (cconv-convert value env extend))))) + + (when (and (eq letsym 'let*) (memq var new-extend)) + ;; One of the lambda-lifted vars is shadowed, so add + ;; a reference to the outside binding and arrange to use + ;; that reference. + (let ((closedsym (make-symbol (format "closed-%s" var)))) + (setq new-env (cconv--remap-llv new-env var closedsym)) + (setq new-extend (cons closedsym (remq var new-extend))) + (push `(,closedsym ,var) binders-new))) + + ;; We push the element after redefined free variables are + ;; processed. This is important to avoid the bug when free + ;; variable and the function have the same name. + (push (list var new-val) binders-new) + + (when (eq letsym 'let*) + (setq env new-env) + (setq extend new-extend)))))) + ) ; end of dolist over binders (when (not (eq letsym 'let*)) ;; We can't do the cconv--remap-llv at the same place for let and -- cgit v1.2.3 From 0809c9f6ef1e575a28b64c040834991588adf383 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Tue, 3 Aug 2021 15:28:10 +0200 Subject: Declare `match-beginning` and `match-end` as side-effect-free * lisp/emacs-lisp/byte-opt.el (side-effect-free-fns): Add functions. --- lisp/emacs-lisp/byte-opt.el | 1 + 1 file changed, 1 insertion(+) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 142f206428e..2232617e5ea 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -1362,6 +1362,7 @@ See Info node `(elisp) Integer Basics'." local-variable-if-set-p local-variable-p locale-info log log10 logand logb logcount logior lognot logxor lsh make-byte-code make-list make-string make-symbol mark marker-buffer max + match-beginning match-end member memq memql min minibuffer-selected-window minibuffer-window mod multibyte-char-to-unibyte next-window nth nthcdr number-to-string parse-colon-path plist-get plist-member -- cgit v1.2.3 From 301ce625cb4b04c6a1662605d0221099b1de6359 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Tue, 3 Aug 2021 19:08:43 +0200 Subject: Declare file-name-concat as side-effect free * lisp/emacs-lisp/byte-opt.el (side-effect-free-fns): Declare file-name-concat as side-effect (and error) free. --- lisp/emacs-lisp/byte-opt.el | 1 + 1 file changed, 1 insertion(+) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 2232617e5ea..ad0273dc153 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -1405,6 +1405,7 @@ See Info node `(elisp) Integer Basics'." current-buffer current-global-map current-indentation current-local-map current-minor-mode-maps current-time eobp eolp eq equal eventp + file-name-concat fixnump floatp following-char framep get-largest-window get-lru-window hash-table-p -- cgit v1.2.3 From 88577aed3a17109bb7b13871f185133641c25e73 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Tue, 3 Aug 2021 19:41:57 +0200 Subject: file-name-concat is not error free * lisp/emacs-lisp/byte-opt.el (side-effect-free-fns): Declare file-name-concat as side-effect free. --- lisp/emacs-lisp/byte-opt.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index ad0273dc153..96072ea1b55 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -1348,6 +1348,7 @@ See Info node `(elisp) Integer Basics'." elt encode-char exp expt encode-time error-message-string fboundp fceiling featurep ffloor file-directory-p file-exists-p file-locked-p file-name-absolute-p + file-name-concat file-newer-than-file-p file-readable-p file-symlink-p file-writable-p float float-time floor format format-time-string frame-first-window frame-root-window frame-selected-window @@ -1405,7 +1406,6 @@ See Info node `(elisp) Integer Basics'." current-buffer current-global-map current-indentation current-local-map current-minor-mode-maps current-time eobp eolp eq equal eventp - file-name-concat fixnump floatp following-char framep get-largest-window get-lru-window hash-table-p -- cgit v1.2.3 From 2a17925aab75735b40f9b0ccaab4c46f6b9a9a32 Mon Sep 17 00:00:00 2001 From: Mattias Engdegård Date: Thu, 5 Aug 2021 11:50:25 +0200 Subject: Cease attempts to const-propagate through setq The current method of propagating constants through setq was unsound because it relied on each setq form only being traversed at most once during optimisation, which isn't necessarily true in general; it could be made to miscompile code in rare cases. Since it was only used in limited circumstances, disabling this optimisation doesn't cost us much. * lisp/emacs-lisp/byte-opt.el (byte-optimize-form-code-walker): Don't update the known value when traversing `setq`. * test/lisp/emacs-lisp/bytecomp-tests.el (bytecomp-tests--test-cases): Add test case. --- lisp/emacs-lisp/byte-opt.el | 12 +++--------- test/lisp/emacs-lisp/bytecomp-tests.el | 9 +++++++++ 2 files changed, 12 insertions(+), 9 deletions(-) (limited to 'lisp/emacs-lisp/byte-opt.el') diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index 96072ea1b55..6475f69eded 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -601,15 +601,9 @@ Same format as `byte-optimize--lexvars', with shared structure and contents.") (lexvar (assq var byte-optimize--lexvars)) (value (byte-optimize-form expr nil))) (when lexvar - ;; Set a new value or inhibit further substitution. - (setcdr (cdr lexvar) - (and - ;; Inhibit if bound outside conditional code. - (not (assq var byte-optimize--vars-outside-condition)) - ;; The new value must be substitutable. - (byte-optimize--substitutable-p value) - (list value))) - (setcar (cdr lexvar) t)) ; Mark variable to be kept. + (setcar (cdr lexvar) t) ; Mark variable to be kept. + (setcdr (cdr lexvar) nil)) ; Inhibit further substitution. + (push var var-expr-list) (push value var-expr-list)) (setq args (cddr args))) diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el b/test/lisp/emacs-lisp/bytecomp-tests.el index 5aa853c7212..80003c264a2 100644 --- a/test/lisp/emacs-lisp/bytecomp-tests.el +++ b/test/lisp/emacs-lisp/bytecomp-tests.el @@ -432,6 +432,15 @@ (let ((x 2)) (list (or (bytecomp-test-identity 'a) (setq x 3)) x)) + (mapcar (lambda (b) + (let ((a nil)) + (+ 0 + (progn + (setq a b) + (setq b 1) + a)))) + '(10)) + (let* ((x 1) (y (condition-case x (/ 1 0) -- cgit v1.2.3