Consider this code:
(... (const code-pointer (code ...)) (prim closure cons code-pointer bound-vars) (jump p closure))
Say p
is D
and bound-vars
is partially static. Then
normally closure
would be lifted to D
before the jump. But if
we know closure
will only be called, then we can do better:
replace it with a closure where code-pointer
has been specialized
to the static portion of bound-vars
.
Rather than automatically recognizing this opportunity or using a
typed language, nitrous relies on a hint in the code: closure-cons
marks a particular cons cell as a closure.
bt ::=S
|D
| (cons bt bt cp is-clo) | (const v) cp ::= instruction is-clo ::= boolean v ::= any-value
Unlike other higher-order partial-evaluators, specialization of the
code pointer to its bound variables does not happen at the point in
the source where the lambda (closure-cons
) appears. Instead, the
full structure is propagated as a normal value until its binding time
is lifted to D
.
Cogen will create an extension for code structure cd
against
partially static binding time p
when it encounters the
following lift:
(cons (const cd) p _ #t) --> D
The compiler calls this extension to preserve the static information
in p
rather than lose it to the lift.
closure-cons
guarantees to cogen that the cell will only be used
in a particular way (jump to the car, passing the cell itself as the
first argument), thus it defines a higher-order calling convention.
If the programmer used a different representation, cogen would not be
able to follow the control flow. This is one way the system is
brittle.
This is somewhat different from how lambda expressions are handled in [Consel90].