Tom's Lisp uses reference counting for garbage collection, which cannot handle cycles. The interpreted language is such that interpreted code cannot create cyclic data structures, so those structures are not a problem. But garbage collection must deal with one internal data structure which does create cycles. Closures contain pointers to contexts, and contexts typically contain pointers to closures. Context objects themselves are linked in a stack arrangement: when a scope is entered, a new context is created which points to the previous one. This can create cycles, such as:
In the diagram, node
is the main context created when the program starts. The frames , and are contexts created by scope or by function invocation, but and have returned. While in scope , two closures, and , were created and stored with set. Obviously, several pointer cycles are created.Now, suppose the context
is popped, and then the pointer to is removed from the main context. Regular reference counting will clean up , but the remaining cycle will keep , and alive, and the cycle keeps around. All of these nodes are garbage which plain reference counting cannot clean up.The solution leverages the stack-ordering of scopes, and involves some additional counting. First, pointers to closures are considered supporting or non-supporting; supporting is the usual case. Non-supporting pointers are any stored in a context which has exited. The red pointer in the diagram is non-supporting. The blue one is supporting, as are any pointers which are not stored in contexts. Pointers from closures to contexts may be discounted, meaning that they do not contribute to the reference count of what they point to. Closure objects count the number of non-supporting pointers to them. When all pointers to them are non-supporting (the non-support count equals the reference count), they discount their context pointer. For instance, assuming the diagram shows all pointers, the nodes have the following counts:
Node | Reference Count | Non-Support Count | ||||
2 | ||||||
2 | ||||||
1 | ||||||
1 | The brown pointer is discounted. | |||||
1 | 0 | |||||
1 | 1 | The equal counts discount the brown pointer. |
Now, again consider the change where
is popped, and the blue pointer to is removed using the modified system. The new situation is:Note that discounting the brown pointer has the effect of breaking the cycle through
, . Once is gone, goes and the dominoes fall.The use careful use of nonsupporting and discounted pointers enables the reference counting to clean up the cyclic structures. Note that this relies on the stack-like behavior of context creation and destruction. It is not some long-sought general solution to collecting cycles with reference counting. It also depends on the fact that the Toms Lisp implements a very limited language, so it is impossible for the user program to create cyclic data structures. Therefore, the interpreter must clean up only its own well-understood messes, not figure out the user's.
Nonsupporting pointers are implemented by keeping to a count in each Closure object of the number of pointers pointing to it which are non-supporting. The Closure class has a method nonsup which increments the discount count and compares it to the reference count, then discounts its context pointer if needed. The Context class has a method last_rites which calls nonsup on each on each closure it contains. Last_rites is called from various places just before removing the context from the current context stack.