If you have a closed-world assumption (which is not possible in a dynamic language without a lot of careful work, c.f. Dylan's "sealing" operation), you can view the execution environment as a kind of DAG that changes at each program step. Whenever a subgraph gets isolated, it is garbage, and you can reclaim its storage at that point. This was called "compile-time garbage collection" in the Concurrent Clean literature. Basically you need to know the input and output types and the side effects of all the functions in the graph. This is also very similar to Henry Baker's "linear lisp" concept as described in his papers.
In practice, I don't know if this static approach is really an advantage. With a copying GC, you tend to have better cache locality, although the precise point in the computation when tracing happens is less predictable and can have a major impact.