4.2BSD/usr/lisp/franz.n

."  franz.n				-[Thu Jun 17 11:01:27 1982 by jkf]-
." this file will contain a description of the franz lisp system.
." sort of a systems manual.
.de Fr
F\s-2RANZ\s0 L\s-2ISP\s0
..
.tp
.(l C
.sz +2
\fBThe Franz Lisp System\fP
.sz -2
.sp 2v
\fIby
\fIJohn K. Foderaro\fP
.)l
.sp 3v
.tl ''Abstract''
.br
This document describes the 
.Fr
system written at The University of California 
at Berkeley.
Included are descriptions of the memory management, interpreter
and compiler, the conventions used within the C coded kernel
and those within the compiled code.
This does not duplicate the information found in The Franz Lisp Manual.
.++ C '\s-2Draft\s+2'\fBThe\ Franz\ Lisp\ System\fP'%'
.+c Characteristics\ of\ \F\s-2RANZ\ \s0L\s-2ISP\s0
.ls 1
.pp
There is no formal standard for lisp systems, thus each lisp
system is almost guaranteed to be different from any other lisp system.
In this section we will examine the design decisions which most often
characterize a lisp system.
This
focus does  not imply that all other parts of the language are generally
agreed upon.
In fact, there is nothing sacred to lisp system designers.
For example, one usually identifies the symbol 
.i nil 
with the empty list,
and one usually thinks of lisp as a dynamically scoped language.
Both of these conventions are are not true in the lisp dialect 
.i NIL
currently being written at MIT.
As another example, one imagines that a lisp system must 
use  garbage collection to reclaim storage.
The lisp dialect
.i ZetaLisp
that is running on Lisp Machines doesn't
normally use a garbage collector
because of the way in which it allocates its address space.
It is faster to reboot the machine than to do a garbage collection.
In most lisp dialects the lisp expressions are written in 
parenthesised form.  In 
.i Portable
.i Standard
.i Lisp
(PSL) at the University of Utah, programs are written 
primarily 
in an Algol-like
syntax.
.pp
Thus some of the discussion to follow indicates not so much 
.i unique
charateristics of 
.Fr
but instead, how decisions were made.
.sh 2 Scoping\ and\ Binding
.pp
The 
.Fr
interpreter
uses 
.i dynamic 
.i scoping , 
that is, after A calls B, B can access all of the
variables that  A allocated
as well
as those that A can access, provided B doesn't allocate variables of the same
names.
There are two popular ways of implementing dynamic scoping: 
.i deep
.i binding
and 
.i shallow
.i binding .
Note that we will use the terms variable allocation and lambda
binding interchangeably in this report.
The former term is less language-specific than the latter.
.sh +1 Deep\ Binding
.pp
In a deep binding implementation, when a variable is  allocated,
the name of the variable and a space for its value are pushed on the
top of a stack.
When a program asks for the value of a variable, 
the lisp system must search down the stack for the first occurrence of
the variable.
This system offers great flexibility for the 
programmer since  the state of his program 
can be described by the contents of the stack.
It is thus possible to save the context of a program by just saving
the stack or in some cases just a pointer to
a place in  the stack.
The problem with deep binding is that it is time-consuming to search
down the stack for the value of a variable, and, 
as a result, most systems 
modify the deep binding model by giving variables a global value
cell and allowing programs to set and access the global cell.
Some implementations of Interlisp use deep binding.
.sh +0 Shallow\ Binding
.pp
In a shallow binding implementation, each variable has a global value cell
which contains the current value of the variable.
When a variable is allocated inside a program, its name 
and old value are pushed on a stack called a
.i bindstack .
The variable is then given a new value. 
Throughout the lifetime of the allocation, the current value of the
variable can be found in the global value cell associated with the variable.
When leaving the context of the variable's allocation, the old value
is restored from the 
.i bindstack .
A shallow binding scheme makes it much cheaper to access 
the values of variables, however it is much more difficult to 
save the state and to restore it. 
.pp
.Fr
and most other lisps which are not derived from Interlisp
use shallow binding.
Some versions of Interlisp use shallow binding.
.sh -1 Lisp\ Compiler
.pp
Dynamic scoping often is not necessary and it is never cheap, even
in a shallow binding implementation.
Thus the 
.Fr  
compiler prefers to do lexical scoping rather than dynamic
scoping.
If the user does not specify otherwise,  when a 
function is compiled, all variables 
allocated will be 
put on a local stack and will 
.i not 
be accessible by any
function that this function calls.
This
convention results in faster code being generated by
the compiler, but if the
user is not careful, it can result in compiled and interpreted code
working differently.
In some of the new lisp designs, such as 
.i NIL 
and
.i Common
.i Lisp
the semantics of compiled and interpreted code have been
unified by
transferring the compiler semantics (lexical scoping) to the interpreter.
This is a rather large step
if dynamic scoping is no
longer an option, and it is not clear whether the resulting
language should be called 'lisp'.
.sh +0 Data\ Types
.pp
A complete list of data types in 
.Fr 
can be found in the first chapter of the 
.Fr
Manual.
The most important ones are described below.
.pp
Lisp is a list processing language and the basic data type is the list
cell. 
A list cell also goes by the name of cons cell, pair, or dotted pair (dtpr).
It contains pointers to two lisp objects, and these pointers can
be accessed via the 
.i car
and
.i cdr 
functions.
Each pointer requires four (8-bit) bytes and thus the list cell is 
eight bytes large.
The cdr pointer is stored first since this makes it easier 
to 'cdr down a list' using the addressing modes of the VAX.
.pp
The symbol cell is used to implement variables.  It contains  a pointer
to a string that is the print name, a cell to store a value, a cell to
store the function definition, a cell to store a pointer to  the property
list and one pointer that the list system uses if it stores
the symbol in the oblist.
.sh +0 Memory\ Management
.pp
A lisp system must be able to determine the type of an
object at run time.
The method used to determine the type influences the
way storage is managed and garbage collection is done.
Next, we will look at three possible places
to store the type information.
.sh +1 Typed\ data
.pp
The type of the data object could be placed in the object itself, much
as Pascal stores the type of a variant record in the record itself.
This could result is a large amount of storage being used to store
type information.
No lisp system that we know of uses this method exclusively.
.sh +0 Typed\ pointers
.pp
Every reference to a lisp object could contain the type of the object
referenced.
This is a good idea in a machine like an IBM 370 where only
part of  machine word is used by the addressing hardware.
Lisps that use typed pointers generally use a
.i heap
memory management scheme and a 
.i copying 
garbage collector.
In a heap scheme, 
all memory is divided by a pointer (called the
heap pointer) separating lisp
data and free space.  
When a new lisp object is needed, it is formed at the
dividing line and then  
the heap pointer is incremented
past the new object.
Garbage collection is done by maintaining two separate spaces for lisp
data, only one of which is used at any one time.
When one fills up, all active lisp objects are copied to the 
other space, leaving the first space totally free. 
Subsequent allocations are done from the second space, until it fills up,
at which point 
all active data in the second space is copied to the first space.
The advantage of the copying garbage collector is that the data space
is compacted, which will improve virtual memory behavior.
The disadvantage is that
half the data space is always unused.
.pp
PSL on a PDP-10 uses this scheme, as does Lisp 370 on 
an IBM 370.
PSL  and NIL on the VAX will also use this scheme.  
Since the VAX hardware 
uses the entire word for address 
calculation, 
PSL and NIL 
must mask off the type bits
of a pointer before using it to reference memory.
.sh +0 Typed\ pages
.pp
The final alternative is to allocate data objects by pages rather than
individually.
Each page contains only one type of object and there is a table that 
keeps track of the type of data object in each page.
The address of an object tells which page the object is on and 
the page number tells which type the object is.
Since a whole page of objects is allocated when only one 
object is necessary,
the rest of the objects in the page are put on a free list.
This method of allocation is sometimes referred to as
.i bibop
for BIg Bag Of Pages.
The garbage collector for bibop is usually
a traditional
.i mark
and
.i sweep
algorithm.
All active data objects are marked  and
then each page is swept linearly with
the free cells being put on the free list and the used cells 
left alone.
The advantage of mark and sweep over a copying garbage collector is that
the mark phase is cheaper because data objects do not have to be copied.
Also, all of memory can be used since there is no requirement for two spaces.
The disadvantage is that a sweep phase is required in a mark and sweep
but one is not required in a copying garbage collector.
The sweep phase is expensive because it has to alter most data pages 
while building a free list.
This operation 
can be expensive in a virtual memory environment.  
Alternatives to the sweep phase have been proposed in
[Foderaro+Fateman Characterization of Vax Macsyma].
.pp
.Fr
uses a bibop memory allocator and a mark and sweep garbage collector,
as does Maclisp (on a PDP-10).
The reason that
.Fr 
uses bibop is primarily due to the VAX architecture,
which makes typed pointers
expensive to implement.
Also, typed pointers would make it difficult to pass lisp values
to  programs written in other languages, some of which may not have
the ability to extract the address of a lisp object from a typed pointer.
.sh -1 Construction
.pp
The
.Fr
system is built by adding lisp code to a C-coded kernel.
The C-coded kernel is a working lisp interpreter, although it has few of
the functions a lisp system needs.
The lisp code provides most of the functions, the top level and error
handlers.
The lisp compiler is also written in lisp, 
but is usually 
run as a separate process.
.sh +0 Unique\ features
.pp
.Fr
can dynamically load and execute functions defined in the other languages
which run on the VAX.
It uses two different dynamic loaders.
One is part of the lisp system and is used for lisp only, it is called 
.i fasl
(for fast loader).
The other is the Unix loader, ld, which is used for loading in 
C, Fortran or Pascal code.
Loading code with fasl
is much faster than with ld
since fasl benefits from 
the simplicity of a lisp object file.
.+c Data\ Structures
.sh 0 _
.nr $1 \n(ch
.pp
In this chapter we shall examine the data structures which are most 
important to the 
.Fr
system.
First, though we will see how the lisp system uses the address space.
.sh 2 Physical\ layout
.pp
As a Unix process, lisp has three address regions: text, data and stack.
Text begins at location 0 and continues up to 0x3b73c (0x means hex number).
The data segment begins on the next page boundary and continues
up to a limit set by the configuration parameters (currently 0x2fd000).
Lisp does not begin running with such a large data segment,  rather it grows
when necessary.
The stack segment begins at address 0x7fffffff and grows downward to a 
maximum size of one-half megabyte.
.pp
The text segment stores the instructions for the C kernel as well as read-only
data.
The read-only data for lisp are 
the symbol nil, the i/o ports, and the small integer
table.
The symbol nil is stored at location 0 which makes it very easy to test
whether a value is nil.
The problem with storing nil in read-only space is that a special case
must be made for nil's property list, which is the only thing in the
nil symbol that the user is permitted to alter.
.pp
The fixnums -1024 through 1023 are stored sequentially, with 0 being
stored at 0x1400.
.Fr
doesn't have any 'immediate' lisp data, that is data whose value is 
stored as part of the reference to the data. 
But, by storing some of the fixnums in a known place, we can achieve
some of the benefits of immediate data: 
A program  can use 
.i eq
as a test for a fixnum in the range -1024 to 1023.
In the majority of cases, when asked
to allocate a fixnum, the system can  return a pointer into this table
and bypass the memory allocator.
.sh +0 Typetable
.pp
.Fr
uses the typed pages (or bibop)
method of type determination.
The 
.i typetable
is an array of bytes (
.i chars 
in  C lingo).
This table describes the type of all pages, from page 0 where nil is stored,
up to the end of data space.
Thus there are many entries that describe the types of the pages which
make up the C kernel.
In order to reduce the wasted space in the typetable,
we could have made the typetable begin typing pages at the start of data
space and  make a special case of the pages that contain nil and
the small integer table.
However, the
effect of this change would be that type checks 
(which are done in-line in compiled code) would
be longer and slower.  Since type checking happens quite
frequently, we felt it was better to waste the space in the
typetable in order to save execution time  and instruction space.
.pp
Each page on a VAX is 512 bytes, and thus to find the type of an 
object given the address of it, 
it is only necessary to shift the address right 9 bits
and index the typetable array offset by one.
The offset by one is required because the value -4, which is an illegal
address, is used as a sentinel value to indicate an illegal value.
Thus when we take the type of -4 we will be indexing the table by -1
and we want to retrieve the first byte in the table (which contains
the value UNBOUND).
The C macro which retrieves the type is this (from file h/global.h):
.(b I

#define	TYPE(a1)	((typetable+1)[(int)(a1) >> 9])

.)b
This is code generated by the lisp compiler to check if the type
code of an argument (stored at 0(r10)) is a symbol (which is type
code 1):
.(b I

ashl	$-9,0(r10),r0
cmpb	_typetable+1[r0],$1

.)b
.pp
The type codes which are stored in the typetable are these:
.ts 2i 4i 6i
.(b I
UNBO     -1\tSTRNG     0\tATOM     1\tINT       2
DTPR      3\tDOUB      4\tBCD      5\tPORT      6
ARRAY     7\tOTHER     8\tSDOT     9\tVALUE    10
HUNK2    11\tHUNK4    12\tHUNK8   13\tHUNK16   14
HUNK32   15\tHUNK64   16\tHUNK128 17
.)b
The names given above are the C kernel internal names.
ATOM is symbol, INT is fixnum, DTPR is list cell, 
DOUB is flonum, BCD is binary,
SDOT is bignum and all the HUNKn types are just known as hunk to
the user.
.sh +0 Stacks
.pp
.Fr
uses three stacks: the 
.i C 
.i runtime 
stack, the 
.i namestack
and the
.i bindstack .
The C runtime stack is the stack part of the address space 
and the other two stacks are stored in the data space.
.sh +1 C\ runtime\ stack
The C-coded kernel uses this
stack in the same way as a typical  C program
use the stack:
storing return addresses
and non-lisp-object arguments to  subroutines,
saving registers,
and storing  local variables within a function.
The lisp system stores 
.i catch
.i frames
on this stack as well (to be described later).
.pp
The lisp system assumes that there are no lisp data on the stack and
thus  the use of this stack 
by a program is unrestricted.
As will be discussed later on, the 
.b calls
and 
.b ret
instructions are used for most subroutine calls and returns.
These instructions expect the stack to look a certain way.
.sh +0 Namestack
.pp
The namestack contains only lisp data.
It is used to pass arguments to functions and to  hold
local (lexically scoped) data within lisp functions.
It is also used as a temporary storage spot for lisp data
which must be protected from garbage collection.
.pp
A slight digression on the garbage collector:
The person who writes code for the lisp system must always be aware of
his greatest enemy: the garbage collector.  
Whenever a function is called that could possibly allocate more lisp
data, one must assume that it 
when it attempts to allocate space,  the garbage collector
will be invoked and that it will take away everything that isn't protected
from garbage collection.
The objects that are protected from garbage collection are: the
namestack, the bindstack, the oblist (table of interned symbols),
and the compiler literals.  Objects that are only 
referred to by values in registers or
the C runtime stack will not be seen by the mark phase of the
garbage collector and will be swept up during the sweep phase.
.pp
Back to the namestack:
The first free element on the namestack is pointed to by the
C variable 
.i np .
This variable is always stored in the VAX register r6.
Another pointer into the namestack is the C variable
.i lbot .
It is always stored in VAX register r7.
Its use will be described in the section on calling conventions.
.sh +0 Bindstack
.pp
The bindstack is a stack of lisp object pairs: symbol and
value.
It is used to implement shallow binding.
When a symbol is lambda-bound, the symbol and its current value are put
on this stack.
Then the symbol can be given a new value.
When the context of the lambda binding is over, the symbol and value pair
are popped from the stack and the symbol is given its old value.
The C variable
.i bnp
points to the first free entry on the bindstack.
In the C code, the following macros lambda-bind a symbol to a value
and restore the old value:
.(b
#define PUSHDOWN(atom,value)\e
	{bnp->atm=(atom);\e
         bnp++->val=(atom)->a.clb;\e
 	 (atom)->a.clb=value;\e
	if(bnp>bnplim) binderr();}

#define POP\e
	{--bnp;  bnp->atm->a.clb=bnp->val;}
.)b
.sh -1 Bitmap
.pp
The bitmap is used in garbage collection to hold
the mark bits for the mark and sweep garbage collector.
As its names implies, it is viewed as a collection of bits.
The minimum size of a lisp data object is 4 bytes, thus
there are 128 of those on a VAX page of 512 bytes.
Since there are 8 bits in a byte, it takes 16 bytes to obtain 128 bits.
Therefore the size of the bitmap in bytes is 16 
times the maximum number of
pages.
Like the typetable, the bitmap  keeps track of marks from the
bottom of memory, not the bottom of data space.
The bitmap and the typetable are static structures.
It is their size, which is determined when the C kernel is built,
which determines the size to which the data segment can grow.
.sh +0 Transfer\ Tables
.pp
Transfer tables are used by compiled lisp code as a transfer vector to
other functions.
A transfer table consists of pairs of entries: symbol and pointer to
function.
When a compiled lisp function  calls another (non-local) function, it
calls indirectly through an entry in the transfer table.
Depending on the setting of certain status variables, the call may
bring control into a function linkage routine or directly to
the function desired (if that function is a compiled lisp or C function).
.pp
Transfer tables serve a number of useful purposes.
.np
They allow compiled code to call interpreted code
or compiled code using the same calling sequence.
.np
They allow the selection of which function to call to be determined
at runtime based on the name of the function.
In most other languages, this selection process is done at either
compile or load time.
.np
They allow the user to run in a debugging mode where all calls
from compiled code go through the interpreter.
Once control reaches the interpreter, it is easier to debug.
.np
They allow the user to run in a production mode, where all calls
from compiled to other compiled code are done without function
lookup overhead.
.np
They allow the user to switch between debugging and production modes
at runtime with one function call.
.pp
Transfer tables will be described further  in the section on 
the lisp compiler.
.sh +0 Catch\ frames
.pp
Lisp has a number of forms of non-local transfers.
Among them are 
.i throw ,
.i error ,
.i return 
and
.i go .
If a program is willing to accept a non-local transfer, it puts a 
.i catch
.i frame
on the stack which describes which type of transfer it 
accepts.
The catch frame describes the current state of the registers,
the variables np, lbot, and bnp, and also contains entries that describe
what kinds of non-local transfers the function will accept.
After creating a catch frame, the program  goes on to evaluate forms.
Should the evaluation of one of those forms result in a non-local
transfer to the catch frame that this program has set up, the 
system will restore the namestack and bindstack to the way
they were when the program put the catch frame on the stack (by using
np and bnp) and will return control to the program (setting 
the variable retval to describe why it returned).
This is described further in the 
section on interpreter conventions.
.pp
The C variable 
.i errp
points to the most recent catch frame, and then each catch frame points
to the previous catch frame.
.sh +0 oblist
.pp
Normally when symbols are created they are
.i interned ,
that is they are put in a hash table called an oblist (or obarray).
The purpose of the oblist is to insure that if you type a
symbol's name to the reader, you will always get  the  same symbol.
Also it protects the symbol from garbage collection.
The oblist is simply a hash table with buckets, with a hash link being
part of the symbol data structure.
Currently the hash table is not accessible to a lisp program, but with
a little work it could be.
.+c Interpreter
.sh 0 _
.nr $1 \n(ch
.pp
The interpreter is composed of these parts:
.ip \fIcore:\fP
The functions eval, apply and funcall.
.ip \fIstack\ management:\fP
The code to create catch frames and handle non-local transfers.
.ip \fImemory\ management:\fP
The code to allocate and garbage collect memory.
.ip \fIthe\ functions:\fP
The user callable functions that expect lisp arguments and return
lisp values.
.pp
In the above list, the first three are written mainly in C (with a little
assembler) and the last is written mainly in Lisp with a little 
bit in C
and a very small amount in assembler.
.pp
The core functions are the center of activity  in the interpreter.
The 
.i eval 
function given a lisp expression  to evaluate.  
It must determine if it is a simple expression such as a symbol or number
whose value eval can determine immediately, or if it is an function
calling expression.
If the form is a function call, eval must determine if the arguments should
be evaluated and if so eval must recursively call itself to evaluate the
arguments and then stack them.
If the function called is to be interpreted, eval must lambda-bind the
formal variables of the function to the arguments just stacked.
If the function being called is compiled, eval just calls the function and
lets the function do the lambda binding if it wants to.
.pp
The 
.i apply 
function is passed two lisp objects: 
a function to call (either a symbol or a functional object)
and a list of arguments already evaluated.
It will do lambda binding if the function to call is to
be interpreted.
.pp
The 
.i funcall
function is passed any number of lisp objects,
the first of which is the function to call, and the rest are the
arguments to the function 
which have already been evaluated and stacked.
Funcall will  do lambda binding if the function to call is
to be interpreted.
.pp
When compiled lisp code calls a function 
which must be interpreted, it enters the interpreter through the
funcall function.
The interpreter may go to compiled code through either eval, apply or
funcall, though most often it goes through eval.
.sh 2 Conventions
.pp
These are the conventions used in interpreted and compiled code.
.sh +1 C\ conventions
.pp
Our conventions are extensions of the C calling conventions, so we
describe here the conventions for C.
The VAX has 16 general
purpose registers.
Registers r12 through r15 are reserved
for use by the hardware because we use the
.i calls
subroutine call.
Registers r0-r5 can be used by a program without saving them first.
The result of a function call is returned in r0.
Registers r11-r6 are allocated  (from high to low)
by the C compiler when a 
.i register
declaration is made in the C code.
Registers r11-r6 must be
saved upon entry and restored upon exit if they are used within a function.
On the VAX it is very easy to preserve registers
since it is done automatically
by the hardware if the
.i calls
(or 
.i callg ) 
instruction is used.
The first short word (two bytes) of a subroutine is a 
register save mask which
tells which registers should be saved (on the C runtime stack) upon
entry and restored when a 
.i ret
instruction is done.
.sh +0 np\ and\ lbot
.pp
Earlier we  mentioned that the C variables np and lbot are 
always stored in
registers r6 and r7.
It is very difficult to globally assign a variable to a register
in the C language (no external register declarations
are permitted)
and thus we have to filter
the output of the  C compiler and convert all occurrences of 'np' to 'r6'
and all occurrences of 'lbot' to 'r7'.
This is only half the job, though.
We also must modify  the register save masks for those routines which 
alter the value of np or lbot to insure that those registers get 
saved and restored upon function entry and exit.
This is done in the C code by putting 
.(b C
Savestack(n)
.)b
at the beginning of a routine which will alter np or lbot and which
also allocates n register variables.
Also in that routine, before the function returns, we put 
.(b C
Restorestack()
.)b
This is not really necessary in the VAX, but it is there for other 
machine implementations which don't have a 
.i ret
function which restores registers.
The calls to Savestack are recognized by a filter which processes
the output
of the C compiler and fixes up 
the save masks.
.sh +0 Function\ calling
.pp
The following text describes what the conventions are for 
calling
compiled lisp functions, whether they were written in lisp or C.
We look at it from the viewpoint of the called function.
.pp
Upon entry
to the compiled functon,
lbot points to the first argument and np points to the
first free spot on the namestack.
If np equals lbot then there are no arguments.
Recall that np will be in r6 and lbot in r7.
The function is free to alter registers r0 through r5 and should return
the result in r0.
The function may alter registers r6 through r11 as long as their 
original values
are restored when the function returns.
The value of np should always 
point to the first free entry in the namestack.
This is all that is required.
The extra conventions followed by 
the lisp compiler 
in the code it generates are described in the next chapter.
.sh +0 Catch\ frames
.pp
A catch frame saves the state of execution that a program
might  want to return to at some later time.
A catch frame  is stored on the C runtime stack, with the most recent 
frame pointed to by the C global variable errp.
The information saved is 
.ip \fIargs\fP
- one or two optional arguments. The lisp compiler always 
stacks two 
arguments since it must know exactly how large the frame is.
.ip \fIclass\fP
- the class describes what type of frame this is (described below).
.ip \fIretaddr\fP
- address to return to if returning from this frame
.ip \fIolderrp\fP
- pointer to next older catch frame on the stack
.ip \fIbnp\fP
- value of bnp (bindstack pointer) when the frame was created
.ip \fInp\fP
- value of np when the frame was created
.ip \fIlbot\fP
- value of lbot when the frame was created. 
.ip \fIsystem\ dependent\fP
- the rest of the information stacked depends on the particular machine.
In the case of the VAX, registers r13 through r8 are stacked. (r14 and
r15 are the stack pointer and program counter; they are not saved since
they can be calculated from the other information).
.pp
The information in a catch frame is put on the C runtime stack
in the precise order given above, and the variable errp points not
at the beginning of the entire frame, but to the lbot entry.
Thus errp\ +\ 4 points to np.
The classes of frames are described below.
Each class is defined as a constant in the C code (h/frame.h) whose value
is a small integer. 
.ip F_PROG\ [1]
- takes no arguments.  This frame marks the beginning of a piece of
code which can accept a 
.i return
or a 
.i go .
The interpreter uses this to implement
.i prog
and 
.i do
and for its primative break loop.
The lisp compiler does not use catch frames for progs since it only
permits 
.i returns
and
.i gos
to occur within
.i progs 
or 
.i dos
and thus it can determine how to do the non-local transfer
at compile time.
.ip F_CATCH\ [2]
- this takes one or two arguments and is used to implement both
.i catch
and 
.i errset .
In both cases 
the first argument is the tag which is being caught,
which in the case of an 
.i errset
is symbol ER%all.
An 
.i errset
also
supplies a second argument which is non nil if the error message
should be printed.
.ip F_RESET\ [3]
- in the C kernel, the reset function 
is implemented as a non local transfer to a F_RESET catchframe.
When the lisp system is built, the reset function is redefined to
do a 
.i throw.
Thus this type of catch frame is rarely used.
.ip F_EVAL\ [4]
- this has one argument, the form being evaluated. 
Since stacking this
on every eval would be expensive,
this type of catch frame is only stacked if a \fI(*rset\ t)\fP
has been done and if the value of the symbol 
.i *rset
is non nil.
The form being evaluated is stacked  so that 
the necessary information for the
.i evalframe
function is available and so that 
.i freturn
can return from the context of any pending evaluation.
.ip F_FUNCALL\ [5]
- this is much like F_EVAL, 
except the one argument it takes is the
name of the function to call.  
It has the same restrictions on when it is stacked as  F_EVAL
and for the same reasons.
.pp
In C, a frame is pushed on the stack with Pushframe, with a call
of one of these forms:
.(b
errp = Pushframe(class);
errp = Pushframe(class,arg1);
errp = Pushframe(class,arg1,arg2);
.)b
After the call the C variable 
.i retval
contains the value which describes why this function returned.
You must remember that it is possible for this one function call to
return more than once! 
The reasons it can return are these (from h/frame.h):
.ip C_INITIAL\ [0]
This is the initial call to set up the frame.
.ip C_GO\ [1]
This will only happen for F_PROG frames. 
In this case, 
the C variable lispretval contains a lisp symbol which is the tag
to which control should be transferred.
If the tag cannot be found in this prog or do body, the
tag should be again thrown up the stack to a next higher prog or do.
.ip C_RET\ [2]
This will only happen for F_PROG frames.
In this case, lispretval contains the value to return from this prog.
.ip C_THROW\ [3]
This will only happen for F_CATCH frames.
In this case lispretval contains the value to return from this catch.
.ip C_RESET\ [4]
This will only happen for F_RESET frames.
It simply means that a reset is being done.
.ip C_FRETURN\ [5]
This will only happen for F_EVAL and F_FRETURN frames.
The variable lispretval contains the value to return from this
call to 
.i eval
or
.i funcall .
.pp
The call to Pushframe is turned into a simple subroutine call (using
the 
.i jsb
instruction instead of the
.i calls )
by the filters which alter the code coming out of the C compiler.
Thus it may be useful to see what stacking a catch frame really looks
like.  
Here is what the lisp compiler generates 
to stack the frame for \fI(*catch\ 'tag\ x)\fP
.(b
movl	0(r8),r0	#move 'tag to r0
pushl	$0		# dummy argument
pushl	r0		# put tag argument on C runtime stack
pushl	$2		# push F_CATCH
jsb	_qpushframe	# call Pushframe
movl	r0,_errp	# move result into errp
.)b
.pp
Every function which does a Pushframe() must also do a Popframe()
before it returns to its caller.
This simply removes the frame from the stack.
In C it is written:
.br
.tl ''errp = Popframe()''
in the code generated by the lisp compiler it looks like:
.(b
movl	_errp,r1	# put current errp in r1
movl	12(r1),_errp	# put previous errp in errp
addl3	$32,r1,sp	# pop frame from stack
.)b
.pp
Non-local transfers are done with the Inonlocalgo
function. This function always takes three arguments. 
The
first is the return type (one of the types mentioned
above which begin with 'C_'). It will be assigned to retval.
The second argument is the value to be assigned to lispretval,
except in the case of a C_THROW, where the second argument
is the tag to throw to and the third argument is the value
to assign to lispretval.
This function never returns. 
If it doesn't find a catch frame
which matches what it is looking for, it signals an error.
The function is called with 
.i calls
and the arguments are stacked on the C runtime stack, not the 
namestack.
.+c Liszt:\ The\ Lisp\ Compiler
.sh 0 _
.nr $1 \n(ch
.pp
The purpose of compiling a lisp function is to create a representation of 
the function which can be evaluated in less time and perhaps take
up less space.
There are two approaches to lisp compilers.
One is to convert the functions to a compact form, often called 
.i bytecodes
which can be rapidly interpreted.
Each bytecode represents a primitive operation in the lisp system.
This approach is simple to implement but there is an time penalty
in using an  interpreter.
The other approach is to compiled to  machine code.
In general, this is not as portable as the bytecode  approach but the result
generally runs faster.
There are two ways of compiling to  machine code. 
One is to place arguments to functions in registers. 
If there are more arguments than registers, the arguments are put on
a stack and a special type of call is made.
This method is used in Maclisp.
The other method is to assume a stack model, in which
arguments to a function are placed on a stack.
This is what the 
.Fr
compiler Liszt does.
The stack model made it much easier to write parts of the lisp
system in the C langauge.
It also simplifies the garbage collector since the mark phase doesn't
have to locate and peruse all saved registers looking for lisp data.
.sh 2 File\ Compiler
.pp
When a file of lisp expressions is loaded,
the 
.i load 
function repeatedly reads and evaluates the forms in the
file.
Some of these evaluations may result in functions being defined, 
and others may use the functions just defined or previously defined.
The job of the lisp compiler is to create an object file, which,
when read in 
by 
.i fasl,
acts just like it was 
.i load ed
in, except when a function is defined it is defined in machine
code, not as a lisp expression.
This is quite a bit different from what compilers do in other languages
and it is done this way to make it easier to 
switch between compiled and
interpreted code.
.sh +0 Differences\ between\ compiled\ and\ interpreted
.pp
There are some differences, though, between compiled and interpreted code.
Local variables in compiled code are lexically scoped (put on the
namestack and inaccessible to other functions) unless the variable
has been declared 
.i special.
The declaration:
.(b
\fI(declare (special x y))\fP
.)b
declares both x and y to be special from this point in the file on.
The declaration 
.(b
\fI(declare (specials t))\fP
.)b
declares all variables to be special.
.pp
Lisp has a very powerful macro definition system.
The compiler will macro expand all it can, whereas the interpreter
expands macros when necessary but never replaces a macro call with
its expansion.
Thus if you redefine a macro, the compiled code that uses it will
still work the same way, but the interpreted code will use the 
new definition. 
Also, when compiling a file, macro definitions must be
done before any call on the macro  is encountered during compiling.
In the interpreter, 
macros can be defined or redefined anytime up until
they are used.
Thus the interpreter is far freer about macro definitions than 
the compiler.
This could cause programs which work interpreted to
fail compiled.
.sh +0 Top\ level\ algorithm
.pp
The top level algorithm of the compiler is simply this: 
.np 
read a lisp expression from the input file
.np 
macro expand the top level of the form as much as possible
.np 
if the form is a function definition (a list beginning with
the symbol 'def')
then compile it.
.np 
if the form is not a function definition then put it on a list of 
forms that will be evaluated when the file is 
.i fasl ed
in.
.np
if not at end of file, go to step 1.
.pp
In reality, step 3 is a bit more complex.
If the definition is of a macro, then the form will be evaluated
immediately, thus adding the macro definition to the compiler's 
environment.
If the lisp variable 
.i macros 
is t then the macro will also be compiled.
There are also some forms like \fI(progn\ 'compile\ ...)\fP
and \fI(eval-when\ ()\ )\fP which determine when the 
forms get compiled and evaluated.  
See the lisp manual for details.
.sh +0 Expression\ Compilation
.pp
Just as the interpreter is centered around the 
.i eval
function, the lisp compiler is centered around the function
.i d-exp
whose job it is to compile the expression passed to it.
The lisp compiler is one pass.
Before a call to d-exp returns,
all the compiled code for a form has been computed and written
to a file.
One reason that the lisp compiler can be a single pass compiler
is that the assembler which reads the compiler's output is 
a two pass assembler.
.sh +1 global\ state\ variables
.pp
There are a number of variables that maintain the state of
the compilation process.
These variables are declared special and are thus dynamically scoped
and visible to any function within the compiler.
When d-exp 
is called their meanings are:
.ip \fIv-form\fP
- contains the expression to be compiled.
.ip \fIg-loc\fP
- tells where the result of evaluating this expression should be put.
If g-loc is nil, then the value returned is unimportant and shouldn't
be put anywhere.
.ip \fIg-cc\fP
- controls conditional branches.
If g-cc is non nil, then it is a list cell whose value has
either a non-null car or non-null cdr but not both. 
If the car is non-nil then
if  the  value  of  the expression held in 
v-form  is  non-nil, a branch should be done to the symbol
stored  in  \fI(car\ g-cc)\fP. 
If the cdr is non-nil  then if the value of v-form
is  nil,  a branch should be done to the symbol stored in 
\fI(cdr\ g-cc)\fP. Since at
every  conditional  
branch  control should  either jump or continue, the car and cdr will
never both be specified.
.ip   \fIg-ret\fP
- is non nil if the result of evaluating v-form will be returned as the
value of the function we are evaluating. 
This is used to allow liszt to detect
when tail recursion removal is possible.
.ip  \fIg-locs\fP
- maintains current information about the state of the stacks:
the bindstack (for specials), the namestack (for locals) and the
C runtime stack (for catch frames)
The form of g-locs is a stack of tokens stored as a list.
The meaning of each token is as follows:
.br
\fInil\fP - this represents an unnamed object on the namestack.  This happens
when calling functions, when the arguments are stacked prior
to a function call.
.br
\fI<symbol>\fP - the given symbol is the name of a local variable on the namestack.
.br
\fI(prog . <n>)\fP - prog statement which binds <n> special variables
.br
\fI(do . <n>)\fP - head of a do statement which binds <n> special variables
.br
\fI(catcherrset . 0)\fP - catch or errset frame on stack
.br
\fI(lambda . <n>)\fP - lambda expression which binds <n> special variables
.ip \fIg-labs\fP
- this is a stack of labels. 
There is one entry in g-labs for every entry which is a list
in g-locs.
the forms of the entries are:
.br
\fInil\fP - no labels in this form
.br
\fI(endlab (real . gen) (real2 . gen2) ...)\fP - endlab is the label to go to
to get out of this body.  After this label will be code
to unbind specials and pop off locals.
The 'real' labels are those found in the code. the gen
labels are those generated and put in the assembler code.
.sh +0 Function\ compilation
.pp
The action d-exp takes when compiling an expression depends on the
type of expression.
For atoms (symbols and numbers) the compilation is very simple, it
is just a matter of putting the value where g-loc specifies and 
then jumping if specified by g-cc.
When the expression is a list, d-exp first macro
expands the form and then looks at the first element of 
the list (if the list has not macro expanded to an atom).
If the first element is a symbol  then the list is
is a function call.
If the function is one of the functions which liszt knows how to open compile
then liszt will call the particular routine designated to open
compile this function.
There are two classes of functions within liszt that
do open compiling.
The first class, the fl-expr class, are distinguished by names which
begin with c-.
These functions always generate
code which returns the result in r0.
They are not equipped to obey the contents of g-loc and g-cc.
Thus d-exp, after calling one of these functions, must do what
is necessary to obey g-loc and g-cc.
The other class of open compiling functions, the fl-exprcc class (whose
names begin with cc-),
handle g-loc and g-cc.
As a result these are usually much more complex and generate better code.
.sh -1 Register\ Conventions
.pp
The register conventions used by liszt are an extension of those
used by the C code.
.ip \fIr0\fP
- return value placed here
.ip \fIr1,r2,r3,r4\fP
- scratch registers.
When long strings of 
.i car's
or
.i cdr's
are done (such as
.i caddadr )
these registers are used in a least-recently-used fashion to access down
the list. 
The compiler keeps track of the values in these registers but must
assume that they are garbage after a function is called.
.ip \fIr5\fP
- fixnum accumulator.
There a number of functions which work on fixnum's only and these
usually put their result in r5.
The assembler code function 
.i qnewint
which returns a pointer to a cell containing a fixnum value (after
checking if it is in the fixnum table), expects its argument to be
in r5.
.ip \fIr6\fP
np
.ip \fIr7\fP
lbot.
When calling a function, this register is set just before the function
call, and after the function call its value is used to reset the value
of np in order to pop the arguments off the namestack.
.ip \fIr8\fP
the literal table pointer.
The compiler generates a table of all the literal lisp data
which the compiled code might access.
Upon function entry a pointer to the base of this table is put in r8.
For example, if we compile \fI(setq\ x\ 'foo)\fP then we will
need a pointer to the lisp symbol
.i foo
and if the symbol
.i x
as been declared special we will also need a pointer to 
.i x .
.ip \fIr10\fP
- upon function entry the value of lbot (r7) is put in r10.  
This allows us to reference the arguments to our function while
using lbot to call other function.
.sh +0 Addresses
There are four types of addresses used internally in Franz: 
symbolic, intermediate addresses (iadr), extended intermediate (eiadr)
and vax assembler format.
.sh +1 Symbolic
.pp
These are the names of lisp objects, such as `a', `foo', 34,
or 3.234234.
A name is either a local variable, a special variable or a
number.  A number is either a small fixnum, large fixnum,
bignum or floating point number.
.sh +0 Intermediate\ address\ (IADR)
.pp
This type of address is generated from a symbolic address by
.i d-loc, 
.i d-loclit 
and the routines 
.i d-simple
and 
.i d-rsimple 
which
call them. The forms are
.ip \fINil\fP
- the location or value of nil.
.ip \fIT\fP   
- the location or value of t.
.ip \fIreg\fP
- register r0, which is where the result of function calls
are stored.
.ip \fIstack\fP
- the address pointed to by np with np incremented after
the value is stored.  (i.e  (r6)+)
.ip \fIunstack\fP
- the address one word below np (np is decremented before
accessing. (i.e. -(r6))
.ip \fI<symbol>\fP
- this is just <symbol>.  This allows strange forms to be
represented directly.
.ip \fI(stack\ n)\fP
- The n'th value on the namestack for this function.
The first value 0(r10) is (stack 1).
.ip \fI(vstack\ n)\fP
- The cdr of the n'th value on the namestack.  
That is, (stack 1) is *0(r10)
.ip \fI(bind\ n)\fP
- The value of the n'th value in 
the literal table.  If 
this refers to a symbol, then this is the value
of the symbol.
If this refers to a list then this
this is the cdr of the list (although this is a rare
use). (bind 1) corresponds to *0(r8).
.ip \fI(lbind\ n)\fP
- The n'th value in the literal table.  If 
this refers to 
object foo then this is the address of foo
in memory.
.ip \fI(fixnum\ n)\fP
- This is the address of small fixnum n in memory.
A small fixnum is in the range -1024 to 1023.
.ip \fI(immed\ n)\fP
- The is the immediate constant n. 
.sh +0 extended\ intermediate\ address\ (EIADR)
.pp
This address is generated from an IADR by e-cvt.  It 
symbolically represents a vax address.  
.ip \fI<atom>\fP
- This corresponds to <atom>.
.ip \fI(n\ <atom>)\fP
- This corresponds to n(<atom>)
(stack n+1) and (lbind n+1) are converted to these forms.
.ip \fI(n\ <atom1>\ <atom2>)\fP
- This corresponds to n(<atom1>)[<atom2>]
There is currently no IADR which generates this.
.ip \fI(*\ n\ <atom>)\fP
-This corresponds to *n(<atom>)
(vstack n+1) and (bind n+1) are converted to these forms.
.ip \fI(+\ <atom>)\fP
- This corresponds to (<atom>)+.
stack is converted to this form.
.ip \fI(-\ <atom>)\fP
- This corresponds to -(<atom>)
unstack is converted to this form.
.ip \fI($\ <numb>)\fP
- This corresponds to $<atom>
(immed <numb>) is converted to this form.
.ip \fI(#\ <numb>)\fP
- This corresponds to $<loc> where <loc> equals 
the base of the fixnums (0x1400) plus 4 * <numb>
(fixnum <numb>) is converted to this form
.ip \fI(*\ #\ <numb>)\fP
- This corresponds to $<numb>.  It is generated
by d-rsimple occasionally when you ask for the
cdr of a number (which you do sometimes when you
are compiling fixnum operators).
.sh +0 Vax\ Unix\ assembler\ addresses
.pp
The addresses are printed from a EIADR by e-cvtas.  The conversions
are shown in the above section.
.sh -1 Function\ calling\ convention
.sh +1 Function\ linkages
.pp
The function associated with a symbol is stored in the function 
definition slot of the symbol.  If the function slot contains a
list then the function is to be interpreted and its 'car' will 
be lambda, nlambda, lexpr or macro.  If the function is compiled then 
the function definition slot will contain a binary object. 
A binary object 
is composed of two independent parts: the discipline and the entry.
The discipline is one of:
.ip \fIlambda\fP
- a function whose arguments should be evaluated.
.ip \fInlambda\fP
- a function whose arguments should not be evaluated but
which should be passed as a list
.ip  \fImacro\fP
- a function which should be passed the unevaluated form
being evaluated and whose result should be evaluated.
.ip \fI\"subroutine\"\fP
- a foreign function subroutine
.ip  \fI\"integer-function\"\fP
- a foreign function returning an integer
.ip \fI\"real-function"\fP
- a foreign function returning a flonum.
.pp   
A lexpr is not in the list as there is no difference to the caller
between compiled lambda's and compiled lexprs.
.sh +0 Transfer\ tables
Most calls from compiled code to other lisp functions go through
transfer tables.  A transfer table entry is a pair: symbol, address
of routine. 
When another lisp function is called it uses the 
.i calls 
instruction which will
indirectly jump through the address in the transfer table.  This
address may point to the desired function or it may point to the
interface routine.  If a call ends up in the interface routine, 
then that routine will trace back through the call stack and eventually
reach the tranfer table entry that it was 'called through'. Since the
transfer table entry contains a symbol which names the function
to be called, the interface routine can determine which routine
was to have been called.  If that routine is compiled then the 
interface routine can modify the transfer table so that a call
through that table entry will go directly to the desired function.
If the routine to be called is interpreted, or if the user has
requested that transfer linkages should be disabled, then the
interface routine will go through the 'funcall' function 
in the interpreter to continue the call.  
.sh +0 calling\ sequence\ in\ the\ compiler:
.pp
When transfer tables are used
.(b
	\fBforeach\fP arg
	 \fBcompute\fP arg and stack result using (r6)+
	 for example: movl r0,(r6)+
	movab -n(r6),r7    where n = 4*number of args to fcn
	calls  $0,*trantb+m      where m is the index of the function
				 in the transfer table.
	movl r7,r6		restore r6
.)b
.pp
The compiler supports local functions, which are function accessible
only within one file.
Because they are not accessible from C code, we can use a very fast
call and return sequence when calling them.
To call a local function
.(b
	\fBforeach\fP arg
	 \fBcompute\fP and stack using (r6)+
	jsb	LOCALNAME	go directly to the function, it will 
				restore r6 before it returns.
.)b
.pp
The beginning of each function looks as follows. 
First for a non-lexpr function called in the standard way
(topsim is the label jumped to when tail merging, it will be unique
for each function; the brackets indicate the optional code which
exists if the -p switch is given to liszt);
.(b
	.word	0x5c0		# save r6, r7, r8, r10
    [   movab	mcounts,r0	# if profiling, mcounts replaced by fasl
	jsb	mcount	]
	movab	linker,r8	# set up r8
	movl	r7,r10		# set up oldlbot
	movab	n(r10),r6	# n = 4*Number of args expected.
topsim:
.)b
.pp
Now for lexprs:
.(b
	.word	0x5c0
    [   movab	mcounts,r0	# if profiling. [mcounts replaced by fasl]
	jsb	mcount	]
	movab	linker,r8	# set up r8
	subl3	$4,r7,-(sp)	# address one word below bottom of args
topsim:
	movl	r6,r10		# first free addr to arg base
	subl3	r7,r6,r0	# number of args * 4 into r0
	movab 	0x1400(r0),(r6)+ # stack boxed number of args
	movl	0(r10),-(sp)	# also store on stack so user can't clobber
.)b
.pp
And finally for local functions:
.(b
	movl	r10,-(sp)	# save r10
	movab	-n(r6),r10	# set up arg base using arg top
topsim:
.)b
.sh -1 Assembler\ file\ format
.pp
The liszt compiler generates a file which is in Unix assembler format.
The Unix assembler converts that file into an object file which fasl
then reads.
Although the object file generated is a standard Unix object
file (as defined in /usr/include/a.out.h), it is not of a 
format that  the Unix ld loader can understand.
This is because the requirements  of a lisp object file are different
from an object file of other languages.
The 
run time semantics of lisp and the fact that lisp data must be protected
from garbage collection are the principal differences.
The unconventional object file created by the unix assembler 
is a result of the
unconventional assembler input file.
Next we will look at what must be put in the assembler file and how
it is put there.
.pp
The assembler file must contain
.ip \fIinstructions\fP
- vax assembler code for the compiled functions. 
If there aren't any functions compiled, this can be empty.
.ip \fIliterals\fP
- lisp data which is referred to by compiled code
.ip \fItransfer\ table\fP
- the names of the functions which correspond to the calls through
the transfer table.
The instructions simply say 'call indirect through the nth transfer
table entry'.
.ip \fIfunction\ names\fP
- the names of the functions which are being 
defined.
.ip \fIload\ time\ forms\fP
- in order to mimic the 
.i load
function, fasl has to be able to evaluate lisp expressions at fasl 
time, so we must be able to store lisp expressions in the assembler
file and indicate when they should be evaluated.
.pp
Based on the requirements above, the form of the assembler file
is as described below.
The assembler  builds two segments: text and data.
We put all of our information in the text segment.
The compiler places some annotation strings in the data segment
so that the object file can be identified, however the data segment
is ignored by fasl.
The format is
.ip \fIcompiled\ instructions\fP
The instructions for each compiled  (non-local) function begins with
.(b
    .globl F00003
    F00003:
   .word 0x5c0
.)b
The globl declaration and the fact that the symbol name begins
with a F will tell fasl that this is the beginning of a lisp
function.
The symbols beginning with F must be unique within a file
but may be duplicated in other files.
The lisp  name of the function will appear later.
Next the instructions for the function are given.
Only a small fixed set of external symbols may be referenced.
The list is found in the code for nfasl.c and the common
ones will be described below (soon).
Labels should be given a name beginning with L and should
be unique within the file.
.ip \fItable\ sizes\fP
somewhere in the file there should be a pair of 'set' assembler
pseudo ops which describe the sizes of the literal table and
transfer table.
The form is this
.(b
    .set linker_size  4
    .set trans_size 3
.)b
where linker_size is the number of entries in the literal table
which will be required and trans_size is the number of entries
in the transfer table which will be required.
Those tables will be built by  fasl.
.ip \fIbinder\ table\fP
- this table describes  the order that the functions should be
defined and forms evaluated.
It is a table of longwords  beginning at the symbol bind_org
and ending when a -1 entry is seen.
The meaning of the entries will be described below.
.ip \fIlisp\ expression\ table\fP
- this is a table of null terminated strings beginning at the 
symbol lit_org and ending at the symbol lit_end.
Each string is read by the lisp read function (using the
raw readtable).
The first linker_size expressions are put in the literal table.
The next trans_size expressions are the names  of the
functions to put in the transfer table.
The remaining expressions correspond to the entries in the
binder table.
The binder entries are processed, one by one. 
Provided that the binder entry is not -1,  an expression
is read from the lisp expression table.
Then if the binder table entry is 0, that expression is the name of
a lambda type lisp function.
A binary object is created, the discipline is set to lambda and
the function address is set to the lexically next function 
defined in the
file.
If the binder entry is 1 then this is an nlambda function, and
if the entry is 2 then this is a macro function.
If the entry is 99 then the expression just read should be evaluated
by the lisp function eval.
.pp
The lisp compiler normally puts the assembler format output file in /tmp
and removes it when it is done. 
The -S switch will tell liszt to write the 
assembler file in the same place as
the source file, and with the same root name but a .s extension.
The -C file will tell the lisp compiler to comment the file as it
generates it, making it easier to understand what is going on.
Assume that the following is file foo.l:
.(b
(defun foo (x) (bar y) (baz k))
(print (foo 3))
(def test (nlambda (l) (print 'hithere) (foo 3)))
.)b
we now compile it with -SC
.(b
    .globl	F00007	#(fcn lambda foo)
    F00007:
    .word	0x5c0
    movab	linker,r8
    movl	r7,r10
    movab	4(r10),r6
    L00008:
    movl	*0(r8),(r6)+	#(calling bar)
	    #(from y to stack)
    movab	-4(r6),r7
    calls	$0,*trantb+0
    movl	r7,r6
    movl	*4(r8),(r6)+	#(calling baz)
	    #(from k to stack)
    movab	-4(r6),r7
    calls	$0,*trantb+8
    movl	r7,r6
    ret
    .globl	F00009	#(fcn nlambda test)
    F00009:
    .word	0x5c0
    movab	linker,r8
    movl	r7,r10
    movab	4(r10),r6
    L00010:
    movl	8(r8),(r6)+	#(calling print)
	    #(from 'hithere to stack)
    movab	-4(r6),r7
    calls	$0,*trantb+16
    movl	r7,r6
    movl	$5132,(r6)+	#(calling foo)
	    #(from (fixnum 3) to stack)
    movab	-4(r6),r7
    calls	$0,*trantb+24
    movl	r7,r6
    ret
    bind_org:
    .set linker_size,	3
    .set trans_size,	4
    .long	0
    .long	99
    .long	1
    .long -1
    lit_org:
    .asciz "y"
    .asciz "k"
    .asciz "hithere"
    .asciz "bar"
    .asciz "baz"
    .asciz "print"
    .asciz "foo"
    .asciz "foo"
    .asciz "(print (foo 3))"
    .asciz "test"
    lit_end:
    .data  # this is just for documentation 
    .asciz "@(#)Compiled by Lisp Compiler 7.1 on Sun Feb 21 17:51:54 1982"
    .asciz "@(#)decl.l	1.5	2/10/82"
    .asciz "@(#)array.l	1.1	9/25/81"
    .asciz "@(#)datab.l	1.1	9/25/81"
    .asciz "@(#)expr.l	1.1	9/25/81"
    .asciz "@(#)io.l	1.1	9/25/81"
    .asciz "@(#)funa.l	1.3	2/10/82"
    .asciz "@(#)funb.l	1.2	2/10/82"
    .asciz "@(#)func.l	1.2	2/10/82"
    .asciz "@(#)tlev.l	1.4	10/24/81"
    .asciz "@(#)fixnum.l	1.6	10/21/81"
    .asciz "@(#)util.l	1.2	10/7/81"
.)b
.sh +0 functions\ callable\ from\ compiled\ lisp\ code
.sh +0 Object\ File\ Format
.sh +0 Fasl