[### Continuation kinds...]
The block structure represents a basic block, in the the normal sense. Control transfers other than simple
sequencing are represented by information in the block structure. The continuation for the last node in a block
represents only the destination for the result.
It is very difficult to reconstruct anything resembling the original source from ICR, so we record the original
source form in each node. The location of the source form within the input is also recorded, allowing for
interfaces such as “Edit Compiler Warnings”. See section ??.
Forms such as special-bind and catch need to have cleanup code executed at all exit points from the form. We
represent this constraint in ICR by annotating the code syntactically within the form with a Cleanup structure
describing what needs to be cleaned up. Environment analysis determines the cleanup locations by watching
for a change in the cleanup between two continuations. We can’t emit cleanup code during ICR conversion,
since we don’t know which exits will be local until after ICR optimizations are done.
Special binding is represented by a call to the funny function %Special-Bind. The first argument is the
Global-Var structure for the variable bound and the second argument is the value to bind it to.
Some subprimitives are implemented using a macro-like mechanism for translating %PRIMITIVE forms
into arbitrary lisp code. Subprimitives special-cased by VMR conversion are represented by a call to the funny
function %%Primitive. The corresponding Template structure is passed as the first argument.
We check global function calls for syntactic legality with respect to any defined function type function. If
the call is illegal or we are unable to tell if it is legal due to non-constant keywords, then we give a warning
and mark the function reference as :notinline to force a full call and cause subsequent phases to ignore the call.
If the call is legal and is to a known function, then we annotate the Combination node with the Function-Info
structure that contains the compiler information for the function.
4.1 Tail sets
#— Probably want to have a GTN-like function result equivalence class mechanism for ICR type inference.
This would be like the return value propagation being done by Propagate-From-Calls, but more powerful, less
hackish, and known to terminate. The ICR equivalence classes could probably be used by GTN, as well.
What we do is have local call analysis eagerly maintain the equivalence classes of functions that return the
same way by annotating functions with a Tail-Info structure shared between all functions whose value could
be the value of this function. We don’t require that the calls actually be tail-recursive, only that the call deliver
its value to the result continuation. [### Actually now done by ICR-OPTIMIZE-RETURN, which is currently
making ICR optimize mandatory.]
We can then use the Tail-Set during ICR type inference. It would have a type that is the union across all
equivalent functions of the types of all the uses other than in local calls. This type would be recomputed during
optimization of return nodes. When the type changes, we would propagate it to all calls to any of the equivalent
functions. How do we know when and how to recompute the type for a tail-set? Recomputation is driven by
type propagation on the result continuation.
This is really special-casing of RETURN nodes. The return node has the type which is the union of all the
non-call uses of the result. The tail-set is found though the lambda. We can then recompute the overall union
by taking the union of the type per return node, rather than per-use.
How do result type assertions work? We can’t intersect the assertions across all functions in the equivalence
class, since some of the call combinations may not happen (or even be possible). We can intersect the assertion
of the result with the derived types for non-call uses.
When we do a tail call, we obviously can’t check that the returned value matches our assertion. Although in
principle, we would like to be able to check all assertions, to preserve system integrity, we only need to check
assertions that we depend on. We can afford to lose some assertion information as long as we entirely lose it,
ignoring it for type inference as well as for type checking.
Things will work out, since the caller will see the tail-info type as the derived type for the call, and will emit
a type check if it needs a stronger result.
A remaining question is whether we should intersect the assertion with per-RETURN derived types from
the very beginning (i.e. before the type check pass). I think the answer is yes. We delay the type check pass so
that we can get our best guess for the derived type before we decide whether a check is necessary. But with the
function return type, we aren’t committing to doing any type check when we intersect with the type assertion;
the need to type check is still determined in the type check pass by examination of the result continuation.
15