0.pre7.7:
[sbcl.git] / doc / cmucl / internals / compiler-overview.tex
diff --git a/doc/cmucl/internals/compiler-overview.tex b/doc/cmucl/internals/compiler-overview.tex
deleted file mode 100644 (file)
index 74182cd..0000000
+++ /dev/null
@@ -1,540 +0,0 @@
-\chapter{Compiler Overview} % -*- Dictionary: design -*-
-
-The structure of the compiler may be broadly characterized by describing the
-compilation phases and the data structures that they manipulate.  The steps in
-the compilation are called phases rather than passes since they don't
-necessarily involve a full pass over the code.  The data structure used to
-represent the code at some point is called an {\it intermediate
-representation.}
-
-Two major intermediate representations are used in the compiler:
-\begin{itemize}
-
-\item The Implicit Continuation Representation (ICR) represents the lisp-level
-semantics of the source code during the initial phases.  Partial evaluation and
-semantic analysis are done on this representation.  ICR is roughly equivalent
-to a subset of Common Lisp, but is represented as a flow-graph rather than a
-syntax tree.  Phases which only manipulate ICR comprise the "front end".  It
-would be possible to use a different back end such as one that directly
-generated code for a stack machine.
-
-\item The Virtual Machine Representation (VMR) represents the implementation of
-the source code on a virtual machine.  The virtual machine may vary depending
-on the the target hardware, but VMR is sufficiently stylized that most of the
-phases which manipulate it are portable.
-\end{itemize}
-
-Each phase is briefly described here.  The phases from ``local call analysis''
-to ``constraint propagation'' all interact; for maximum optimization, they
-are generally repeated until nothing new is discovered.  The source files which
-primarily contain each phase are listed after ``Files: ''.
-\begin{description}
-
-\item[ICR conversion]
-Convert the source into ICR, doing macroexpansion and simple source-to-source
-transformation.  All names are resolved at this time, so we don't have to worry
-about name conflicts later on.  Files: {\tt ir1tran, srctran, typetran}
-
-\item[Local call analysis] Find calls to local functions and convert them to
-local calls to the correct entry point, doing keyword parsing, etc.  Recognize
-once-called functions as lets.  Create {\it external entry points} for
-entry-point functions.  Files: {\tt locall}
-
-\item[Find components]
-Find flow graph components and compute depth-first ordering.  Separate
-top-level code from run-time code, and determine which components are top-level
-components.  Files: {\tt dfo}
-
-\item[ICR optimize] A grab-bag of all the non-flow ICR optimizations.  Fold
-constant functions, propagate types and eliminate code that computes unused
-values.  Special-case calls to some known global functions by replacing them
-with a computed function.  Merge blocks and eliminate IF-IFs.  Substitute let
-variables.  Files: {\tt ir1opt, ir1tran, typetran, seqtran, vm/vm-tran}
-
-\item[Type constraint propagation]
-Use global flow analysis to propagate information about lexical variable
-types.   Eliminate unnecessary type checks and tests.  Files: {\tt constraint}
-
-\item[Type check generation]
-Emit explicit ICR code for any necessary type checks that are too complex to be
-easily generated on the fly by the back end.  Files: {\tt checkgen}
-
-\item[Event driven operations]
-Various parts of ICR are incrementally recomputed, either eagerly on
-modification of the ICR, or lazily, when the relevant information is needed.
-\begin{itemize}
-\item Check that type assertions are satisfied, marking places where type
-checks need to be done.
-
-\item Locate let calls.
-
-\item Delete functions and variables with no references
-\end{itemize}
-Files: {\tt ir1util}, {\tt ir1opt}
-
-\item[ICR finalize]
-This phase is run after all components have been compiled.  It scans the
-global variable references, looking for references to undefined variables
-and incompatible function redefinitions.  Files: {\tt ir1final}, {\tt main}.
-
-\item[Environment analysis]
-Determine which distinct environments need to be allocated, and what
-context needed to be closed over by each environment.  We detect non-local
-exits and set closure variables.  We also emit cleanup code as funny
-function calls.  This is the last pure ICR pass.  Files: {\tt envanal}
-
-\item[Global TN allocation (GTN)]
-Iterate over all defined functions, determining calling conventions
-and assigning TNs to local variables.  Files: {\tt gtn}
-
-\item[Local TN allocation (LTN)]
-Use type and policy information to determine which VMR translation to use
-for known functions, and then create TNs for expression evaluation
-temporaries.  We also accumulate some random information needed by VMR
-conversion.  Files: {\tt ltn}
-
-\item[Control analysis]
-Linearize the flow graph in a way that minimizes the number of branches.  The
-block-level structure of the flow graph is basically frozen at this point.
-Files: {\tt control}
-
-\item[Stack analysis]
-Maintain stack discipline for unknown-values continuation in the presence
-of local exits.  Files: {\tt stack}
-
-\item[Entry analysis]
-Collect some back-end information for each externally callable function.
-
-\item[VMR conversion] Convert ICR into VMR by translating nodes into VOPs.
-Emit type checks.  Files: {\tt ir2tran, vmdef}
-
-\item[Copy propagation] Use flow analysis to eliminate unnecessary copying of
-TN values.  Files: {\tt copyprop}
-
-\item[Representation selection]
-Look at all references to each TN to determine which representation has the
-lowest cost.  Emit appropriate move and coerce VOPS for that representation.
-
-\item[Lifetime analysis]
-Do flow analysis to find the set of TNs whose lifetimes 
-overlap with the lifetimes of each TN being packed.  Annotate call VOPs with
-the TNs that need to be saved.  Files: {\tt life}
-
-\item[Pack]
-Find a legal register allocation, attempting to minimize unnecessary moves.
-Files: {\tt pack}
-
-\item[Code generation]
-Call the VOP generators to emit assembly code.  Files: {\tt codegen}
-
-\item[Pipeline reorganization] On some machines, move memory references
-backward in the code so that they can overlap with computation.  On machines
-with delayed branch instructions, locate instructions that can be moved into
-delay slots.  Files: {\tt assem-opt}
-
-\item[Assembly]
-Resolve branches and convert in to object code and fixup information.
-Files: {\tt assembler}
-
-\item[Dumping] Convert the compiled code into an object file or in-core
-function.  Files: {\tt debug-dump}, {\tt dump}, {\tt vm/core}
-
-\end{description}
-
-\chapter{The Implicit Continuation Representation}
-
-The set of special forms recognized is exactly that specified in the Common
-Lisp manual.  Everything that is described as a macro in CLTL is a macro.
-
-Large amounts of syntactic information are thrown away by the conversion to an
-anonymous flow graph representation.  The elimination of names eliminates the
-need to represent most environment manipulation special forms.  The explicit
-representation of control eliminates the need to represent BLOCK and GO, and
-makes flow analysis easy.  The full Common Lisp LAMBDA is implemented with a
-simple fixed-arg lambda, which greatly simplifies later code.
-      
-The elimination of syntactic information eliminates the need for most of the
-"beta transformation" optimizations in Rabbit.  There are no progns, no
-tagbodys and no returns.  There are no "close parens" which get in the way of
-determining which node receives a given value.
-
-In ICR, computation is represented by Nodes.  These are the node types:
-\begin{description}
-\item[if]  Represents all conditionals.
-
-\item[set] Represents a {\tt setq}.
-
-\item[ref] Represents a constant or variable reference.
-
-\item[combination] Represents a normal function call.
-
-\item[MV-combination] Represents a {\tt multiple-value-call}.  This is used to
-implement all multiple value receiving forms except for {\tt
-multiple-value-prog1}, which is implicit.
-
-\item[bind]
-This represents the allocation and initialization of the variables in
-a lambda.
-
-\item[return]
-This collects the return value from a lambda and represents the
-control transfer on return.
-
-\item[entry] Marks the start of a dynamic extent that can have non-local exits
-to it.  Dynamic state can be saved at this point for restoration on re-entry.
-
-\item[exit] Marks a potentially non-local exit.  This node is interposed
-between the non-local uses of a continuation and the {\tt dest} so that code to
-do a non-local exit can be inserted if necessary.
-\end{description}
-
-Some slots are shared between all node types (via defstruct inheritance.)  This
-information held in common between all nodes often makes it possible to avoid
-special-casing nodes on the basis of type.  This shared information is
-primarily concerned with the order of evaluation and destinations and
-properties of results.  This control and value flow is indicated in the node
-primarily by pointing to continuations.
-
-The {\tt continuation} structure represents information sufficiently related
-to the normal notion of a continuation that naming it so seems sensible.
-Basically, a continuation represents a place in the code, or alternatively the
-destination of an expression result and a transfer of control.  These two
-notions are bound together for the same reasons that they are related in the
-standard functional continuation interpretation.
-
-A continuation may be deprived of either or both of its value or control
-significance.  If the value of a continuation is unused due to evaluation for
-effect, then the continuation will have a null {\tt dest}.  If the {\tt next}
-node for a continuation is deleted by some optimization, then {\tt next} will
-be {\tt :none}.
-
-  [\#\#\# Continuation kinds...]
-
-The {\tt 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 \ref{source-paths}.
-
-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.
-
-\f
-\section{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.
-
-What is the relationship between the per-RETURN types and the types in the
-result continuation?  The assertion is exactly the Continuation-Asserted-Type
-(note that the asserted type of result continuations will never change after
-ICR conversion).  The per-RETURN derived type is different than the
-Continuation-Derived-Type, since it is intersected with the asserted type even
-before Type Check runs.  Ignoring the Continuation-Derived-Type probably makes
-life simpler anyway, since this breaks the potential circularity of the
-Tail-Info-Type will affecting the Continuation-Derived-Type, which affects...
-
-When a given return has no non-call uses, we represent this by using
-*empty-type*.  This consistent with the interpretation that a return type of
-NIL means the function can't return.
-
-\f
-\section{Hairy function representation}
-
-Non-fixed-arg functions are represented using Optional-Dispatch.  An
-Optional-Dispatch has an entry-point function for each legal number of
-optionals, and one for when extra args are present.  Each entry point function
-is a simple lambda.  The entry point function for an optional is passed the
-arguments which were actually supplied; the entry point function is expected to
-default any remaining parameters and evaluate the actual function body.
-
-If no supplied-p arg is present, then we can do this fairly easily by having
-each entry point supply its default and call the next entry point, with the
-last entry point containing the body.  If there are supplied-p args, then entry
-point function is replaced with a function that calls the original entry
-function with T's inserted at the position of all the supplied args with
-supplied-p parameters.
-
-We want to be a bit clever about how we handle arguments declared special when
-doing optional defaulting, or we will emit really gross code for special
-optionals.  If we bound the arg specially over the entire entry-point function,
-then the entry point function would be caused to be non-tail-recursive.  What
-we can do is only bind the variable specially around the evaluation of the
-default, and then read the special and store the final value of the special
-into a lexical variable which we then pass as the argument.  In the common case
-where the default is a constant, we don't have to special-bind at all, since
-the computation of the default is not affected by and cannot affect any special
-bindings.
-
-Keyword and rest args are both implemented using a LEXPR-like "more args"
-convention.  The More-Entry takes two arguments in addition to the fixed and
-optional arguments: the argument context and count.  (ARG <context> <n>)
-accesses the N'th additional argument.  Keyword args are implemented directly
-using this mechanism.  Rest args are created by calling %Listify-Rest-Args with
-the context and count.
-
-The More-Entry parses the keyword arguments and passes the values to the main
-function as positional arguments.  If a keyword default is not constant, then
-we pass a supplied-p parameter into the main entry and let it worry about
-defaulting the argument.  Since the main entry accepts keywords in parsed form,
-we can parse keywords at compile time for calls to known functions.  We keep
-around the original parsed lambda-list and related information so that people
-can figure out how to call the main entry.
-
-\f
-\section{ICR representation of non-local exits}
-
-All exits are initially represented by EXIT nodes:
-How about an Exit node:
-    (defstruct (exit (:include node))
-      value)
-The Exit node uses the continuation that is to receive the thrown Value.
-During optimization, if we discover that the Cont's home-lambda is the same is
-the exit node's, then we can delete the Exit node, substituting the Cont for
-all of the Value's uses.
-
-The successor block of an EXIT is the entry block in the entered environment.
-So we use the Exit node to mark the place where exit code is inserted.  During
-environment analysis, we need only insert a single block containing the entry
-point stub.
-
-We ensure that all Exits that aren't for a NLX don't have any Value, so that
-local exits never require any value massaging.
-
-The Entry node marks the beginning of a block or tagbody:
-    (defstruct (entry (:include node))
-      (continuations nil :type list)) 
-
-It contains a list of all the continuations that the body could exit to.  The
-Entry node is used as a marker for the the place to snapshot state, including
-the control stack pointer.  Each lambda has a list of its Entries so
-that environment analysis can figure out which continuations are really being
-closed over.  There is no reason for optimization to delete Entry nodes,
-since they are harmless in the degenerate case: we just emit no code (like a
-no-var let).
-
-
-We represent CATCH using the lexical exit mechanism.  We do a transformation
-like this:
-   (catch 'foo xxx)  ==>
-   (block \#:foo
-     (%catch \#'(lambda () (return-from \#:foo (%unknown-values))) 'foo)
-     (%within-cleanup :catch
-       xxx))
-
-%CATCH just sets up the catch frame which points to the exit function.  %Catch
-is an ordinary function as far as ICR is concerned.  The fact that the catcher
-needs to be cleaned up is expressed by the Cleanup slots in the continuations
-in the body.  %UNKNOWN-VALUES is a dummy function call which represents the
-fact that we don't know what values will be thrown.  
-
-%WITHIN-CLEANUP is a special special form that instantiates its first argument
-as the current cleanup when converting the body.  In reality, the lambda is
-also created by the special special form %ESCAPE-FUNCTION, which gives the
-lambda a special :ESCAPE kind so that the back end knows not to generate any
-code for it.
-
-
-We use a similar hack in Unwind-Protect to represent the fact that the cleanup
-forms can be invoked at arbitrarily random times.
-    (unwind-protect p c)  ==>
-    (flet ((\#:cleanup () c))
-      (block \#:return
-       (multiple-value-bind
-           (\#:next \#:start \#:count)
-           (block \#:unwind
-             (%unwind-protect \#'(lambda (x) (return-from \#:unwind x)))
-             (%within-cleanup :unwind-protect
-               (return-from \#:return p)))
-         (\#:cleanup)
-         (%continue-unwind \#:next \#:start \#:count))))
-
-We use the block \#:unwind to represent the entry to cleanup code in the case
-where we are non-locally unwound.  Calling of the cleanup function in the
-drop-through case (or any local exit) is handled by cleanup generation.  We
-make the cleanup a function so that cleanup generation can add calls at local
-exits from the protected form.  \#:next, \#:start and \#:count are state used in
-the case where we are unwound.  They indicate where to go after doing the
-cleanup and what values are being thrown.  The cleanup encloses only the
-protected form.  As in CATCH, the escape function is specially tagged as
-:ESCAPE.  The cleanup function is tagged as :CLEANUP to inhibit let conversion
-(since references are added in environment analysis.)
-
-Notice that implementing these forms using closures over continuations
-eliminates any need to special-case ICR flow analysis.  Obviously we don't
-really want to make heap-closures here.  In reality these functions are
-special-cased by the back-end according to their KIND.
-
-\f
-\section{Block compilation}
-
-One of the properties of ICR is that supports "block compilation" by allowing
-arbitrarily large amounts of code to be converted at once, with actual
-compilation of the code being done at will.
-
-
-In order to preserve the normal semantics we must recognize that proclamations
-(possibly implicit) are scoped.  A proclamation is in effect only from the time
-of appearance of the proclamation to the time it is contradicted.  The current
-global environment at the end of a block is not necessarily the correct global
-environment for compilation of all the code within the block.  We solve this
-problem by closing over the relevant information in the ICR at the time it is
-converted.  For example, each functional variable reference is marked as
-inline, notinline or don't care.  Similarly, each node contains a structure
-known as a Cookie which contains the appropriate settings of the compiler
-policy switches.
-
-We actually convert each form in the file separately, creating a separate
-"initial component" for each one.  Later on, these components are merged as
-needed.  The main reason for doing this is to cause EVAL-WHEN processing to be
-interleaved with reading. 
-
-\f
-\section{Entry points}
-
-\#|
-
-Since we need to evaluate potentially arbitrary code in the XEP argument forms
-(for type checking), we can't leave the arguments in the wired passing
-locations.  Instead, it seems better to give the XEP max-args fixed arguments,
-with the passing locations being the true passing locations.  Instead of using
-%XEP-ARG, we reference the appropriate variable.
-
-Also, it might be a good idea to do argument count checking and dispatching
-with explicit conditional code in the XEP.  This would simplify both the code
-that creates the XEP and the VMR conversion of XEPs.  Also, argument count
-dispatching would automatically benefit from any cleverness in compilation of
-case-like forms (jump tables, etc).  On the downside, this would push some
-assumptions about how arg dispatching is done into ICR.  But then we are
-currently violating abstraction at least as badly in VMR conversion, which is
-also supposed to be implementation independent.
-|\#
-
-As a side-effect of finding which references to known functions can be
-converted to local calls, we find any references that cannot be converted.
-References that cannot be converted to a local call must evaluate to a
-"function object" (or function-entry) that can be called using the full call
-convention.  A function that can be called from outside the component is called
-an "entry-point".
-
-Lots of stuff that happens at compile-time with local function calls must be
-done at run-time when an entry-point is called.
-
-It is desirable for optimization and other purposes if all the calls to every
-function were directly present in ICR as local calls.  We cannot directly do
-this with entry-point functions, since we don't know where and how the
-entry-point will be called until run-time.
-
-What we do is represent all the calls possible from outside the component by
-local calls within the component.  For each entry-point function, we create a
-corresponding lambda called the external entry point or XEP.  This is a
-function which takes the number of arguments passed as the first argument,
-followed by arguments corresponding to each required or optional argument.
-
-If an optional argument is unsupplied, the value passed into the XEP is
-undefined.  The XEP is responsible for doing argument count checking and
-dispatching.  
-
-In the case of a fixed-arg lambda, we emit a call to the %VERIFY-ARGUMENT-COUNT
-funny function (conditional on policy), then call the real function on the
-passed arguments.  Even in this simple case, we benefit several ways from
-having a separate XEP:
- -- The argument count checking is factored out, and only needs to be done in
-    full calls.
- -- Argument type checking happens automatically as a consequence of passing
-    the XEP arguments in a local call to the real function.  This type checking
-    is also only done in full calls.
- -- The real function may use a non-standard calling convention for the benefit
-    of recursive or block-compiled calls.  The XEP converts arguments/return
-    values to/from the standard convention.  This also requires little
-    special-casing of XEPs.
-
-If the function has variable argument count (represented by an
-OPTIONAL-DISPATCH), then the XEP contains a COND which dispatches off of the
-argument count, calling the appropriate entry-point function (which then does
-defaulting).  If there is a more entry (for keyword or rest args), then the XEP
-obtains the more arg context and count by calling the %MORE-ARG-CONTEXT funny
-function.
-
-All non-local-call references to functions are replaced with references to the
-corresponding XEP.  ICR optimization may discover a local call that was
-previously a non-local reference.  When we delete the reference to the XEP, we
-may find that it has no references.  In this case, we can delete the XEP,
-causing the function to no longer be an entry-point.
-
-\f
\ No newline at end of file