11.11.2014 Views

Design of CMU Common Lisp.pdf - Common Lisp.net

Design of CMU Common Lisp.pdf - Common Lisp.net

Design of CMU Common Lisp.pdf - Common Lisp.net

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

[### Continuation kinds...]<br />

The block structure represents a basic block, in the the normal sense. Control transfers other than simple<br />

sequencing are represented by information in the block structure. The continuation for the last node in a block<br />

represents only the destination for the result.<br />

It is very difficult to reconstruct anything resembling the original source from ICR, so we record the original<br />

source form in each node. The location <strong>of</strong> the source form within the input is also recorded, allowing for<br />

interfaces such as ”Edit Compiler Warnings”. See section ??.<br />

Forms such as special-bind and catch need to have cleanup code executed at all exit points from the form. We<br />

represent this constraint in ICR by annotating the code syntactically within the form with a Cleanup structure<br />

describing what needs to be cleaned up. Environment analysis determines the cleanup locations by watching<br />

for a change in the cleanup between two continuations. We can’t emit cleanup code during ICR conversion,<br />

since we don’t know which exits will be local until after ICR optimizations are done.<br />

Special binding is represented by a call to the funny function %Special-Bind. The first argument is the<br />

Global-Var structure for the variable bound and the second argument is the value to bind it to.<br />

Some subprimitives are implemented using a macro-like mechanism for translating %PRIMITIVE forms<br />

into arbitrary lisp code. Subprimitives special-cased by VMR conversion are represented by a call to the funny<br />

function %%Primitive. The corresponding Template structure is passed as the first argument.<br />

We check global function calls for syntactic legality with respect to any defined function type function. If<br />

the call is illegal or we are unable to tell if it is legal due to non-constant keywords, then we give a warning<br />

and mark the function reference as :notinline to force a full call and cause subsequent phases to ignore the call.<br />

If the call is legal and is to a known function, then we annotate the Combination node with the Function-Info<br />

structure that contains the compiler information for the function.<br />

4.1 Tail sets<br />

#— Probably want to have a GTN-like function result equivalence class mechanism for ICR type inference.<br />

This would be like the return value propagation being done by Propagate-From-Calls, but more powerful, less<br />

hackish, and known to terminate. The ICR equivalence classes could probably be used by GTN, as well.<br />

What we do is have local call analysis eagerly maintain the equivalence classes <strong>of</strong> functions that return the<br />

same way by annotating functions with a Tail-Info structure shared between all functions whose value could<br />

be the value <strong>of</strong> this function. We don’t require that the calls actually be tail-recursive, only that the call deliver<br />

its value to the result continuation. [### Actually now done by ICR-OPTIMIZE-RETURN, which is currently<br />

making ICR optimize mandatory.]<br />

We can then use the Tail-Set during ICR type inference. It would have a type that is the union across all<br />

equivalent functions <strong>of</strong> the types <strong>of</strong> all the uses other than in local calls. This type would be recomputed during<br />

optimization <strong>of</strong> return nodes. When the type changes, we would propagate it to all calls to any <strong>of</strong> the equivalent<br />

functions. How do we know when and how to recompute the type for a tail-set? Recomputation is driven by<br />

type propagation on the result continuation.<br />

This is really special-casing <strong>of</strong> RETURN nodes. The return node has the type which is the union <strong>of</strong> all the<br />

non-call uses <strong>of</strong> the result. The tail-set is found though the lambda. We can then recompute the overall union<br />

by taking the union <strong>of</strong> the type per return node, rather than per-use.<br />

How do result type assertions work? We can’t intersect the assertions across all functions in the equivalence<br />

class, since some <strong>of</strong> the call combinations may not happen (or even be possible). We can intersect the assertion<br />

<strong>of</strong> the result with the derived types for non-call uses.<br />

When we do a tail call, we obviously can’t check that the returned value matches our assertion. Although in<br />

principle, we would like to be able to check all assertions, to preserve system integrity, we only need to check<br />

assertions that we depend on. We can afford to lose some assertion information as long as we entirely lose it,<br />

ignoring it for type inference as well as for type checking.<br />

Things will work out, since the caller will see the tail-info type as the derived type for the call, and will emit<br />

a type check if it needs a stronger result.<br />

A remaining question is whether we should intersect the assertion with per-RETURN derived types from<br />

the very beginning (i.e. before the type check pass). I think the answer is yes. We delay the type check pass so<br />

that we can get our best guess for the derived type before we decide whether a check is necessary. But with the<br />

function return type, we aren’t committing to doing any type check when we intersect with the type assertion;<br />

the need to type check is still determined in the type check pass by examination <strong>of</strong> the result continuation.<br />

14

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!