google / starlark-go

Starlark in Go: the Starlark configuration language, implemented in Go
BSD 3-Clause "New" or "Revised" License
2.34k stars 212 forks source link

starlark: capture free variables by reference #172

Closed alandonovan closed 5 years ago

alandonovan commented 5 years ago

This change causes closures for nested functions to capture their enclosing functions' variables by reference. Even though an inner function cannot update outer variables, it must observe updates to them made by the outer function.

A special case of this is a nested recursive function f:

def outer(): def f(): ...f()...

The def f statement constructs a closure which captures f, and then binds the closure value to f. If the closure captures by value (as before this change), the def statement will fail because f is undefined (see issue #170). Now, the closure captures a reference to f, so it is safe to execute before f has been assigned.

This is implemented as follows. During resolving, captured local variables such as f are marked as as "cells". The compiler assumes and guarantees that such locals are values of a special internal type called 'cell', and it emits explicit instructions to load from and store into the cell. At runtime, cells are created on entry to the function; parameters may be "spilled" into cells as needed. Each cell variable gets its own allocation to avoid spurious liveness. A function's tuple of free variables contains only cells.

Fixes #170