diff options
| author | Stefan Monnier | 2016-08-09 13:05:03 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2016-08-09 13:05:03 -0400 |
| commit | c97cd6c005e138856d99ecef86fa04674c34b779 (patch) | |
| tree | 28ff4e151014231c4c9e33d49285727336565765 | |
| parent | 9cf9095838aefec9871b9922a95cb4c199696eb8 (diff) | |
| download | emacs-c97cd6c005e138856d99ecef86fa04674c34b779.tar.gz emacs-c97cd6c005e138856d99ecef86fa04674c34b779.zip | |
* lisp/emacs-lisp/cconv.el: Fix λ-lifting in the presence of shadowing
Change the code which detects and circumvents the case where one of the
variables used in λ-lifting is shadowed, so that it also works when the
shadowing comes before the λ-lifted function (bug#24171).
(cconv--remap-llv): New function, extracted from cconv-convert.
(cconv-convert): Use it, but differently for `let' and `let*'.
| -rw-r--r-- | lisp/emacs-lisp/cconv.el | 76 |
1 files changed, 48 insertions, 28 deletions
diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el index 50b1fe32661..9f843676357 100644 --- a/lisp/emacs-lisp/cconv.el +++ b/lisp/emacs-lisp/cconv.el | |||
| @@ -253,6 +253,32 @@ Returns a form where all lambdas don't have any free variables." | |||
| 253 | `(internal-make-closure | 253 | `(internal-make-closure |
| 254 | ,args ,envector ,docstring . ,body-new))))) | 254 | ,args ,envector ,docstring . ,body-new))))) |
| 255 | 255 | ||
| 256 | (defun cconv--remap-llv (new-env var closedsym) | ||
| 257 | ;; In a case such as: | ||
| 258 | ;; (let* ((fun (lambda (x) (+ x y))) (y 1)) (funcall fun 1)) | ||
| 259 | ;; A naive lambda-lifting would return | ||
| 260 | ;; (let* ((fun (lambda (y x) (+ x y))) (y 1)) (funcall fun y 1)) | ||
| 261 | ;; Where the external `y' is mistakenly captured by the inner one. | ||
| 262 | ;; So when we detect that case, we rewrite it to: | ||
| 263 | ;; (let* ((closed-y y) (fun (lambda (y x) (+ x y))) (y 1)) | ||
| 264 | ;; (funcall fun closed-y 1)) | ||
| 265 | ;; We do that even if there's no `funcall' that uses `fun' in the scope | ||
| 266 | ;; where `y' is shadowed by another variable because, to treat | ||
| 267 | ;; this case better, we'd need to traverse the tree one more time to | ||
| 268 | ;; collect this data, and I think that it's not worth it. | ||
| 269 | (mapcar (lambda (mapping) | ||
| 270 | (if (not (eq (cadr mapping) 'apply-partially)) | ||
| 271 | mapping | ||
| 272 | (cl-assert (eq (car mapping) (nth 2 mapping))) | ||
| 273 | `(,(car mapping) | ||
| 274 | apply-partially | ||
| 275 | ,(car mapping) | ||
| 276 | ,@(mapcar (lambda (arg) | ||
| 277 | (if (eq var arg) | ||
| 278 | closedsym arg)) | ||
| 279 | (nthcdr 3 mapping))))) | ||
| 280 | new-env)) | ||
| 281 | |||
| 256 | (defun cconv-convert (form env extend) | 282 | (defun cconv-convert (form env extend) |
| 257 | ;; This function actually rewrites the tree. | 283 | ;; This function actually rewrites the tree. |
| 258 | "Return FORM with all its lambdas changed so they are closed. | 284 | "Return FORM with all its lambdas changed so they are closed. |
| @@ -350,34 +376,13 @@ places where they originally did not directly appear." | |||
| 350 | (if (assq var new-env) (push `(,var) new-env)) | 376 | (if (assq var new-env) (push `(,var) new-env)) |
| 351 | (cconv-convert value env extend))))) | 377 | (cconv-convert value env extend))))) |
| 352 | 378 | ||
| 353 | ;; The piece of code below letbinds free variables of a λ-lifted | 379 | (when (and (eq letsym 'let*) (memq var new-extend)) |
| 354 | ;; function if they are redefined in this let, example: | 380 | ;; One of the lambda-lifted vars is shadowed, so add |
| 355 | ;; (let* ((fun (lambda (x) (+ x y))) (y 1)) (funcall fun 1)) | 381 | ;; a reference to the outside binding and arrange to use |
| 356 | ;; Here we can not pass y as parameter because it is redefined. | 382 | ;; that reference. |
| 357 | ;; So we add a (closed-y y) declaration. We do that even if the | 383 | (let ((closedsym (make-symbol (format "closed-%s" var)))) |
| 358 | ;; function is not used inside this let(*). The reason why we | 384 | (setq new-env (cconv--remap-llv new-env var closedsym)) |
| 359 | ;; ignore this case is that we can't "look forward" to see if the | 385 | (setq new-extend (cons closedsym (remq var new-extend))) |
| 360 | ;; function is called there or not. To treat this case better we'd | ||
| 361 | ;; need to traverse the tree one more time to collect this data, and | ||
| 362 | ;; I think that it's not worth it. | ||
| 363 | (when (memq var new-extend) | ||
| 364 | (let ((closedsym | ||
| 365 | (make-symbol (concat "closed-" (symbol-name var))))) | ||
| 366 | (setq new-env | ||
| 367 | (mapcar (lambda (mapping) | ||
| 368 | (if (not (eq (cadr mapping) 'apply-partially)) | ||
| 369 | mapping | ||
| 370 | (cl-assert (eq (car mapping) (nth 2 mapping))) | ||
| 371 | `(,(car mapping) | ||
| 372 | apply-partially | ||
| 373 | ,(car mapping) | ||
| 374 | ,@(mapcar (lambda (arg) | ||
| 375 | (if (eq var arg) | ||
| 376 | closedsym arg)) | ||
| 377 | (nthcdr 3 mapping))))) | ||
| 378 | new-env)) | ||
| 379 | (setq new-extend (remq var new-extend)) | ||
| 380 | (push closedsym new-extend) | ||
| 381 | (push `(,closedsym ,var) binders-new))) | 386 | (push `(,closedsym ,var) binders-new))) |
| 382 | 387 | ||
| 383 | ;; We push the element after redefined free variables are | 388 | ;; We push the element after redefined free variables are |
| @@ -390,6 +395,21 @@ places where they originally did not directly appear." | |||
| 390 | (setq extend new-extend)) | 395 | (setq extend new-extend)) |
| 391 | )) ; end of dolist over binders | 396 | )) ; end of dolist over binders |
| 392 | 397 | ||
| 398 | (when (not (eq letsym 'let*)) | ||
| 399 | ;; We can't do the cconv--remap-llv at the same place for let and | ||
| 400 | ;; let* because in the case of `let', the shadowing may occur | ||
| 401 | ;; before we know that the var will be in `new-extend' (bug#24171). | ||
| 402 | (dolist (binder binders-new) | ||
| 403 | (when (memq (car-safe binder) new-extend) | ||
| 404 | ;; One of the lambda-lifted vars is shadowed, so add | ||
| 405 | ;; a reference to the outside binding and arrange to use | ||
| 406 | ;; that reference. | ||
| 407 | (let* ((var (car-safe binder)) | ||
| 408 | (closedsym (make-symbol (format "closed-%s" var)))) | ||
| 409 | (setq new-env (cconv--remap-llv new-env var closedsym)) | ||
| 410 | (setq new-extend (cons closedsym (remq var new-extend))) | ||
| 411 | (push `(,closedsym ,var) binders-new))))) | ||
| 412 | |||
| 393 | `(,letsym ,(nreverse binders-new) | 413 | `(,letsym ,(nreverse binders-new) |
| 394 | . ,(mapcar (lambda (form) | 414 | . ,(mapcar (lambda (form) |
| 395 | (cconv-convert | 415 | (cconv-convert |