Compiler Steps 7' to 9': C code generation


Step 7': C-specific rewrite rules

This step applies scheme-to-scheme rewrite rules that are required for C and not for assembler. Specifically:

Expanding the 1D conversions simplifies generation of C code in step 8'. The packing rules are careful to avoid nested sequences of pack and unpack instructions.

Step 8': C code generation

This step translates the scheme code into C syntax. This is done in one recursive descent through the code. The code for handling each individual operation pushes fragments of code (strings) onto a global list. When the C code is written to a file, the items in the list will be written to the file one-by-one, in order. This avoids repeated calls to string append (very slow in Scheme48).

The code generator uses the type information provided by steps 1-6 to write type declarations for variables. It is also used to distinguish integer and real versions sheet-domain-scaling, sheet-codomain-scaling, sheet-domain-offset, and sheet-codomain-offset. The function make-point is handled in four cases: real 2D, real 3D, integer 2D, and integer 3D.

For efficiency reasons, the C code allocates all of its space on the C stack. When a point must be allocated, one of two strategies is used. Points may be created and returned by calls to library functions such as binary-atan. The overhead of this is acceptable when the operation itself is relatively slow, but unacceptable for simple arithmetic operations.

Therefore, the compiler allocates four arrays of point structures on the C stack, at the start of the function. In-line coded operations that wish to return a point deposit their data (one or more numerical coordinates) in a pre-assigned location in the appropriate array, and return the corresponding array entry (a point structure). Operations in different branches of the recursive descent can share a common location. (Notice that points behave like numbers, not like cons cells, in terms of usage and modifiability.)

Two of the four arrays contain generic points, one array for integers and one array for reals. (The coprocessor allocates the same structure for points of all dimensions.) When data is deposited into a location in these arrays, the dimension and missing/non-missing fields must be set.

The other two arrays handle the special, but very common, case of 1D points (real and integer) known not to be missing. The dimension and missing/non-missing fields are pre-set at the start of the C code. Therefore, when data is deposited into locations in these arrays, it is only necessary to set a single numerical field.

The generic arrays are used by make-missing and make-point. The specialized 1D arrays are used by integer-pack and real-pack (introduced in step 7').

C library functions

When implementing the following operations, the C code must check for certain potential arithmetic exceptions and return a missing value if they would occur. Since all of these are relatively slow math library functions, we can call a special C function which does the tests and calls the library function, rather than coding the tests in-line.

Step 9': C wrapper generation

This step generates the wrapper for each compiled function.

Compiled functions call one another directly, passing their arguments in the normal way and returning values by setting local variables provided by the calling function (because there may be multiple return values). When code is called from the coprocessor interpreter, however, inputs must be retrieved from the coprocessor's stack and return values left there. The wrapper for each compiled function does this housekeeping.

It would be nice if all compiled functions could use a common wrapper. However, the work done by a wrapper changes with the number of input arguments and return values. And C requires the wrapper's local variables to be declared with the correct type, which also varies. It's not obvious how a general-purpose wrapper could be written in C.

Wrappers could be eliminated by having each compiled function include instructions to manipulate the stack as required. However, this would force them to use the stack even when one compiled function calls another, a major inefficiency if the function call lies inside a scan loop enumerating all locations in an image. To us, efficiency of the fully compiled calls was more important than that of the interpreted interface.


Ownership, Maintenance and Disclaimers

Manual Top Page

Last modified