summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp/cconv.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/emacs-lisp/cconv.el')
-rw-r--r--lisp/emacs-lisp/cconv.el362
1 files changed, 167 insertions, 195 deletions
diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el
index 60bc906b60c..af42a2864c9 100644
--- a/lisp/emacs-lisp/cconv.el
+++ b/lisp/emacs-lisp/cconv.el
@@ -1,4 +1,4 @@
-;;; cconv.el --- Closure conversion for statically scoped Emacs lisp. -*- lexical-binding: t -*-
+;;; cconv.el --- Closure conversion for statically scoped Emacs lisp. -*- lexical-binding: nil -*-
;; Copyright (C) 2011 Free Software Foundation, Inc.
@@ -82,8 +82,19 @@ is less than this number.")
(defvar cconv-captured+mutated nil
"An intersection between cconv-mutated and cconv-captured lists.")
(defvar cconv-lambda-candidates nil
- "List of candidates for lambda lifting")
-
+ "List of candidates for lambda lifting.
+Each candidate has the form (VAR INCLOSURE BINDER PARENTFORM).")
+
+(defun cconv-not-lexical-var-p (var)
+ (or (not (symbolp var)) ; form is not a list
+ (special-variable-p var)
+ ;; byte-compile-bound-variables normally holds both the
+ ;; dynamic and lexical vars, but the bytecomp.el should
+ ;; only call us at the top-level so there shouldn't be
+ ;; any lexical vars in it here.
+ (memq var byte-compile-bound-variables)
+ (memq var '(nil t))
+ (keywordp var)))
(defun cconv-freevars (form &optional fvrs)
"Find all free variables of given form.
@@ -166,24 +177,17 @@ Returns a list of free variables."
(append fvrs fvrs-1)))
(`(,(and sym (or `defun `defconst `defvar)) . ,_)
- ;; we call cconv-freevars only for functions(lambdas)
+ ;; We call cconv-freevars only for functions(lambdas)
;; defun, defconst, defvar are not allowed to be inside
- ;; a function(lambda)
+ ;; a function (lambda).
+ ;; FIXME: should be a byte-compile-report-error!
(error "Invalid form: %s inside a function" sym))
- (`(,_ . ,body-forms) ; first element is a function or whatever
+ (`(,_ . ,body-forms) ; First element is (like) a function.
(dolist (exp body-forms)
(setq fvrs (cconv-freevars exp fvrs))) fvrs)
- (_ (if (or (not (symbolp form)) ; form is not a list
- (special-variable-p form)
- ;; byte-compile-bound-variables normally holds both the
- ;; dynamic and lexical vars, but the bytecomp.el should
- ;; only call us at the top-level so there shouldn't be
- ;; any lexical vars in it here.
- (memq form byte-compile-bound-variables)
- (memq form '(nil t))
- (keywordp form))
+ (_ (if (cconv-not-lexical-var-p form)
fvrs
(cons form fvrs)))))
@@ -200,12 +204,13 @@ Returns a list of free variables."
-- TOPLEVEL(optional) is a boolean variable, true if we are at the root of AST
Returns a form where all lambdas don't have any free variables."
+ (message "Entering cconv-closure-convert...")
(let ((cconv-mutated '())
(cconv-lambda-candidates '())
(cconv-captured '())
(cconv-captured+mutated '()))
;; Analyse form - fill these variables with new information
- (cconv-analyse-form form '() nil)
+ (cconv-analyse-form form '() 0)
;; Calculate an intersection of cconv-mutated and cconv-captured
(dolist (mvr cconv-mutated)
(when (memq mvr cconv-captured) ;
@@ -271,7 +276,7 @@ Returns a form where all lambdas don't have any free variables."
(dolist (elm varsvalues) ;begin of dolist over varsvalues
(let (var value elm-new iscandidate ismutated)
- (if (listp elm) ; (let (v1) ...) => (let ((v1 nil)) ...)
+ (if (consp elm) ; (let (v1) ...) => (let ((v1 nil)) ...)
(progn
(setq var (car elm))
(setq value (cadr elm)))
@@ -430,9 +435,7 @@ Returns a form where all lambdas don't have any free variables."
(letbinds '())
(fvrs-new)) ; list of (closed-var var)
(dolist (elm varsvalues)
- (if (listp elm)
- (setq var (car elm))
- (setq var elm))
+ (setq var (if (consp elm) (car elm) elm))
(let ((lmenvs-1 lmenvs)) ; just to avoid manipulating
(dolist (lmenv lmenvs-1) ; the counter inside the loop
@@ -490,7 +493,7 @@ Returns a form where all lambdas don't have any free variables."
(`(quote . ,_) form) ; quote form
(`(function . ((lambda ,vars . ,body-forms))) ; function form
- (let (fvrs-new) ; we remove vars from fvrs
+ (let (fvrs-new) ; we remove vars from fvrs
(dolist (elm fvrs) ;i use such a tricky way to avoid side effects
(when (not (memq elm vars))
(push elm fvrs-new)))
@@ -577,7 +580,7 @@ Returns a form where all lambdas don't have any free variables."
(`(,(and sym (or `defun `defmacro))
,func ,vars . ,body-forms)
(if defs-are-legal
- (let ((body-new '()) ; the whole body
+ (let ((body-new '()) ; the whole body
(body-forms-new '()) ; body w\o docstring and interactive
(letbind '()))
; find mutable arguments
@@ -592,12 +595,11 @@ Returns a form where all lambdas don't have any free variables."
(when ismutated
(push elm letbind)
(push elm emvrs))))
- ;transform body-forms
+ ;transform body-forms
(when (stringp (car body-forms)) ; treat docstring well
(push (car body-forms) body-new)
(setq body-forms (cdr body-forms)))
- (when (and (listp (car body-forms)) ; treat (interactive) well
- (eq (caar body-forms) 'interactive))
+ (when (eq (car-safe (car body-forms)) 'interactive)
(push
(cconv-closure-convert-rec
(car body-forms)
@@ -707,201 +709,171 @@ Returns a form where all lambdas don't have any free variables."
`(,func . ,body-forms-new)))
(_
- (if (memq form fvrs) ;form is a free variable
- (let* ((numero (position form envs))
- (var '()))
- (assert numero)
- (if (null (cdr envs))
- (setq var 'env)
+ (let ((free (memq form fvrs)))
+ (if free ;form is a free variable
+ (let* ((numero (- (length fvrs) (length free)))
+ (var '()))
+ (assert numero)
+ (if (null (cdr envs))
+ (setq var 'env)
;replace form =>
;(aref env #)
- (setq var `(aref env ,numero)))
- (if (memq form emvrs) ; form => (car (aref env #)) if mutable
- `(car ,var)
- var))
- (if (memq form emvrs) ; if form is a mutable variable
- `(car ,form) ; replace form => (car form)
- form)))))
-
-(defun cconv-analyse-form (form vars inclosure)
-
+ (setq var `(aref env ,numero)))
+ (if (memq form emvrs) ; form => (car (aref env #)) if mutable
+ `(car ,var)
+ var))
+ (if (memq form emvrs) ; if form is a mutable variable
+ `(car ,form) ; replace form => (car form)
+ form))))))
+
+(defun cconv-analyse-function (args body env parentform inclosure)
+ (dolist (arg args)
+ (cond
+ ((cconv-not-lexical-var-p arg)
+ (byte-compile-report-error
+ (format "Argument %S is not a lexical variable" arg)))
+ ((eq ?& (aref (symbol-name arg) 0)) nil) ;Ignore &rest, &optional, ...
+ (t (push (list arg inclosure parentform) env)))) ;Push vrs to vars.
+ (dolist (form body) ;Analyse body forms.
+ (cconv-analyse-form form env inclosure)))
+
+(defun cconv-analyse-form (form env inclosure)
"Find mutated variables and variables captured by closure. Analyse
lambdas if they are suitable for lambda lifting.
-- FORM is a piece of Elisp code after macroexpansion.
--- MLCVRS is a structure that contains captured and mutated variables.
- (first MLCVRS) is a list of mutated variables, (second MLCVRS) is a
-list of candidates for lambda lifting and (third MLCVRS) is a list of
-variables captured by closure. It should be (nil nil nil) initially.
--- VARS is a list of local variables visible in current environment
- (initially empty).
--- INCLOSURE is a boolean variable, true if we are in closure.
-Initially false"
+-- ENV is a list of variables visible in current lexical environment.
+ Each entry has the form (VAR INCLOSURE BINDER PARENTFORM)
+ for let-bound vars and (VAR INCLOSURE PARENTFORM) for function arguments.
+-- INCLOSURE is the nesting level within lambdas."
(pcase form
; let special form
- (`(,(and (or `let* `let) letsym) ,varsvalues . ,body-forms)
-
- (when (eq letsym 'let)
- (dolist (elm varsvalues) ; analyse values
- (when (listp elm)
- (cconv-analyse-form (cadr elm) vars inclosure))))
+ (`(,(and (or `let* `let) letsym) ,binders . ,body-forms)
- (let ((v nil)
+ (let ((orig-env env)
(var nil)
- (value nil)
- (varstruct nil))
- (dolist (elm varsvalues)
- (if (listp elm)
+ (value nil))
+ (dolist (binder binders)
+ (if (not (consp binder))
(progn
- (setq var (car elm))
- (setq value (cadr elm)))
- (progn
- (setq var elm) ; treat the form (let (x) ...) well
- (setq value nil)))
-
- (when (eq letsym 'let*) ; analyse value
- (cconv-analyse-form value vars inclosure))
-
- (let (vars-new) ; remove the old var
- (dolist (vr vars)
- (when (not (eq (car vr) var))
- (push vr vars-new)))
- (setq vars vars-new))
-
- (setq varstruct (list var inclosure elm form))
- (push varstruct vars) ; push a new one
-
- (when (and (listp value)
- (eq (car value) 'function)
- (eq (caadr value) 'lambda))
- ; if var is a function
- ; push it to lambda list
- (push varstruct cconv-lambda-candidates))))
-
- (dolist (elm body-forms) ; analyse body forms
- (cconv-analyse-form elm vars inclosure))
- nil)
+ (setq var binder) ; treat the form (let (x) ...) well
+ (setq value nil))
+ (setq var (car binder))
+ (setq value (cadr binder))
+
+ (cconv-analyse-form value (if (eq letsym 'let*) env orig-env)
+ inclosure))
+
+ (unless (cconv-not-lexical-var-p var)
+ (let ((varstruct (list var inclosure binder form)))
+ (push varstruct env) ; Push a new one.
+
+ (pcase value
+ (`(function (lambda . ,_))
+ ;; If var is a function push it to lambda list.
+ (push varstruct cconv-lambda-candidates)))))))
+
+ (dolist (form body-forms) ; Analyse body forms.
+ (cconv-analyse-form form env inclosure)))
+
; defun special form
(`(,(or `defun `defmacro) ,func ,vrs . ,body-forms)
- (let ((v nil))
- (dolist (vr vrs)
- (push (list vr form) vars))) ;push vrs to vars
- (dolist (elm body-forms) ; analyse body forms
- (cconv-analyse-form elm vars inclosure))
- nil)
-
- (`(function . ((lambda ,vrs . ,body-forms)))
- (if inclosure ;we are in closure
- (setq inclosure (+ inclosure 1))
- (setq inclosure 1))
- (let (vars-new) ; update vars
- (dolist (vr vars) ; we do that in such a tricky way
- (when (not (memq (car vr) vrs)) ; to avoid side effects
- (push vr vars-new)))
- (dolist (vr vrs)
- (push (list vr inclosure form) vars-new))
- (setq vars vars-new))
-
- (dolist (elm body-forms)
- (cconv-analyse-form elm vars inclosure))
- nil)
-
- (`(setq . ,forms) ; setq
- ; if a local variable (member of vars)
- ; is modified by setq
- ; then it is a mutated variable
+ (when env
+ (byte-compile-log-warning
+ (format "Function %S will ignore its context %S"
+ func (mapcar #'car env))
+ t :warning))
+ (cconv-analyse-function vrs body-forms nil form 0))
+
+ (`(function (lambda ,vrs . ,body-forms))
+ (cconv-analyse-function vrs body-forms env form (1+ inclosure)))
+
+ (`(setq . ,forms)
+ ;; If a local variable (member of env) is modified by setq then
+ ;; it is a mutated variable.
(while forms
- (let ((v (assq (car forms) vars))) ; v = non nil if visible
+ (let ((v (assq (car forms) env))) ; v = non nil if visible
(when v
(push v cconv-mutated)
- ;; delete from candidate list for lambda lifting
+ ;; Delete from candidate list for lambda lifting.
(setq cconv-lambda-candidates (delq v cconv-lambda-candidates))
- (when inclosure
- ;; test if v is declared as argument for lambda
- (let* ((thirdv (third v))
- (isarg (if (listp thirdv)
- (eq (car thirdv) 'function) nil)))
- (if isarg
- (when (> inclosure (cadr v)) ; when we are in closure
- (push v cconv-captured)) ; push it to captured vars
- ;; FIXME more detailed comments needed
- (push v cconv-captured))))))
- (cconv-analyse-form (cadr forms) vars inclosure)
- (setq forms (cddr forms)))
- nil)
-
- (`((lambda . ,_) . ,_) ; first element is lambda expression
+ (unless (eq inclosure (cadr v)) ;Bound in a different closure level.
+ (push v cconv-captured))))
+ (cconv-analyse-form (cadr forms) env inclosure)
+ (setq forms (cddr forms))))
+
+ (`((lambda . ,_) . ,_) ; first element is lambda expression
(dolist (exp `((function ,(car form)) . ,(cdr form)))
- (cconv-analyse-form exp vars inclosure))
- nil)
+ (cconv-analyse-form exp env inclosure)))
(`(cond . ,cond-forms) ; cond special form
- (dolist (exp1 cond-forms)
- (dolist (exp2 exp1)
- (cconv-analyse-form exp2 vars inclosure)))
- nil)
+ (dolist (forms cond-forms)
+ (dolist (form forms)
+ (cconv-analyse-form form env inclosure))))
(`(quote . ,_) nil) ; quote form
-
(`(function . ,_) nil) ; same as quote
- (`(condition-case ,var ,protected-form . ,conditions-bodies)
- ;condition-case
- (cconv-analyse-form protected-form vars inclosure)
- (dolist (exp conditions-bodies)
- (cconv-analyse-form (cadr exp) vars inclosure))
- nil)
-
- (`(,(or `defconst `defvar) ,value)
- (cconv-analyse-form value vars inclosure))
+ (`(condition-case ,var ,protected-form . ,handlers)
+ ;; FIXME: The bytecode for condition-case forces us to wrap the
+ ;; form and handlers in closures (for handlers, it's probably
+ ;; unavoidable, but not for the protected form).
+ (setq inclosure (1+ inclosure))
+ (cconv-analyse-form protected-form env inclosure)
+ (push (list var inclosure form) env)
+ (dolist (handler handlers)
+ (dolist (form (cdr handler))
+ (cconv-analyse-form form env inclosure))))
+
+ ;; FIXME: The bytecode for catch forces us to wrap the body.
+ (`(,(or `catch `unwind-protect) ,form . ,body)
+ (cconv-analyse-form form env inclosure)
+ (setq inclosure (1+ inclosure))
+ (dolist (form body)
+ (cconv-analyse-form form env inclosure)))
+
+ ;; FIXME: The bytecode for save-window-excursion and the lack of
+ ;; bytecode for track-mouse forces us to wrap the body.
+ (`(,(or `save-window-excursion `track-mouse) . ,body)
+ (setq inclosure (1+ inclosure))
+ (dolist (form body)
+ (cconv-analyse-form form env inclosure)))
+
+ (`(,(or `defconst `defvar) ,var ,value . ,_)
+ (push var byte-compile-bound-variables)
+ (cconv-analyse-form value env inclosure))
(`(,(or `funcall `apply) ,fun . ,args)
- ;; Here we ignore fun because
- ;; funcall and apply are the only two
- ;; functions where we can pass a candidate
- ;; for lambda lifting as argument.
- ;; So, if we see fun elsewhere, we'll
- ;; delete it from lambda candidate list.
-
- ;; If this funcall and the definition of fun
- ;; are in different closures - we delete fun from
- ;; canidate list, because it is too complicated
- ;; to manage free variables in this case.
- (let ((lv (assq fun cconv-lambda-candidates)))
- (when lv
- (when (not (eq (cadr lv) inclosure))
- (setq cconv-lambda-candidates
- (delq lv cconv-lambda-candidates)))))
-
- (dolist (elm args)
- (cconv-analyse-form elm vars inclosure))
- nil)
-
- (`(,_ . ,body-forms) ; first element is a function or whatever
- (dolist (exp body-forms)
- (cconv-analyse-form exp vars inclosure))
- nil)
-
- (_
- (when (and (symbolp form)
- (not (memq form '(nil t)))
- (not (keywordp form))
- (not (special-variable-p form)))
- (let ((dv (assq form vars))) ; dv = declared and visible
- (when dv
- (when inclosure
- ;; test if v is declared as argument of lambda
- (let* ((thirddv (third dv))
- (isarg (if (listp thirddv)
- (eq (car thirddv) 'function) nil)))
- (if isarg
- ;; FIXME add detailed comments
- (when (> inclosure (cadr dv)) ; capturing condition
- (push dv cconv-captured))
- (push dv cconv-captured))))
- ; delete lambda
- (setq cconv-lambda-candidates ; if it is found here
- (delq dv cconv-lambda-candidates)))))
- nil)))
+ ;; Here we ignore fun because funcall and apply are the only two
+ ;; functions where we can pass a candidate for lambda lifting as
+ ;; argument. So, if we see fun elsewhere, we'll delete it from
+ ;; lambda candidate list.
+ (if (symbolp fun)
+ (let ((lv (assq fun cconv-lambda-candidates)))
+ (when lv
+ (unless (eq (cadr lv) inclosure)
+ (push lv cconv-captured)
+ ;; If this funcall and the definition of fun are in
+ ;; different closures - we delete fun from candidate
+ ;; list, because it is too complicated to manage free
+ ;; variables in this case.
+ (setq cconv-lambda-candidates
+ (delq lv cconv-lambda-candidates)))))
+ (cconv-analyse-form fun env inclosure))
+ (dolist (form args)
+ (cconv-analyse-form form env inclosure)))
+
+ (`(,_ . ,body-forms) ; First element is a function or whatever.
+ (dolist (form body-forms)
+ (cconv-analyse-form form env inclosure)))
+
+ ((pred symbolp)
+ (let ((dv (assq form env))) ; dv = declared and visible
+ (when dv
+ (unless (eq inclosure (cadr dv)) ; capturing condition
+ (push dv cconv-captured))
+ ;; Delete lambda if it is found here, since it escapes.
+ (setq cconv-lambda-candidates
+ (delq dv cconv-lambda-candidates)))))))
(provide 'cconv)
;;; cconv.el ends here