github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/notes/to-be-closed.md (about)

     1  # Implementation notes for to-be-closed variables
     2  
     3  This is a feature introduced in Lua 5.4.  See
     4  https://www.lua.org/manual/5.4/manual.html#3.3.8
     5  
     6  ## Approach
     7  
     8  I try to give a succinct explanation of the implementation approach.
     9  
    10  ### Height of a lexical scope
    11  
    12  During AST -> IR compilation, a **height** is assigned to each lexical scope.
    13  If L2 is a lexical scope with parent L1, then
    14  
    15      height(L2) = height(L1) + n, where n is the number of to-be-closed variables that L2 defines
    16  
    17  If L is a root lexical scope (i.e. it is the outermost block in a function body)
    18  then
    19  
    20      height(L) = 0
    21  
    22  ### Code generation
    23  
    24  There are 2 new opcodes:
    25  - `clpush <reg>`: push the contents of `<reg>` onto the close stack
    26  - `cltrunc h`: truncate the close stack to height `h` (`h` is encoded in the
    27    opcode and must be known at compile time)
    28  
    29  This is how the opcodes are inserted
    30  - Whenever a to-be-closed varable is defined (`local x <close> = val`), a
    31    `clpush r1` instruction is emitted, where `r1` is the register containing
    32    `val`.
    33  - Whenever a lexical scope L is exited, a `cltrunc h` instruction is emitted
    34    where `h` is the height of the parent scope of `L`
    35  - Whenever a Jump is emitted, just before the jump is emitted a `cltrunc h`
    36    instruction is emitted where `h` is the height of the lexical scope of the
    37    jump destination.
    38  
    39  ### Runtime
    40  
    41  There are two aspects to the runtime machinery - normal execution of Lua
    42  continutaions and error handling
    43  
    44  #### Normal execution of Lua continuations
    45  
    46  Each Lua thread maintains a **close stack**, which is a stack of Lua
    47  values. It starts empty and is modified as follows
    48  - when a Lua continuation is created it records the current height of the close
    49    stack as an offset, called `base`.
    50  - `clpush <reg>` pushes the value of `<reg>` on top of the close stack.
    51  - `cltrunc h` pops values from the top of the close stack and executes their
    52    `__close` metamethod until the height of the close stack is at most `h + base`.
    53  - When returning from a Lua function, a `cltrunc 0` instruction is executed.
    54  - tail-calls are forbidden if there are to-be-closed values pending at the point
    55    of the tail-call, because they need to be run after returning from the call.
    56  
    57  #### Error handling
    58  
    59  If there is an error, then all close stacks in the "call stack" should be called
    60  until the error is caught.  The `Thread.CallContext` function makes sure the
    61  pending to-be-closed values are called after execution has finished, whether
    62  there was an error or not.
    63  
    64  #### Coroutines
    65  
    66  Lua 5.4 adds a `coroutine.close()` function that allows stopping a suspended
    67  coroutine but executing all pending to-be-closed variables.  Given the approach
    68  above this is simply a matter of executing the `cleanupCloseStack()` method on
    69  the suspended thread.
    70