To make Idea 1 of the OP work with @goto, scrap the above idea of working with the AST and instead work closer to the IR. Seems easier overall (less special cases).
Draft Pseudo-Code of Idea 1 (with goto):
(assumption: all AST has been lowered to flattened IR, but no Core.Boxes have been inserted yet. There might currently be no point during lowering where this is true, but the concept could be adapted.)
With prior knowledge that a local variable x is captured by a closure, decide whether to box x by the following procedure (true for box, false for no-box):
Define assigns(n,x,checked) to take a line number n, a symbol x, and a set of previously explored line numbers checked. assigns operates as follows:
- If
n ∈ checked, return false. - Push
nintochecked. - While true:
a. If expression at linenis aExpr(:(=), x, ...), return true.
b. If expression at linenis a:methodof a closure known to capturex, pushnintochecked. Loop over every sub-expression in its CodeInfo’s.code. If any is aExpr(:(=), x, ...), return true.
c. If expression at linenis aCore.GotoNode, returnassigns(ex.label,x,checked).
d. If expression at linenis aCore.GotoIfNot, returnassigns(n+1,x,checked)||assigns(ex.label,x,checked).
e. If expression at linenis aCore.ReturnNode, or if there are no more expressions, return false.
f. Incrementn.
Then, initialize checked to an empty set; let n be the line at which the closure :method is defined; and if assigns(n,x,checked), then box x. If multiple closures are known to capture x, simply call assigns on them without emptying checked to save some computations.
The basic gist is: Explore all expressions that are syntactically reachable in and after the closure’s declaration. If any is an assignment to the captured variable, then box it—but otherwise don’t. Keep a record checked of labels and methods that have been visited, to avoid repeated work and getting stuck in loops. As before:
cc @simeonschaub you seem to have a pretty good grasp of closure capture boxing; do you mind if I entice you to opine?