Subject: Re: bug#51982: Erroneous handling of local variables in byte-compiled nested lambdas

From: Mattias Engdegård <[email-masked]>
Date: Mon, 22 Nov 2021 18:35:18 +0100

> I just found a case where it doesn't work. A repaired patch will arrive soon (we hope).

Not one but two patches for your enjoyment, representing two alternative solutions. Patch A is an extension of the original proposal and is simpler but perhaps less performant; patch B is messier but may result in better code.

To connect to the previous example, cconv transforms the function

(defun f (x)
  (lambda ()
    (let ((f (lambda () x)))
      (let ((x 'a))
        (list x (funcall f))))))

with patch A into

(defun f (x)
  (internal-make-closure
   nil (x) nil
   (let ((f (lambda (x) x)))
     (let ((x 'a)
           (closed-x (internal-get-closed-var 0)))
       (list x (funcall f closed-x))))))

and with patch B into

(defun f (x)
  (internal-make-closure
   nil (x) nil
   (let ((f (lambda (x) x)))
     (let ((x 'a))
       (list x (funcall f (internal-get-closed-var 0)))))))

This looks like a wash but the optimiser isn't able to elide that superfluous closed-x variable yet, and in Paul's original example the captured variable is only used in one conditional branch which makes it a loss to bind it up-front whereas it's very cheap to materialise at the call site (a single constant-pushing byte op).

On the other hand, patch B does abuse the cconv data structures a little (but it works!). We'll see if Stefan can stomach it.

(This reminds me: we should probably declare internal-get-closed-var as pure and error-free, even though it's not even an actual function.)