diff options
| author | Stefan Monnier | 2022-10-28 11:33:24 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2022-10-28 11:33:24 -0400 |
| commit | d79cdcd4ff6687c2f0dcfde83ba36732408e52e8 (patch) | |
| tree | 570e8832ca29ba5f8e6db49cd0b9b9acaf831011 | |
| parent | de5a3fa1e529810f30d461d6682762c9c5e564a4 (diff) | |
| download | emacs-d79cdcd4ff6687c2f0dcfde83ba36732408e52e8.tar.gz emacs-d79cdcd4ff6687c2f0dcfde83ba36732408e52e8.zip | |
cconv.el: Fix regression in cconv-tests-interactive-closure-bug51695
The new code to make interpreted closures safe-for-space introduced
a regression in `cconv-tests-interactive-closure-bug51695`, only seen
when using TEST_LOAD_EL.
A few other issues were found and fixed along the way.
* lisp/emacs-lisp/cconv.el (cconv-fv): Change calling convention and
focus on finding the free variables.
(cconv-make-interpreted-closure): New function.
* lisp/loadup.el: Use `compiled-function-p` rather than
`byte-code-function-p` so we also use safe-for-space interpreted
closures when we build with native compilation.
(internal-make-interpreted-closure-function):
Use `cconv-make-interpreted-closure`.
* src/eval.c (syms_of_eval): Rename `internal-filter-closure-env-function`
to `internal-make-interpreted-closure-function`.
(Ffunction): Let that new var build the actual closure.
* test/lisp/emacs-lisp/cconv-tests.el
(cconv-tests-interactive-closure-bug51695): Test specifically the
interpreted case.
| -rw-r--r-- | lisp/emacs-lisp/cconv.el | 101 | ||||
| -rw-r--r-- | lisp/loadup.el | 7 | ||||
| -rw-r--r-- | src/eval.c | 21 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/cconv-tests.el | 17 |
4 files changed, 90 insertions, 56 deletions
diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el index 289e2b0eee4..f3431db4156 100644 --- a/lisp/emacs-lisp/cconv.el +++ b/lisp/emacs-lisp/cconv.el | |||
| @@ -828,49 +828,78 @@ This function does not return anything but instead fills the | |||
| 828 | (setf (nth 1 dv) t)))))) | 828 | (setf (nth 1 dv) t)))))) |
| 829 | (define-obsolete-function-alias 'cconv-analyse-form #'cconv-analyze-form "25.1") | 829 | (define-obsolete-function-alias 'cconv-analyse-form #'cconv-analyze-form "25.1") |
| 830 | 830 | ||
| 831 | (defun cconv-fv (form env &optional no-macroexpand) | 831 | (defun cconv-fv (form lexvars dynvars) |
| 832 | "Return the list of free variables in FORM. | 832 | "Return the list of free variables in FORM. |
| 833 | ENV is the lexical environment from which the variables can be taken. | 833 | LEXVARS is the list of statically scoped vars in the context |
| 834 | It should be a list of pairs of the form (VAR . VAL). | 834 | and DYNVARS is the list of dynamically scoped vars in the context. |
| 835 | The return value is a list of those (VAR . VAL) bindings, | 835 | Returns a pair (LEXV . DYNV) of those vars actually used by FORM." |
| 836 | in the same order as they appear in ENV. | 836 | (let* ((fun |
| 837 | If NO-MACROEXPAND is non-nil, we do not macro-expand FORM, | 837 | ;; Wrap FORM into a function because the analysis code we |
| 838 | which means that the result may be incorrect if there are non-expanded | 838 | ;; have only computes freevars for functions. |
| 839 | macro calls in FORM." | 839 | ;; In practice FORM is always already of the form |
| 840 | (let* ((fun `#'(lambda () ,form)) | 840 | ;; #'(lambda ...), so optimize for this case. |
| 841 | ;; Make dummy bindings to avoid warnings about the var being | 841 | (if (and (eq 'function (car-safe form)) |
| 842 | ;; left uninitialized. | 842 | (eq 'lambda (car-safe (cadr form))) |
| 843 | (analysis-env | 843 | ;; To get correct results, FUN needs to be a "simple lambda" |
| 844 | (delq nil (mapcar (lambda (b) (if (consp b) | 844 | ;; without nested forms that aren't part of the body. :-( |
| 845 | (list (car b) nil nil nil nil))) | 845 | (not (assq 'interactive (cadr form))) |
| 846 | env))) | 846 | (not (assq ':documentation (cadr form)))) |
| 847 | (cconv--dynbound-variables | 847 | form |
| 848 | (delq nil (mapcar (lambda (b) (if (symbolp b) b)) env))) | 848 | `#'(lambda () ,form))) |
| 849 | (analysis-env (mapcar (lambda (v) (list v nil nil nil nil)) lexvars)) | ||
| 850 | (cconv--dynbound-variables dynvars) | ||
| 849 | (byte-compile-lexical-variables nil) | 851 | (byte-compile-lexical-variables nil) |
| 850 | (cconv--dynbindings nil) | 852 | (cconv--dynbindings nil) |
| 851 | (cconv-freevars-alist '()) | 853 | (cconv-freevars-alist '()) |
| 852 | (cconv-var-classification '())) | 854 | (cconv-var-classification '())) |
| 853 | (if (null analysis-env) | 855 | (let* ((body (cddr (cadr fun)))) |
| 856 | ;; Analyze form - fill these variables with new information. | ||
| 857 | (cconv-analyze-form fun analysis-env) | ||
| 858 | (setq cconv-freevars-alist (nreverse cconv-freevars-alist)) | ||
| 859 | (unless (equal (if (eq :documentation (car-safe (car body))) | ||
| 860 | (cdr body) body) | ||
| 861 | (caar cconv-freevars-alist)) | ||
| 862 | (message "BOOH!\n%S\n%S" | ||
| 863 | body (caar cconv-freevars-alist))) | ||
| 864 | (cl-assert (equal (if (eq :documentation (car-safe (car body))) | ||
| 865 | (cdr body) body) | ||
| 866 | (caar cconv-freevars-alist))) | ||
| 867 | (let ((fvs (nreverse (cdar cconv-freevars-alist))) | ||
| 868 | (dyns (delq nil (mapcar (lambda (var) (car (memq var dynvars))) | ||
| 869 | (delete-dups cconv--dynbindings))))) | ||
| 870 | (cons fvs dyns))))) | ||
| 871 | |||
| 872 | (defun cconv-make-interpreted-closure (fun env) | ||
| 873 | (cl-assert (eq (car-safe fun) 'lambda)) | ||
| 874 | (let ((lexvars (delq nil (mapcar #'car-safe env)))) | ||
| 875 | (if (null lexvars) | ||
| 854 | ;; The lexical environment is empty, so there's no need to | 876 | ;; The lexical environment is empty, so there's no need to |
| 855 | ;; look for free variables. | 877 | ;; look for free variables. |
| 856 | env | 878 | `(closure ,env . ,(cdr fun)) |
| 857 | (let* ((fun (if no-macroexpand fun | 879 | ;; We could try and cache the result of the macroexpansion and |
| 858 | (macroexpand-all fun macroexpand-all-environment))) | 880 | ;; `cconv-fv' analysis. Not sure it's worth the trouble. |
| 859 | (body (cddr (cadr fun)))) | 881 | (let* ((form `#',fun) |
| 860 | ;; Analyze form - fill these variables with new information. | 882 | (expanded-form |
| 861 | (cconv-analyze-form fun analysis-env) | 883 | (let ((lexical-binding t) ;; Tell macros which dialect is in use. |
| 862 | (setq cconv-freevars-alist (nreverse cconv-freevars-alist)) | 884 | ;; Make the macro aware of any defvar declarations in scope. |
| 863 | (cl-assert (equal (if (eq :documentation (car-safe (car body))) | 885 | (macroexp--dynvars |
| 864 | (cdr body) body) | 886 | (if macroexp--dynvars |
| 865 | (caar cconv-freevars-alist))) | 887 | (append env macroexp--dynvars) env))) |
| 866 | (let ((fvs (nreverse (cdar cconv-freevars-alist))) | 888 | (macroexpand-all form macroexpand-all-environment))) |
| 867 | (dyns (mapcar (lambda (var) (car (memq var env))) | 889 | ;; Since we macroexpanded the body, we may as well use that. |
| 868 | (delete-dups cconv--dynbindings)))) | 890 | (expanded-fun-cdr |
| 869 | (or (nconc (mapcar (lambda (fv) (assq fv env)) fvs) | 891 | (pcase expanded-form |
| 870 | (delq nil dyns)) | 892 | (`#'(lambda . ,cdr) cdr) |
| 871 | ;; Never return nil, since nil means to use the dynbind | 893 | (_ (cdr fun)))) |
| 872 | ;; dialect of ELisp. | 894 | |
| 873 | '(t))))))) | 895 | (dynvars (delq nil (mapcar (lambda (b) (if (symbolp b) b)) env))) |
| 896 | (fvs (cconv-fv expanded-form lexvars dynvars)) | ||
| 897 | (newenv (nconc (mapcar (lambda (fv) (assq fv env)) (car fvs)) | ||
| 898 | (cdr fvs)))) | ||
| 899 | ;; Never return a nil env, since nil means to use the dynbind | ||
| 900 | ;; dialect of ELisp. | ||
| 901 | `(closure ,(or newenv '(t)) . ,expanded-fun-cdr))))) | ||
| 902 | |||
| 874 | 903 | ||
| 875 | (provide 'cconv) | 904 | (provide 'cconv) |
| 876 | ;;; cconv.el ends here | 905 | ;;; cconv.el ends here |
diff --git a/lisp/loadup.el b/lisp/loadup.el index 63806ae4565..2a9aff4c1fe 100644 --- a/lisp/loadup.el +++ b/lisp/loadup.el | |||
| @@ -367,9 +367,10 @@ | |||
| 367 | 367 | ||
| 368 | (load "emacs-lisp/eldoc") | 368 | (load "emacs-lisp/eldoc") |
| 369 | (load "emacs-lisp/cconv") | 369 | (load "emacs-lisp/cconv") |
| 370 | (when (and (byte-code-function-p (symbol-function 'cconv-fv)) | 370 | (when (and (compiled-function-p (symbol-function 'cconv-fv)) |
| 371 | (byte-code-function-p (symbol-function 'macroexpand-all))) | 371 | (compiled-function-p (symbol-function 'macroexpand-all))) |
| 372 | (setq internal-filter-closure-env-function #'cconv-fv)) | 372 | (setq internal-make-interpreted-closure-function |
| 373 | #'cconv-make-interpreted-closure)) | ||
| 373 | (load "cus-start") ;Late to reduce customize-rogue (needs loaddefs.el anyway) | 374 | (load "cus-start") ;Late to reduce customize-rogue (needs loaddefs.el anyway) |
| 374 | (if (not (eq system-type 'ms-dos)) | 375 | (if (not (eq system-type 'ms-dos)) |
| 375 | (load "tooltip")) | 376 | (load "tooltip")) |
diff --git a/src/eval.c b/src/eval.c index d2cab006d11..2928a45ac1e 100644 --- a/src/eval.c +++ b/src/eval.c | |||
| @@ -550,15 +550,12 @@ usage: (function ARG) */) | |||
| 550 | CHECK_STRING (docstring); | 550 | CHECK_STRING (docstring); |
| 551 | cdr = Fcons (XCAR (cdr), Fcons (docstring, XCDR (XCDR (cdr)))); | 551 | cdr = Fcons (XCAR (cdr), Fcons (docstring, XCDR (XCDR (cdr)))); |
| 552 | } | 552 | } |
| 553 | Lisp_Object env | 553 | if (NILP (Vinternal_make_interpreted_closure_function)) |
| 554 | = NILP (Vinternal_filter_closure_env_function) | 554 | return Fcons (Qclosure, Fcons (Vinternal_interpreter_environment, cdr)); |
| 555 | ? Vinternal_interpreter_environment | 555 | else |
| 556 | /* FIXME: This macroexpands the body, so we should use the resulting | 556 | return call2 (Vinternal_make_interpreted_closure_function, |
| 557 | macroexpanded code! */ | 557 | Fcons (Qlambda, cdr), |
| 558 | : call2 (Vinternal_filter_closure_env_function, | 558 | Vinternal_interpreter_environment); |
| 559 | Fcons (Qprogn, CONSP (cdr) ? XCDR (cdr) : cdr), | ||
| 560 | Vinternal_interpreter_environment); | ||
| 561 | return Fcons (Qclosure, Fcons (env, cdr)); | ||
| 562 | } | 559 | } |
| 563 | else | 560 | else |
| 564 | /* Simply quote the argument. */ | 561 | /* Simply quote the argument. */ |
| @@ -4361,10 +4358,10 @@ alist of active lexical bindings. */); | |||
| 4361 | (Just imagine if someone makes it buffer-local). */ | 4358 | (Just imagine if someone makes it buffer-local). */ |
| 4362 | Funintern (Qinternal_interpreter_environment, Qnil); | 4359 | Funintern (Qinternal_interpreter_environment, Qnil); |
| 4363 | 4360 | ||
| 4364 | DEFVAR_LISP ("internal-filter-closure-env-function", | 4361 | DEFVAR_LISP ("internal-make-interpreted-closure-function", |
| 4365 | Vinternal_filter_closure_env_function, | 4362 | Vinternal_make_interpreted_closure_function, |
| 4366 | doc: /* Function to filter the env when constructing a closure. */); | 4363 | doc: /* Function to filter the env when constructing a closure. */); |
| 4367 | Vinternal_filter_closure_env_function = Qnil; | 4364 | Vinternal_make_interpreted_closure_function = Qnil; |
| 4368 | 4365 | ||
| 4369 | Vrun_hooks = intern_c_string ("run-hooks"); | 4366 | Vrun_hooks = intern_c_string ("run-hooks"); |
| 4370 | staticpro (&Vrun_hooks); | 4367 | staticpro (&Vrun_hooks); |
diff --git a/test/lisp/emacs-lisp/cconv-tests.el b/test/lisp/emacs-lisp/cconv-tests.el index 37470f863f3..e666fe0a4c2 100644 --- a/test/lisp/emacs-lisp/cconv-tests.el +++ b/test/lisp/emacs-lisp/cconv-tests.el | |||
| @@ -351,11 +351,18 @@ | |||
| 351 | (let ((f (let ((d 51695)) | 351 | (let ((f (let ((d 51695)) |
| 352 | (lambda (data) | 352 | (lambda (data) |
| 353 | (interactive (progn (setq d (1+ d)) (list d))) | 353 | (interactive (progn (setq d (1+ d)) (list d))) |
| 354 | (list (called-interactively-p 'any) data))))) | 354 | (list (called-interactively-p 'any) data)))) |
| 355 | (should (equal (list (call-interactively f) | 355 | (f-interp |
| 356 | (funcall f 51695) | 356 | (eval '(let ((d 51695)) |
| 357 | (call-interactively f)) | 357 | (lambda (data) |
| 358 | '((t 51696) (nil 51695) (t 51697)))))) | 358 | (interactive (progn (setq d (1+ d)) (list d))) |
| 359 | (list (called-interactively-p 'any) data))) | ||
| 360 | t))) | ||
| 361 | (dolist (f (list f f-interp)) | ||
| 362 | (should (equal (list (call-interactively f) | ||
| 363 | (funcall f 51695) | ||
| 364 | (call-interactively f)) | ||
| 365 | '((t 51696) (nil 51695) (t 51697))))))) | ||
| 359 | 366 | ||
| 360 | (provide 'cconv-tests) | 367 | (provide 'cconv-tests) |
| 361 | ;;; cconv-tests.el ends here | 368 | ;;; cconv-tests.el ends here |