Closure Conversion in Compiler Design
- Closure Conversion is a compiler transformation that converts functions with free variables into explicit closures by pairing closed code with an environment holding the captured variables.
- It enables static allocation and separate compilation by making function representations explicit, while addressing type preservation challenges in dependently typed languages.
- Recent studies have analyzed its operational behavior, showing trade-offs between increased initial code size and reduced dynamic costs, with proofs of correctness via abstract machines.
Searching arXiv for recent and foundational papers on closure conversion to ground the article. Closure conversion is a compiler transformation that converts higher-order functions with free variables into explicit closures consisting of closed code together with an environment holding the values of the free variables. Operationally, the environment is constructed at run time, whereas the code is closed and can be statically allocated and lifted. In compiler pipelines this makes first-class functions explicit, suitable for static allocation and low-level calling conventions, and it can enable separate compilation and linking by exposing code and environment structure. In typed settings, however, closure conversion is not only a representational transformation but also a problem in preserving typing and definitional equality; this becomes especially acute in dependently typed languages, where types themselves may depend on term variables (Bowman et al., 2018). Recent work also analyzes closure conversion through abstract machines and flat environments, showing that closure conversion can decrease dynamic costs while increasing initial code size, with overall asymptotic complexity preserved (Accattoli et al., 21 Jul 2025).
1. Core transformation and compiler role
Closure conversion replaces an open function by a pair of components: code and environment. The code is a closed function with no free variables. The environment is a data structure containing the values of the free variables used by the original function body. Application then becomes application of the closed code to both the environment and the ordinary argument.
This transformation is central in functional-language compilation because it makes the run-time representation of functions explicit. In the formulation studied for the Calculus of Constructions, closure conversion is motivated by the need to preserve specifications and proofs of correctness through compilation into target-language programs. In that setting, the difficulty is that dependent type systems are designed around high-level compositional abstractions for type checking, whereas compilation introduces explicit reasoning about run-time terms and environments (Bowman et al., 2018).
A basic distinction is between untyped and typed closure conversion. In the untyped case, a compiler can directly pair code with an environment without confronting type-equality problems. In typed calculi such as STLC or System F, the standard technique is to hide the environment type behind an existential package. The canonical examples given for simply typed settings are
and
This conventional account does not transfer directly to dependent type theory, because the result type of a function may refer to values stored in the environment.
A common misconception is that closure conversion is merely “lambda lifting with a tuple.” The dependent-type setting shows otherwise: if types depend on terms, then the transformation must reconcile closed code types with closure values whose observable types still mention the free variables that were extracted into an environment. That tension determines much of the modern typed literature on closure conversion.
2. Classical formulations, flat environments, and closure invariants
A recent operational study formulates closure conversion over a simple call-by-value -calculus with tuples and decomposes the transformation into two precise phases: wrapping and name elimination. Wrapping replaces a source abstraction by a closure
where is the sequence of free variables of the abstraction and the initial bag stores those variables. Name elimination then removes source variable names, replacing occurrences of bound variables by projected variables such as and , and representing target closures as
The target closures implicitly bind just two parameters, and 0, corresponding to the wrapped environment and the source arguments, and projected-variable lookup becomes indexed tuple access (Accattoli et al., 21 Jul 2025).
The same work focuses on flat closures and environments, meaning that there is no sharing between closure environments. In the intermediate language, bags are either variable bags or evaluated bags; in the target language, bag entries are projected variables or values. Well-formedness conditions enforce the closure invariants: the code may refer only to its designated binders, and the bag length must match the number of captured variables.
These invariants are not merely administrative. They permit abstract machines to use stackable or tupled environments without the shortcomings attributed to other styles. Before closure conversion, local environments and machine closures are required to manage free variables. After closure conversion, the machines can exploit the fact that converted closures already expose their free-variable structure. This yields a new discipline for environment handling that avoids a-renaming, avoids sharing and duplication overheads associated with other approaches, and supports a simple pop-and-restore mechanism for activation records (Accattoli et al., 21 Jul 2025).
The same operational account also gives a correctness proof that avoids reliance on 1-equivalence. Instead, it uses small-step, termination-preserving strong bisimulations between the source, intermediate, and target calculi. This is significant because, in some settings, 2-equivalence is not an adequate foundation for closure-conversion correctness.
3. Dependent types and the challenge of type preservation
In dependently typed languages, free term variables appear not only in bodies but also in types. The source language studied in the typed development is the Calculus of Constructions with strong dependent pairs, described as a subset of the core language of Coq. Its terms and types share a uniform syntax with universes
3
and expressions including variables, dependent let, 4-types, functions, applications, 5-types, dependent pairs, and projections: 6 Definitional equivalence closes reduction under reflexive, transitive, and contextual closure, and includes 7 for functions. Reduction includes 8 for unfolding definitions, 9 for dependent let, 0 for application, and 1 for pair projections (Bowman et al., 2018).
The difficulty for typed closure conversion is sharpest at function types. In CC, a function may have type
2
where 3 depends on 4. If the original function also depends on some free variable 5, then both 6 and 7 may mention 8. Standard existential encodings hide the environment type, but in the dependent setting the result type may still need to refer to components of that hidden environment. The paper identifies three problems for the existential approach: weak dependent sums require impredicativity to live in Set; parametricity assumptions must hold to justify 9; and if environments contain type variables, then environment projections can appear in types and break type preservation (Bowman et al., 2018).
This leads to the central design decision of the typed development: the environment is not hidden via existential types. Instead, the target language introduces primitive notions of code and closure, with a closure typing rule that explicitly substitutes the environment into the code’s dependent type. A plausible implication is that closure conversion for dependent type theory is better treated as an extension of the target type system than as a direct reuse of the existential packaging techniques developed for simply typed languages.
4. CC-CC and typed closure conversion for the Calculus of Constructions
The target language CC-CC extends CC with primitive code and closure forms and with a unit type used to encode environments as dependent 0-tuples. Its expression forms include
1
together with the ordinary dependent products, dependent sums, projections, and applications. The key rule is that code must be closed: 2 Thus the code body type-checks only under an empty environment, with explicit parameters for the environment and the ordinary argument.
Closure formation is governed by the rule
3
The environment is therefore substituted into the code’s dependent type to obtain the observable 4-type of the closure. This is the mechanism that synchronizes closed code types with closure types that may still mention the original free variables (Bowman et al., 2018).
The translation from CC to CC-CC is compositional for most forms. Universes and variables translate directly. 5-types and 6-types translate structurally: 7 For functions, the translation computes the free variables of both the abstraction and its dependent function type: 8 It then packages those free variables into a dependent tuple environment and emits a closure whose code first projects the environment components, brings them into scope through dependent let-destructuring, and then executes the translated body.
Environments are represented as nested dependent pairs ending in unit. The paper presents notation for dependent 9-tuples and corresponding let-destructuring as syntactic sugar over nested 0-types and repeated 1 projections. This representation is essential because the free-variable sequence is itself dependent: the type of each later environment component may mention earlier components.
A representative example uses a source abstraction with one outer free variable 2: 3 The environment type is encoded as
4
and the environment value is
5
The closure type obtained by the Clo rule becomes
6
because substituting the concrete environment projects 7 from the dependent pair and propagates it into the translated domain and codomain types.
5. Equivalence, soundness, and separate compilation
The typed development establishes type preservation for the CC 8 CC-CC translation: 9 The central challenge is not merely typing each translated term, but preserving the source language’s conversion principles after functions become closures. CC uses definitional equivalence with 0 and function 1; CC-CC replaces function 2 with closure-specific equivalence rules, written 3-Clo4 and 5-Clo6, which compare closures by inlining environments and comparing the resulting code bodies applied to a fresh argument (Bowman et al., 2018).
This treatment of closure equivalence is tied to a compositionality lemma for translation: 7 The lemma is necessary because substitution interacts with environment construction and with type-level projections from dependent tuples. The operational semantics of CC-CC includes the closure 8-step
9
together with the usual 0, 1, and 2 reductions.
Soundness of CC-CC is proved by a model back into CC. The model translates code and closures back to curried functions and applications. Its key metatheoretic results include the lemma
3
the coherence statement
4
the consistency theorem asserting that there is no closed 5 such that
6
and the type-safety theorem
7
These results justify the new closure-specific typing and equivalence principles by showing that CC-CC is type-safe and consistent via interpretation in CC (Bowman et al., 2018).
The same development proves correctness of separate compilation. Under the hypotheses that 8 with 9 ground, that the source and target substitutions are well formed, that the substitutions correspond, and that the source program evaluates to 0, the theorem states: 1 This theorem depends on the facts that translation commutes with substitution, preserves equivalence, and that reduction implies equivalence. The result is notable because separate compilation is usually one of the practical motivations for closure conversion, and here it is established in a dependently typed setting rather than only in an untyped or simply typed intermediate language.
6. Abstract machines, correctness proofs, and complexity
The operational study of closure conversion formulates three tupled abstract machines, one for each stage of the source/intermediate/target pipeline: the Source TAM for the source calculus, the Int TAM for the intermediate closure language, and the Target TAM for the name-free target calculus. The Source TAM uses local environments and machine closures in a right-to-left CEK-style machine. The Int TAM uses stackable environments and closures, eliminating machine closures. The Target TAM uses tupled environments and projected variables, so that lookup of 2 or 3 is an indexed access into the appropriate tuple (Accattoli et al., 21 Jul 2025).
The Int TAM and Target TAM exploit the invariants introduced by closure conversion. In the Int TAM, bag evaluation converts a variable bag into a tuple of closed values taken from the current environment. Principal 4-reduction then installs a new stackable environment binding the captured variables and arguments, while the previous machine context is pushed onto an activation stack. In the Target TAM, the same mechanism survives name elimination, but all variable access is mediated by projected-variable lookup, which the paper characterizes as de Bruijn-like.
Correctness is established via small-step bisimulations. Reverse unwrapping gives a bisimulation between the intermediate calculus and the source calculus; name elimination gives a bisimulation between the intermediate and target calculi; and composing them yields a final correctness theorem for closure conversion. For closed target terms, projection, reflection, halt preservation, and inversion are all proved. This proof style is presented as simpler and more robust than logical-relations or big-step arguments, and it does not rely on 5-equivalence (Accattoli et al., 21 Jul 2025).
The same work also studies time complexity. For the Source TAM, the total running time of a complete run is bounded by
6
For the Target TAM after closure conversion, all transitions are amortized 7 on complete runs, and the total cost becomes
8
However, wrapping increases the size of the initial code: 9 and there are families with
0
The final complexity theorem states that running the Target TAM on 1 yields the same asymptotics as running the Source TAM on 2: 3 Thus closure conversion decreases various dynamic costs while increasing initial code size; the two effects cancel asymptotically (Accattoli et al., 21 Jul 2025).
A further operational caveat concerns tuples. The analysis exhibits families with exponential blow-up in values due to tuples, which makes tuple sharing mandatory in implementations: implementations should copy pointers to tuples rather than tuples themselves. This point is part of the paper’s broader claim that closure conversion should be studied together with machine design and concrete cost models, not only as a source-to-source program transformation.
7. Limits of current formulations and research directions
The typed development is carried out for CC rather than the full Calculus of Inductive Constructions, and the paper identifies several obstacles to scaling further. Recursion and termination are one obstacle: closure conversion disrupts syntactic guardedness, so extending the method to richer source languages may require compiling guardedness to inductive eliminators or adopting semantic termination techniques such as sized types (Bowman et al., 2018).
Performance is another open issue. The paper notes that abstract closure conversion may introduce extra allocations and dereferences relative to existential-package encodings, even though the primitive code/closure approach avoids the type-theoretic difficulties of impredicative existential sums. This suggests that there is a trade-off between representational abstraction and run-time overhead in typed intermediate languages.
Computational relevance also remains difficult in Coq-like settings because the infinite universe hierarchy complicates relevance distinctions. The paper suggests that a source language exposing relevance explicitly may be needed. Beyond closure conversion itself, the authors identify heap allocation and code generation as remaining passes on the path toward a dependently typed assembly language. They also note challenges in designing such an assembly language with safe linking, effect/purity interoperation, and consistent control (Bowman et al., 2018).
Finally, the model and compiler theorems for CC and CC-CC preserve and reflect definitional equivalence, which the paper presents as suggesting a path toward full abstraction. The qualification is important: contextual equivalence requires additional work. A plausible implication is that closure conversion, especially in dependently typed settings, occupies a boundary between verified compilation, intermediate-language design, and secure compilation, rather than being a solved standalone pass.
Taken together, the contemporary literature presents closure conversion as both a classical compiler transformation and a locus of active research. In simple operational settings it yields flat closure invariants that support clean machine design and preserved asymptotic complexity. In dependent type theory it requires new target-language primitives and new equivalence principles to preserve typing, definitional equality, and separate compilation.