Lift Compiler

At various times a join in the abstract interpretation of the binding times loses information. This causes a variable to be lifted from one binding time to another. Simple cases are handled directly by cogen, but with partially static and circular binding times, lifting can be much more complex. This section describes how cogen uses itself to create lift compilers to handle complex lifting.

Some lifts are handled directly by cogen; these are primitive lifts. Eg, a lift from S to D causes a constant declaration to be generated by the compiler; the value of the constant is the static value, held in a local variable in the generated compiler. There is also the primitive higher-order lift, described below.

Consider the compiler for lifting (cons (cons S D) (cons S D)) to D. In lisp, the compiler would look like [note compile]

This example belies how hard lifting is in general: circular binding times, variable splitting, and higher-order lifting all make this harder. Fortunately, there is a nice way to handle all of this by using cogen itself. First define lift inductively (ignore higher-order procedures for a moment):

[note match]

This procedure copies val while traversing the binding times in parallel fashion. When the binding times indicate that we have reached a part of val that must be lifted, then the primitive lift is applied. If we execute this procedure, it would only copy the value. Instead, use the lift compiler

to create a procedure specialized to particular binding times:

This procedure lifts the right parts of val, but the control flow based on the binding-times has been eliminated. Control flow based on val will remain because of the pair? in the recursive case. But it's still just a copier.

At the point of the lift, a call to this procedure is inserted into the source instruction list. cont finishes the intructions list, it takes the lifted value and the live variables as arguments. So when cogen reaches this procedure call, another compiler is generated:

This is exactly what we had above. It's interesting how the static data remaining in bt-from is passed to cont. The control flow from the pair? calls remains only where there was a loop in the binding times (see spines) [how does this relate to memoizing compilers only where required?]

Higher-order lifts can be handled by adding an addition case to lift that invokes cogen's primitive higher-order lift. Also when it conses up the result, it must use closure-cons as appropriate.

So when cogen encounters a non-primitive lift, it inserts a procedure call to a specialized version of lift into the source code, and cogens this instead. The lift compiler is a good example of three (it will be four when the general lift is written in mini-scheme instead of root) layer composition (see layers).

Lifting a atom to a partially static binding time requires replicating the atom in both binding times (think of splitting an environment into two lists, each has its own terminating nil). This atom must be copied.

[note lift-compiler-cruft]

I think the lift compiler might simplify things enough that it could be used to support direct implementation of a multistage cogen, instead of using multipass.