update and expand IMPLEMENTATION.md
Incorporate text and explanation from Andy Keep at https://groups.google.com/d/msg/chez-scheme/dz6nn-8KDQE/FUaPu695BAAJ original commit: 5b8a00fc3ef9b892de9af1ae05352fa204e72270
This commit is contained in:
parent
f78dc5724e
commit
56049bcd47
|
@ -1,7 +1,11 @@
|
|||
# Getting Started
|
||||
|
||||
Most of the Chez Scheme implementation is in the "s" directory. The
|
||||
C-implemented kernel is in the "c" directory.
|
||||
The majority of the Chez Scheme compiler and libraries are implemented
|
||||
in Scheme and can be found in the "s" (for Scheme) subdirectory. The
|
||||
run-time kernel (including the garbage collector, support for
|
||||
interacting with the operating system, and some of the more
|
||||
complicated math library support) are implemented in C and can be
|
||||
found in the "c" directory.
|
||||
|
||||
Some key files in "s":
|
||||
|
||||
|
@ -21,6 +25,77 @@ Some key files in "s":
|
|||
provides platform-specific constants that feed into "cmacro.ss" and
|
||||
selects the backend used by "cpnanopass.ss"
|
||||
|
||||
Chez Scheme is a bootstrapped compiler, meaning you need a Chez Scheme
|
||||
compiler to build a Chez Scheme compiler. The compiler and makefiles
|
||||
support cross-compilation, so you can work from an already supported
|
||||
host to cross-compile the boot files and produce the header files for
|
||||
a new platform. In particular, the `pb` (portable bytecode) machine
|
||||
type can run on any supported hardward and operating system, so having
|
||||
`pb` boot files is one way to get started in a new environment.
|
||||
|
||||
# Build System
|
||||
|
||||
Chez Scheme assigns a `machine-type` name to each platform it runs on.
|
||||
The `machine-type` name carries three pieces of information:
|
||||
|
||||
* *whether the system threaded*: A `t` indicates that it is, and an
|
||||
absence indicates that it's not threaded;
|
||||
|
||||
* *the hardware platform*: `i3` for x86, `a6` for x86_64, `arm32` for
|
||||
AArch32, `arm64` for AArch64, and `ppc32` for 32-bit PowerPC; and
|
||||
|
||||
* *the operating system*: `le` for Linux, `nt` for Windows, `osx` for
|
||||
Mac OS, etc.
|
||||
|
||||
When you run "configure", it looks for boot and header files as the
|
||||
directory "boot/*machine-type*". (If it doesn't find them, then
|
||||
configuration cannot continue.)
|
||||
|
||||
The supported machine types are listed in "cmacros.ss" and reflected
|
||||
by a "boot/*machine-type*" directory for boot and headers files, a
|
||||
"s/*machine-type*.def" file to describe the platform, a
|
||||
"s/Mf-*machine-type*" makefile to select relevant files in "s", a
|
||||
"c/Mf-*machine-type*" makefile for configration in "c", and a
|
||||
"mats/Mf-*machine-type*" makefile to configure testing.
|
||||
|
||||
The "workarea" script in the root of the Chez Scheme project is used
|
||||
to generate a subdirectory with the appropriate contents to build for
|
||||
that particular machine. This is the script that "configure" runs when
|
||||
configuring for doing the build, but you can also run the "workarea"
|
||||
script on your own, supplying the machine type you'd like to build.
|
||||
|
||||
If you have a working Chez Scheme build and you want to cross-compile
|
||||
to generate *machine-type* boot and header files, the easiest approach
|
||||
is `make` *machine-type*`.boot`. The output is written to the
|
||||
"boot/*machine-type*" directory.
|
||||
|
||||
# Porting to a New Platform
|
||||
|
||||
Porting to a new system requires both getting the C run time compiled
|
||||
on the new platform and updating the Scheme compiler to generate
|
||||
machine code for the platform. There are several places where the C
|
||||
kernel and code generated by the compiler need to work in harmony in
|
||||
order to get the system to run. For instance, the C kernel needs to
|
||||
know the type tags, sizes, and field offsets into Scheme objects, so
|
||||
that the garbage collector in the C kernel can do its job. This is
|
||||
handled by having the Scheme compiler generate a couple of C headers:
|
||||
"scheme.h" and "equates.h", that the contain the information about the
|
||||
Scheme compiler the C kernel needs to do its job.
|
||||
|
||||
Most of the work of porting to a new platform is producing a new
|
||||
"*machine-type*.def" file, which (except in simple ports to a new
|
||||
operating system) will require a new "*isa*.ss" compiler backend.
|
||||
You'll also have to set up all the "Mf-*machine-type*" makefiles and
|
||||
update "configure", "cmacro.ss", and "version.h" (plus maybe other
|
||||
files). Once you have all of the pieces working together, you
|
||||
cross-compile boot files, then copy them over to the the new machine
|
||||
to start compiling there.
|
||||
|
||||
You can port to a new operating system by imitating the files of a
|
||||
similar supported oerating system, but building a new backend for a
|
||||
new processor requires much more understanding of the compiler and
|
||||
runtime system.
|
||||
|
||||
# Scheme Objects
|
||||
|
||||
A Scheme object is represented at run time by a pointer. The low bits
|
||||
|
@ -30,7 +105,11 @@ additional tag word to further refine the pointer-tag type.
|
|||
|
||||
See also:
|
||||
|
||||
> *Don't Stop the BiBOP: Flexible and Efficient Storage Management for Dynamically Typed Languages.* by R. Kent Dybvig, David Eby, and Carl Bruggeman, Indiana University TR #400, 1994.
|
||||
> *Don't Stop the BiBOP: Flexible and Efficient Storage Management for
|
||||
> Dynamically Typed Languages*
|
||||
> by R. Kent Dybvig, David Eby, and Carl Bruggeman,
|
||||
> Indiana University TR #400, 1994.
|
||||
> [PDF](http://www.cs.indiana.edu/ftp/techreports/TR400.pdf)
|
||||
|
||||
For example, if "cmacro.ss" says
|
||||
|
||||
|
@ -57,8 +136,8 @@ of a Scheme record, that first word will be a record-type descriptor
|
|||
as a record. The based record type, `#!base-rtd` has itself as its
|
||||
record type. Since the type bits are all ones, on a 64-bit machine,
|
||||
every object tagged with an additional type workd will end in "F" in
|
||||
hexadecimal, and adding 1 to the pointer produces the address
|
||||
containing the record content (which starts with the rrecord type, so
|
||||
hexadecimal, and adding 1 to the pointer produces the <address
|
||||
containing the record content (which starts with the record type, so
|
||||
add 9 instead to get to the first field in the record).
|
||||
|
||||
As another example, a vector is represented as `type-typed-object`
|
||||
|
@ -98,8 +177,15 @@ and continuation operations are handled as needed at the boundaries.
|
|||
|
||||
See also:
|
||||
|
||||
> *Representing Control in the Presence of First-Class Continuations* by Robert Hieb, R. Kent Dybvig, and Carl Bruggeman, Programming Language Design and Implementation, 1990.
|
||||
> *Compiler and Runtime Support for Continuation Marks* by Matthew Flatt and R. Kent Dybvig, Programming Language Design and Implementation, 2020.
|
||||
> *Representing Control in the Presence of First-Class Continuations*
|
||||
> bby Robert Hieb, R. Kent Dybvig, and Carl Bruggeman,
|
||||
> Programming Language Design and Implementation, 1990.
|
||||
> [PDF](https://legacy.cs.indiana.edu/~dyb/pubs/stack.pdf)
|
||||
|
||||
> *Compiler and Runtime Support for Continuation Marks*
|
||||
> by Matthew Flatt and R. Kent Dybvig,
|
||||
> Programming Language Design and Implementation, 2020.
|
||||
> [PDF](https://www.cs.utah.edu/plt/publications/pldi20-fd.pdf)
|
||||
|
||||
To the degree that the runtime system needs global state, that state
|
||||
is in the thread context (so, it's thread-local), which we'll
|
||||
|
@ -215,10 +301,18 @@ Compilation
|
|||
involves many individual passes that convert through many different
|
||||
intermediate forms (see "np-language.ss").
|
||||
|
||||
It's worth noting that Chez Scheme produces machine code directly,
|
||||
instead of relying on a system-provided assembler. Chez Scheme also
|
||||
implements its own linker to connect compiled code to runtime kernel
|
||||
facilaties and shared symbols.
|
||||
|
||||
See also:
|
||||
|
||||
> *Nanopass compiler infrastructure* by Dipanwita Sarkar, Indiana University PhD dissertation, 2008
|
||||
> *A Nanopass Framework for Commercial Compiler Development* by Andrew W. Keep, Indiana University PhD dissertation, 2013
|
||||
> *Nanopass compiler infrastructure* by Dipanwita Sarkar,
|
||||
> Indiana University PhD dissertation, 2008.
|
||||
|
||||
> *A Nanopass Framework for Commercial Compiler Development*
|
||||
> by Andrew W. Keep, Indiana University PhD dissertation, 2013.
|
||||
|
||||
Note that the core macro expander always converts its input to the
|
||||
`Lsrc` intermediate form. That intermediate form can be converted back
|
||||
|
@ -259,19 +353,19 @@ Each `<reg>` has the form
|
|||
[<name> ... <preserved? / callee-saved?> <num> <type>]
|
||||
```
|
||||
|
||||
* The <name>s in one <reg> will all refer to the same register, and
|
||||
the first <name> is used as the canonical name. By convention, each
|
||||
<name> starts with `%`. The compiler gives specific meaning to a
|
||||
* The `<name>`s in one `<reg>` will all refer to the same register, and
|
||||
the first `<name>` is used as the canonical name. By convention, each
|
||||
`<name>` starts with `%`. The compiler gives specific meaning to a
|
||||
few names listed below, and a backend can use any names otherwise.
|
||||
|
||||
* The information on preserved (i.e, callee-saved) registers helps
|
||||
the compiler save registers as needed before some C interactions.
|
||||
|
||||
* The <num> value is for the private use of the backend. Typically,
|
||||
* The `<num>` value is for the private use of the backend. Typically,
|
||||
it corresponds to the register's representation within machine
|
||||
instructions.
|
||||
|
||||
* The <type> is either 'uptr or 'fp, indicating whether the register
|
||||
* The `<type>` is either `'uptr` or `'fp`, indicating whether the register
|
||||
holds a pointer/integer value (i.e., an unsigned integer that is
|
||||
the same size as a pointer) or a floating-point value. For
|
||||
`allocatable` registers, the different types of registers represent
|
||||
|
@ -297,37 +391,47 @@ category are automatically saved as needed for C interactions.
|
|||
The main recognized register names, roughly in order of usefulness as
|
||||
real machine registers:
|
||||
|
||||
%tc - the first reserved register, must be mapped as reserved
|
||||
%sfp - the second reserved register, must be mapped as reserved
|
||||
%ap - allocation pointer (for fast bump allocation)
|
||||
%trap - counter for when to check signals, including GC signal
|
||||
* `%tc` - the first reserved register, must be mapped as reserved
|
||||
|
||||
%eap - end of bump-allocatable region
|
||||
%esp - end of current stack segment
|
||||
* `%sfp` - the second reserved register, must be mapped as reserved
|
||||
|
||||
%cp - used for a procedure about to be called
|
||||
%ac0 - used for argument count and call results
|
||||
* `%ap` - allocation pointer (for fast bump allocation)
|
||||
|
||||
%ac1 - various scratch and communication purposes
|
||||
%xp - ditto
|
||||
%yp - ditto
|
||||
* `%trap` - counter for when to check signals, including GC signal
|
||||
|
||||
|
||||
* `%eap` - end of bump-allocatable region
|
||||
|
||||
* `%esp` - end of current stack segment
|
||||
|
||||
|
||||
* `%cp` - used for a procedure about to be called
|
||||
|
||||
* `%ac0` - used for argument count and call results
|
||||
|
||||
|
||||
* `%ac1` - various scratch and communication purposes
|
||||
|
||||
* `%xp` - ditto
|
||||
|
||||
* `%yp` - ditto
|
||||
|
||||
Each of the registers maps to a slot in the TC, so they are sometimes
|
||||
used to communicate between compiled code and the C-implemented
|
||||
kernel. For example, `S_call_help` expects the function to be called
|
||||
in AC1 with the argument count in AC0 (as usual).
|
||||
in AC1 with the argument count in AC0 (as usual). If a recognized name
|
||||
is not mapped to a register, it exists only as a TC slot.
|
||||
|
||||
A few more names are recognized to direct the compiler in different
|
||||
ways:
|
||||
|
||||
%ret - use a return register insteda of just SFP[0]
|
||||
* `%ret` - use a return register insteda of just SFP[0]
|
||||
|
||||
%reify1, %reify2 - a kind of manual allocation of registers for
|
||||
certain hand-coded routines, which otherwise could
|
||||
run out of registers to use
|
||||
* `%reify1`, `%reify2` - a kind of manual allocation of registers for
|
||||
certain hand-coded routines, which otherwise could
|
||||
run out of registers to use
|
||||
|
||||
Variables and Register Allocation
|
||||
---------------------------------
|
||||
# Variables and Register Allocation
|
||||
|
||||
A variables in Scheme code can be allocated either to a register or to
|
||||
a location in the stack frame, and the same goes for temporaries that
|
||||
|
@ -486,7 +590,7 @@ register plus an offset instead of two registers, because the offset
|
|||
is too big, because the offset does not have a required alignment, and
|
||||
so on.
|
||||
|
||||
# Instruction Selection: Compiler <-> Backend
|
||||
# Instruction Selection: Compiler to Backend
|
||||
|
||||
For each primitive that the compiler will reference via `inline`,
|
||||
there must be a `declare-primitive` in "np-language.ss". Each
|
||||
|
@ -509,7 +613,7 @@ binds `%logand`. The `(%inline name ,arg ...)` macro expands to
|
|||
`(inline ,null-info ,%name ,arg ...)` macro, so that's why you don't
|
||||
usually see the `%` written out.
|
||||
|
||||
The backend implementation of a prrimitive is a function that takes as
|
||||
The backend implementation of a primitive is a function that takes as
|
||||
many arguments as the `inline` form, plus an additional initial
|
||||
argument for the destination in the case of a `value` primitive on the
|
||||
right-hand side of a `set!`. The result of the primitive function is a
|
||||
|
@ -575,9 +679,9 @@ see "Foreign Function ABI" below.
|
|||
|
||||
To summarize the interface between the compiler and backend is:
|
||||
|
||||
primitive : L15c.Triv ... -> (listof L15d.Effect)
|
||||
* `primitive : L15c.Triv ... -> (listof L15d.Effect)`
|
||||
|
||||
instruction : (listof code) L16.Triv ... -> (listof code)
|
||||
* `instruction : (listof code) L16.Triv ... -> (listof code)`
|
||||
|
||||
A `code` is mostly bytes to be emitted, but it also contains
|
||||
relocation entries and human-readable forms that are printed when
|
||||
|
@ -617,13 +721,16 @@ registers or a register and an immediate, but the immediate value has
|
|||
to be representable with a funky encoding. The pattern forms above
|
||||
require that the destination is always a register/variable, and either
|
||||
of the arguments can be a literal that fits into the funky encoding or
|
||||
a register/variable. The `define-instruction` macro is itself
|
||||
implemented in "arm64.ss", so it can support specialized patterns like
|
||||
`funkymask`.
|
||||
a register/variable. The `define-instruction` macro is parameterized
|
||||
over patterns like `funkymask` via `coercible?` and `coerce-opnd`
|
||||
macros, so a backend like "arm64.ss" can support specialized patterns
|
||||
like `funkymask`.
|
||||
|
||||
If a call to this `%logand` function is triggered by a form
|
||||
|
||||
```scheme
|
||||
`(set! ,info (mref ,var1 ,%zero 8) ,var2 ,7)
|
||||
```
|
||||
|
||||
then the code generated by `define-instruction` will notice that the
|
||||
first argument is not a register/variable, while 7 does encode as a
|
||||
|
@ -651,10 +758,10 @@ would have to generate an `add` into a second temporary variable.
|
|||
Otherwise, `asm-move` would not be able to deal with the generated
|
||||
`set!` to move `u` into the destination. The implementation of
|
||||
`define-instruction` uses a `mem->mem` helper function to simplify
|
||||
`mref`s. In the "arm32.ss" backend, there's an additional `fpmem`
|
||||
pattern and `fpmem->fpmem` helper, because the constraints on memory
|
||||
references for floating-point operations are different than than the
|
||||
constraints on memory references to load an integer/pointer.
|
||||
`mref`s. There's an additional `fpmem` pattern and `fpmem->fpmem`
|
||||
helper, because the constraints on memory references for
|
||||
floating-point operations can be different than than the constraints
|
||||
on memory references to load an integer/pointer (e.g., on "arm32.ss").
|
||||
|
||||
Note that `%logand` generates a use of the same `(asm-logand #f)`
|
||||
instruction for the register--register and the register--immediate
|
||||
|
@ -710,6 +817,25 @@ human-readable addition.
|
|||
All of that could be done with just plain functions, but the macros
|
||||
help with boilerplate and arrange some helpful compile-time checking.
|
||||
|
||||
# Linking
|
||||
|
||||
Besides actual machine code in the output of the assembly step,
|
||||
machine-specific linking dierctives can appear. In the case of
|
||||
"arm32.ss", the linking options are `arm32-abs` (load an absolute
|
||||
address), `arm32-call` (call an asolute address while setting the link
|
||||
register), and a`arm32-jump` (jump to an asolute address). These are
|
||||
turned into relocation entries associated with compiled code by steps
|
||||
in "compile.ss". Relocaiton entires are used when loding an GCing with
|
||||
update routines implemented in "fasl.c".
|
||||
|
||||
Typically, a linking directive is written just after some code that is
|
||||
generated as installing a dummy value, and theen the update routine in
|
||||
"fasl.c" writes the non-dummy value when it becomes available later.
|
||||
Each linking directive must be handled in "compile.ss", and it must
|
||||
know the position and size of the code (relative to the direction) to
|
||||
be updated. Overall, there's a close conspiracy among the backend, the
|
||||
handling in "compile.ss", and the update routine in "fasl.c".
|
||||
|
||||
# Foreign Function ABI
|
||||
|
||||
Support for foreign procedures and callables in Chez Scheme boils down
|
||||
|
@ -736,15 +862,18 @@ duplicated in the result (matching the C view) and an argument
|
|||
neither the C nor Scheme view, but either view can be reconstructed.
|
||||
|
||||
The compiler creates wrappers to take care of further conversion
|
||||
to/from these primitive shapes.
|
||||
to/from these primitive shapes. You can safely ignore the
|
||||
foreign-callable support, at first, when porting to a new platforrm,
|
||||
but foreign-callable support is needed for generated code to access
|
||||
runtime kernel functionality.
|
||||
|
||||
The `asm-foreign-call` function returns 5 values:
|
||||
|
||||
* allocate : -> L13.Effect
|
||||
* `allocate : -> L13.Effect`
|
||||
|
||||
Any needed setup, such as allocating C stack space for arguments.
|
||||
|
||||
* c-args : (listof (uvar/reg -> L13.Effect))
|
||||
* `c-args : (listof (uvar/reg -> L13.Effect))`
|
||||
|
||||
Generate code to convert each argument. The generated code will be
|
||||
in reverse order, with the first argument last, because that tends
|
||||
|
@ -762,14 +891,14 @@ The `asm-foreign-call` function returns 5 values:
|
|||
- integer or pointer: a 'uptr-typed variable that has the integer
|
||||
- "&": a 'uptr-typed variable that has a pointer to the argument
|
||||
|
||||
* c-call : uvar/reg boolean -> L13.Effect
|
||||
* `c-call : uvar/reg boolean -> L13.Effect`
|
||||
|
||||
Generate code to call the C function whose address is in the given
|
||||
register. The boolean if #t if the call can assume that the C
|
||||
function is not a varargs function on platformss where varargs
|
||||
support is the default.
|
||||
|
||||
* c-result : uvar/reg -> L13.Effect
|
||||
* `c-result : uvar/reg -> L13.Effect`
|
||||
|
||||
Similar to the conversions in `c-args`, but for the result, so the
|
||||
given argument is a destination variable. This function will not be
|
||||
|
@ -777,19 +906,19 @@ The `asm-foreign-call` function returns 5 values:
|
|||
floating-point value, the provided destination variable has type
|
||||
'fp.
|
||||
|
||||
* allocate : -> L13.Effect
|
||||
* `allocate : -> L13.Effect`
|
||||
|
||||
Any needed teardown, such as deallocating C stack space.
|
||||
|
||||
The `asm-foreign-callable` function returns 4 values:
|
||||
|
||||
* c-init : -> L13.Effect
|
||||
* `c-init : -> L13.Effect`
|
||||
|
||||
Anything that needs to be done just before transitioning into
|
||||
Scheme, such as saving preserved registers that call be used within
|
||||
the callable stub.
|
||||
|
||||
* c-args : (listof (uvar/reg -> L13.Effect))
|
||||
* `c-args : (listof (uvar/reg -> L13.Effect))`
|
||||
|
||||
Similar to the `asm-foreign-call` result case, but each function
|
||||
should fill a destination variable form platform-specific argument
|
||||
|
@ -807,7 +936,7 @@ The `asm-foreign-callable` function returns 4 values:
|
|||
- integer or pointer: a 'uptr-typed variable to receive the value
|
||||
- "&": a 'uptr-typed variable to receive the pointer
|
||||
|
||||
* c-result : (uvar/reg -> L13.Effect) or (-> L13.Effect)
|
||||
* `c-result : (uvar/reg -> L13.Effect) or (-> L13.Effect)`
|
||||
|
||||
Similar to the `asm-foreign-call` argument cases, but for a
|
||||
floating-point result, the given result register holds pointer to a
|
||||
|
@ -815,7 +944,7 @@ The `asm-foreign-callable` function returns 4 values:
|
|||
`c-result` takes no argument (because the destination pointer was
|
||||
already produced or there's no result).
|
||||
|
||||
* c-return : (-> L13.Effect)
|
||||
* `c-return : (-> L13.Effect)`
|
||||
|
||||
Generate the code for a C return, including any teardown needed to
|
||||
balance `c-init`.
|
||||
|
|
Loading…
Reference in New Issue
Block a user