aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2022-10-28 11:33:24 -0400
committerStefan Monnier2022-10-28 11:33:24 -0400
commitd79cdcd4ff6687c2f0dcfde83ba36732408e52e8 (patch)
tree570e8832ca29ba5f8e6db49cd0b9b9acaf831011
parentde5a3fa1e529810f30d461d6682762c9c5e564a4 (diff)
downloademacs-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.el101
-rw-r--r--lisp/loadup.el7
-rw-r--r--src/eval.c21
-rw-r--r--test/lisp/emacs-lisp/cconv-tests.el17
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.
833ENV is the lexical environment from which the variables can be taken. 833LEXVARS is the list of statically scoped vars in the context
834It should be a list of pairs of the form (VAR . VAL). 834and DYNVARS is the list of dynamically scoped vars in the context.
835The return value is a list of those (VAR . VAL) bindings, 835Returns a pair (LEXV . DYNV) of those vars actually used by FORM."
836in the same order as they appear in ENV. 836 (let* ((fun
837If NO-MACROEXPAND is non-nil, we do not macro-expand FORM, 837 ;; Wrap FORM into a function because the analysis code we
838which means that the result may be incorrect if there are non-expanded 838 ;; have only computes freevars for functions.
839macro 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