Chapter 3

Compiling Individual Files with mzc

To compile an individual file with mzc, provide the file name as the command line argument to mzc. To compile to byte code, use the -z or --zo flag; to compile to native code, use the -e or --extension flag. If no compilation mode flag is specified, --extension is assumed.

The input file must have a file extension that designates it as a Scheme file, either .ss or .scm. The output file will have the same base name and same directory (by default) as the input file, but with an extension appropriate to the type of the output file (either .zo, .dll, or .so).

Example:

mzc --extension file.ss

Under Windows, the above command reads file.ss from the current directory and produces file.dll in the current directory.

Multiple Scheme files can be specified for compilation at once. A separate compiled file is produced for each Scheme file. By default, each compiled file is placed in the directory containing the corresponding input file. When multiple files are compiled at once, macros defined in a file are visible in the files that are compiled afterwards.

3.1  Compiling with Modules

In terms of both optimization and proper loading of syntax definitions, mzc works best with programs that are encapulated within per-file module expressions. Using a single module expression in a file eliminates the code's dependence on the top-level environment. Consequently, all dependencies of the code on loadable syntax extensions are evident to the compiler.

When compiling a module that requires another module (that is not built into MzScheme), mzc loads the required module, but does not invoke it. Instead, mzc uses the loaded module only for its syntax exports, if any (which means that mzc executes the transformer code in the module, but not any of its normal code).

3.2  Compilation without Modules

Top-level define-syntax(es), module, require, require-for-syntax, and begin expressions are handled specially by mzc: the compile-time portion of the expression is evaluated, because it might affects later expressions.2 For example, when compiling the file containing

(require (lib "etc.ss"))
(define f (opt-lambda (a [b 7]) (+ a b)))

the opt-lambda syntax from the "etc.ss" library must be bound in the compilation namespace at compile time. Thus, the require expression is both compiled (to appear in the output code) and evaluated (for further computation).

Many definition forms expand to define-syntax. For example, define-signature expands to a define-syntax definition. mzc detects define-syntax and other expressions after expansion, so top-level define-signature expressions affect the compilation of later expressions, as a programmer would expect.

In contrast, a load or eval expression in a source file is compiled -- but not evaluated! -- as the source file is compiled. Even if the load expression loads syntax or signature definitions, these will not be loaded as the file is compiled. The same is true of application expressions that affect the reader, such as (read-case-sensitive #t).

mzc's -p or --prefix flag takes a file and loads it before compiling the source files specified on the command line. In general, a better solution is to put all compiled code into module expressions, as explained in section 3.1.

Note that MzScheme provides no eval-when form for controlling the evaluation of compiled code, because module provides a simpler and more consistent interface for separating compile-time and run-time code.

3.3  Autodetecting Compiled Files for Loading

When MzScheme's load/use-compiled, load-relative, or require is used to load a file, MzScheme automatically detects an alternate byte-code and/or native-code file that resides near the requested file. Byte-code files are found in a compiled subdirectory in the directory of the requested file. Native-code files are found in (build-path dir "compiled" "native" (system-library-subpath)) where dir is the directory of the requested file. A byte-code or native-code file is used in place of the requested file only if its modification date is later than the requested file, or if the requested file does not exist. If both byte-code and native-code files can be loaded, the native-code file is loaded.

Example:

mzc --extension --destination compiled/native/i386-linux file.ss

Under Linux, the above command compiles file.ss in the current directory and produces compiled/native/i386-linux/file.so. Evaluating (load/use-compiled "file.ss") in MzScheme will then load compiled/native/i386-linux/file.so instead of file.ss. If file.ss is changed without recreating file.so, then load/use-compiled loads file.ss, because file.so is out-of-date.

3.4  Compiling Multiple Files to a Single Native-Code Library

When the -o or --object flag is provided to mzc, .kp and .o/.obj files are produced instead of a loadable library. The .o/.obj files contain the native code for a single source file. The .kp files contain information used for global optimizations.

Multiple .kp and .o/.obj files are linked into a single library using mzc with the -l or --link-extension flag. All of the .kp and .o/.obj files to be linked together are provided on the command line to mzc. The output library is always named _loader.so or _loader.dll.

Example:

mzc --object file1.ss
mzc --object file2.ss
mzc --link-extension file1.kp file1.o file2.kp file2.o

Under Unix, the above commands produce a _loader.so library that encapsulates both file1.ss and file2.ss.

Loading _loader into MzScheme is not quite the same as loading all of the Source files that are encapsulated by _loader. The return value from (load-extension "_loader.so") is a procedure that takes a symbol or #t. If a symbol is provided and it is the same as the base name of a source file (i.e., the name without a path or file extension) encapsulated by _loader, then a thunk is returned, along with a symbol (or #f) indicating a module name declared by the file. Applying the thunk has the same effect as loading the corresponding source file. If a symbol is not recognized by the _loader procedure, #f is returned instead of a thunk. If #t is provided, a thunk is returned that ``loads'' all of the files (using the order of the .o/.obj files provided to mzc) and returns the result from loading the last one.

The _loader procedure can be called any number of times to obtain thunks, and each thunk can be applied any number of times (where each application has the same effect as loading the source file again). Evaluating (load-extension "_loader.so") multiple times returns an equivalent loader procedure each time.

Given the _loader.so constructed by the example commands above, the following Scheme expressions have the same effect as loading file1.ss and file2.ss:

(let-values ([(go modname) ((load-extension "_loader.so") 'file1)]) (go))
(let-values ([(go modname) ((load-extension "_loader.so") 'file2)]) (go))

or, equivalently:

(let-values ([(go modname) ((load-extension "_loader.so") #t)]) (go))

The special _loader convention is recognized by MzScheme's load/use-compiled, load-relative, and require. MzScheme automatically detects _loader.so or _loader.dll in the same directory as individual native-code files (see section 3.3). If both an individual native-code file and a _loader are available, the _loader file is used.


2 The -m or --module flag turns off this special handling.