Chapter 10. System Operations

This chapter describes operations for handling errors, interrupts, and exceptions, compilation and evaluation, controlling the operation of the system, saving heaps, timing and statistics, defining and setting parameters, and querying the operating system environment.


Section 10.1. Errors, Interrupts, and Exceptions

Chez Scheme defines a set of mechanisms for signaling errors and other events and for controlling the action of the Scheme system when various events occur, including errors signaled by the program or system, an interrupt from the keyboard, the expiration of an internal timer set by set-timer, a breakpoint caused by a call to break, or a request from the storage manager to initiate a garbage collection. These mechanisms are described in this section, except for the collect request mechanism, which is described in Section 11.1.

Timer, keyboard, and collect request interrupts are supported via a counter that is decremented approximately once for each call to a nonleaf procedure. (A leaf procedure is one that does not itself make any calls.) When no timer is running, this counter is set to a default value (1000 in Version 6) when a program starts or after an interrupt occurs. If a timer is set (via set-timer), the counter is set to the minimum of the default value and the number of ticks to which the timer is set. When the counter reaches zero, the system looks to see if the timer is set and has expired or if a keyboard or collect request interrupt has occurred. If so, the current procedure call is pended ("put on hold") while the appropriate interrupt handler is invoked to handle the interrupt. When (if) the interrupt handler returns, the pended call takes place. Thus, timer, keyboard, and collect request interrupts effectively occur synchronously with respect to the procedure call mechanism, and keyboard and collect request interrupts may be delayed by a number of calls equal to the default timer value.

Calls to error, warning, and break handlers occur immediately whenever the error, warning, or break is signaled.


procedure: (error symbol string object ...)
returns: does not return

The arguments to error are passed along to the current error handler (see error-handler). If the handler returns, error resets to the current café.

The default error handler uses the arguments to print a descriptive message. The symbol identifies where the error occurred, the string is a format string (see format) describing the circumstances, and the objects, if any, are additional format arguments. The first argument is ignored if it is #f instead of a symbol. After formatting and printing the message, the default error handler saves the continuation of the error (see debug) and resets.

(define slow-vector-map
 ; an allocation-intensive implementation of vector-map
  (lambda (p v)
    (unless (procedure? p)
       (error 'vector-map "~s is not a procedure" p))
    (unless (vector? v)
       (error 'vector-map "~s is not a vector" v))
    (list->vector (map p (vector->list v)))))


parameter: error-handler

The value of this parameter must be a procedure. The current error handler is called by error, which passes along its arguments. See error for a description of the default error handler. The following example shows how to install a variant of the default error handler that invokes the debugger directly (using break).

(error-handler
  (lambda (who msg . args)
    (fprintf (console-output-port)
             "~%Error~a: ~a.~%"
             (if who (format " in ~s" who) "")
             (parameterize ([print-level 3] [print-length 6])
                (apply format msg args)))
    (break)))

A quick-and-dirty alternative with similar behavior is to simply set error-handler to break.

The version below exits immediately to the shell with a nonzero error status, which is especially useful when running Chez Scheme from a "make" file:

(error-handler
  (lambda (who msg . args)
    (fprintf (console-output-port)
             "~%Error~a: ~a.~%"
             (if who (format " in ~s" who) "")
             (parameterize ([print-level 3] [print-length 6])
                (apply format msg args)))
    (abort)))


procedure: (warning symbol string object ...)
returns: unspecified

The arguments to warning follow the protocol described above for error. The arguments to warning are passed to the current warning handler (see warning-handler). Unlike the default error handler, the default warning handler simply displays a message and returns.


parameter: warning-handler

The value of this parameter must be a procedure. The current warning handler is called by warning, which passes along its arguments. See warning for a description of the default warning handler. The following example shows how to install a warning handler that resets instead of continuing.

(warning-handler
  (let ([old-warning-handler (warning-handler)])
    (lambda args
      (apply old-warning-handler args)
      (reset))))

It is even simpler to turn warnings into errors with

(warning-handler (error-handler))

or

(warning-handler error)


procedure: (break symbol string object ...)
procedure: (break symbol)
procedure: (break)
returns: unspecified

The arguments to break follow the protocol described above for error. The default break handler (see break-handler) displays a message and invokes the debugger. The format string and objects may be omitted, in which case the message issued by the default break handler identifies the break using the identifying symbol but provides no more information about the break. If the identifying symbol is omitted as well, no message is generated. The default break handler returns normally if the debugger exits normally.


parameter: break-handler

The value of this parameter must be a procedure. The current break handler is called by break, which passes along its arguments. See break for a description of the default break handler. The example below shows how to disable breaks.

(break-handler (lambda args (void)))


parameter: keyboard-interrupt-handler

The value of this parameter must be a procedure. The keyboard-interrupt handler is called (with no arguments) when a keyboard interrupt occurs. The default keyboard-interrupt handler invokes the interactive debugger. If the debugger exits normally the interrupted computation is resumed. The example below shows how to install a keyboard-interrupt handler that resets without invoking the debugger.

(keyboard-interrupt-handler 
  (lambda ()
    (newline (console-output-port))
    (reset)))


procedure: (set-timer n)
returns: previous current timer value

n must be a nonnegative integer. When n is nonzero, set-timer starts an internal timer with an initial value of n. When n ticks elapse, a timer interrupt occurs, resulting in invocation of the timer interrupt handler. Each tick corresponds roughly to one nonleaf procedure call (see the introduction to this section); thus, ticks are not uniform time units but instead depend heavily on how much work is done by each procedure call.

When n is zero, set-timer turns the timer off.

The value returned in either case is the value of the timer before the call to set-timer. A return value of 0 should not be taken to imply that the timer was not on; the return value may also be 0 if the timer was just about to fire when the call to set-timer occurred.

The engine mechanism (Section 5.3) is built on top of the timer interrupt so timer interrupts should not be used with engines.


parameter: timer-interrupt-handler

The value of this parameter must be a procedure. The timer interrupt handler is called by the system when the internal timer (set by set-timer) expires. The default handler signals an error to say that the handler has not been defined; any program that uses the timer should redefine the handler before setting the timer.


procedure: (disable-interrupts)
procedure: (enable-interrupts)
returns: disable count

disable-interrupts disables the handling of interrupts, including timer, keyboard, and collect request interrupts. enable-interrupts reenables these interrupts. Calls to disable-interrupts are counted by the system, and it takes as many calls to enable-interrupts as calls to disable-interrupts to cause interrupt handling to be reenabled. For example, two calls to disable-interrupts followed by one call to enable-interrupts leaves interrupts disabled. The value returned by either procedure is the number of calls to enable-interrupts required to enable interrupts. This value is always nonnegative; calls to enable-interrupts when interrupts are already enabled have no effect.

Great care should be exercised when using these procedures, since disabling interrupts inhibits the normal processing of keyboard interrupts, timer interrupts, and, perhaps most importantly, collect request interrupts. Since garbage collection does not happen automatically when interrupts are disabled, it is possible for the storage allocator to run out of space unnecessarily should interrupts be disabled for a long period of time.

The critical-section syntactic form should be used instead of these more primitive procedures whenever possible, since critical-section ensures that interrupts are reenabled whenever a nonlocal exit occurs, such as when an error is handled by the default error handler.


syntax: (critical-section exp1 exp2 ...)
returns: result of the last expression

critical-section evaluates the expressions exp1 exp2 ... in sequence (as if in an implicit begin), without interruption. That is, upon entry to the critical section, interrupts are disabled, and upon exit, interrupts are reenabled. Thus, critical-section allows the implementation of indivisible operations. critical-section can be defined as follows.

(define-syntax critical-section
  (syntax-rules ()
    ((_ e1 e2 ...)
     (dynamic-wind
       disable-interrupts
       (lambda () e1 e2 ...)
       enable-interrupts))))

The use of dynamic-wind ensures that interrupts are disabled whenever the body of the critical-section expression is active and reenabled whenever it is not. Since calls to disable-interrupts are counted (see the discussion under disable-interrupts and enable-interrupts above), critical-section expressions may be nested with the desired effect.


procedure: (register-signal-handler sig proc)
returns: unspecified

register-signal-handler is used to establish a signal handler for a given low-level signal. sig must be an exact integer identifying a valid signal, and proc must be a procedure that accepts one argument. See your host system's <signal.h> or documentation for a list of valid signals and their numbers. After a signal handler for a given signal has been registered, receipt of the specified signal results in a call to the handler. The handler is passed the signal number, allowing the same handler to be used for different signals while differentiating among them.

Signals handled in this fashion are treated like keyboard interrupts in that the handler is not called immediately when the signal is delivered to the process, but rather at some procedure call boundary after the signal is delivered. It is generally not a good idea, therefore, to establish handlers for memory faults, illegal instructions, and the like, since the code that causes the fault or illegal instruction will continue to execute (presumably erroneously) for some time before the handler is invoked.

register-signal-handler is supported only on Unix-based systems.


Section 10.2. Compilation and Evaluation


procedure: (eval obj)
procedure: (eval obj env-spec)
returns: value of the Scheme form represented by obj

env-spec must be an environment specifier returned by interaction-environment, scheme-report-environment, null-environment, or ieee-environment. eval treats obj as the representation of an expression. It evaluates the expression in the specified environment and returns its value. If no environment specifier is provided, it defaults to the specifier returned by interaction-environment.

Single-argument eval is a Chez Scheme extension, as is ieee-environment. Chez Scheme also permits obj to be the representation of a nonexpression form, i.e., a definition, whenever the environment specifier is omitted or is the specifier returned by interaction-environment.

In Chez Scheme, eval is actually a wrapper that simply passes its arguments to the current evaluator. (See current-eval.) The default evaluator is compile, which expands the expression via the current expander (see current-expand), compiles it, executes the resulting code, and returns its value. If the environment specifier, env-spec, is present, compile passes it along to the current expander, which is sc-expand by default.


procedure: (scheme-report-environment version)
procedure: (null-environment version)
returns: see below

version must be an exact integer. This integer specifies a revision of the Revised Report on Scheme, i.e., the Revisedv Report on Scheme for version v.

scheme-report-environment returns a specifier for an environment that is empty except for all bindings defined in the specified report that are either required or both optional and supported by the implementation. null-environment returns a specifier for an environment that is empty except for the bindings for all syntactic keywords defined in the specified report that are either required or both optional and supported by the implementation. (Chez Scheme supports all optional bindings as well as required bindings.)

scheme-report-environment and null-environment must accept the value of version that corresponds to the most recent Revised Report that the implementation claims to support, starting with the Revised5 Report. They may also accept other values of version. An error is signaled if the implementation does not support the given version.

The effect of assigning (through the use of eval) a variable bound in the scheme-report environment is unspecified; thus, the environment may be immutable.


procedure: (ieee-environment)
returns: an environment specifier

ieee-environment returns a specifier for an environment that is empty except for all bindings defined in the ANSI/IEEE Scheme standard.


procedure: (interaction-environment)
returns: an environment specifier

interaction-environment returns an environment specifier that represents an environment containing implementation-dependent bindings. This environment is typically the one in which the implementation evaluates expressions dynamically typed by the user. In Chez Scheme, the specifier returned by interaction-environment corresponds to the top-level environment.


parameter: current-eval

current-eval determines the evaluation procedure used by the procedures eval, load, and new-cafe. current-eval is initially bound to the value of compile. It should expect one or two arguments: an object to evaluate and an optional environment specifier.

(current-eval interpret)
(+ 1 1)  2

(current-eval (lambda (x . ignore) x))
(+ 1 1)  (+ 1 1)


procedure: (compile obj)
procedure: (compile obj env-spec)
returns: value of the Scheme form represented by obj

obj is treated as a Scheme expression, expanded with the current expander (see current-expand) in the specified environment (or the interaction environment, if no environment specifier is provided), compiled to machine code, and executed. compile is the default value of the current-eval parameter.


procedure: (interpret obj)
procedure: (interpret obj env-spec)
returns: value of the Scheme form represented by obj

interpret is like compile, except that the expression is interpreted rather than compiled. interpret may be used as a replacement for compile, with the following caveats:

interpreter is often faster than compile when the form to be evaluated is short running, since it has less preevaluation overhead.


procedure: (load filename)
procedure: (load filename eval-proc)
returns: unspecified

filename must be a string. load reads and evaluates the contents of the file specified by filename. The file may contain source or object code or a mixture of both. By default, load employs eval to evaluate each source expression found in the file. If eval-proc is specified, load uses this procedure instead. eval-proc must accept one argument, the expression to evaluate.

The eval-proc argument facilitates the implementation of embedded Scheme-like languages and the use of alternate evaluation mechanisms to be used for Scheme programs. eval-proc can be put to other uses as well. For example, (load "myfile.ss" pretty-print) pretty-prints the contents of the file "myfile.ss" to the screen, and

(load "myfile.ss"
  (lambda (x)
    (write x)
    (newline)
    (eval x)))

writes each expression before evaluating it.


procedure: (visit filename)
returns: unspecified

filename must be a string. Like load, visit reads the contents of the file specified by filename. Unlike load, visit does not perform any evaluation. Instead, it installs any compile-time information generated by compile-file for use in subsequent compilation. For example, if the file t1.ss contains the following forms:

(define-syntax a (identifier-syntax 3))
(module m (x) (define x 4))
(define y 5)

applying load to t1.ss has the effect of defining a, m, and y, whereas applying visit to t1.ss has no effect. If t1.ss is compiled to t1.so, applying load to t1.so again has the effect of defining all three identifiers. Applying visit to t1.so, however, has the effect of installing the transformer for a, installing the interface for m (for use by import), and recording y as a variable. visit is useful when separately compiling one file that depends on syntactic abstractions and modules defined in another without actually loading and evaluating the code in the supporting file.


procedure: (compile-file input-filename)
procedure: (compile-file input-filename output-filename)
procedure: (compile-file input-filename output-filename machine-type)
returns: unspecified

input-filename and output-filename must be strings, and machine-type must be a valid machine-type specifier.

The normal evaluation process proceeds in two steps: compilation and execution. compile-file performs the compilation process for an entire source file, producing an object file. When the object file is subsequently loaded (see load), the compilation process is not necessary, and the file typically loads several times faster.

If the optional output-filename argument is omitted, an extension of ".ss" is assumed for the input (source) filename input-filename, and this extension is replaced by an extension of ".so" in the output (object) filename. In this case, the input filename may be entered with or without the ".ss" extension. If the optional output-filename argument is specified, the file names are used exactly as specified. For example, (compile-file "myfile") produces an object file with the name "myfile.so" from the source file named "myfile.ss", while (compile-file "myfile1" "myfile2") produces an object file with the name "myfile2" from the source file name "myfile1".

By default, compile-file produces object code for the current machine type (see machine-type). compile-file can be used to generate object code for another machine type, however, by specifying a different machine type, if the assembler for that machine type has been loaded.


procedure: (compile-port input-port output-port)
procedure: (compile-port input-port output-port machine-type)
returns: unspecified

compile-port is like compile-file except that it takes input from an arbitrary input port and sends output to an arbitrary output port. Neither port is closed automatically after compilation; it is assumed that the program that opens the ports and invokes compile-port will take care of closing the ports.


procedure: (machine-type)
returns: the current machine type

The value returned by machine-type may be used as the third argument to compile-file. Consult the online documentation distributed with the Chez Scheme software for information on valid machine types.


procedure: (expand obj)
procedure: (expand obj env-spec)
returns: expansion of the Scheme form represented by obj

env-spec must be an environment specifier returned by interaction-environment, scheme-report-environment, null-environment, or ieee-environment. If no environment specifier is provided, the specifier returned by interaction-environment is assumed.

expand passes its arguments to the current expander (see current-expand), initially sc-expand, which expands the form in the specified environment and returns a new object representing the expanded form.


parameter: current-expand

current-expand determines the expansion procedure used by the compiler, interpreter, and direct calls to expand to expand syntactic extensions. current-expand is initially bound to the value of sc-expand.

current-expand may be set to eps-expand to enable support for the older expansion-passing-style macros. It may be set to other procedures as well, but since the format of expanded code expected by the compiler and interpreter is not publicly documented, only sc-expand and eps-expand produce correct output.


procedure: (sc-expand obj)
procedure: (sc-expand obj env-spec)
procedure: (eps-expand obj)
procedure: (eps-expand obj env-spec)
procedure: (eps-expand-once obj)
procedure: (eps-expand-once obj env-spec)
returns: the expanded form of obj

The procedure sc-expand is used to expand programs written using syntax-case macros. sc-expand is the default expander, i.e., the initial value of current-expand. obj represents the program to be expanded. env-spec must be an environment specifier returned interaction-environment, scheme-report-environment, null-environment, or ieee-environment. If not provided, env-spec defaults to the specifier returned by interaction-environment.

eps-expand is used to expand programs that use expansion-passing style macros. (See Section 12.1.) eps-expand-once is used to expand expansion-passing-style macro calls one level only. The eps expander does not support any environment specifiers other than the one returned by interaction-environment.


syntax: (eval-when situations form1 form2 ...)
returns: see below

situations must be a list containing some combination of the symbols eval, compile, and load.

When source files are loaded (see load), the forms in the file are read, compiled, and executed sequentially, so that each form in the file is fully evaluated before the next one is read. When a source file is compiled (see compile-file), however, the forms are read and compiled, but not executed, in sequence. This distinction matters only when the execution of one form in the file affects the compilation of later forms, e.g., when the form results in the definition of a new module or syntactic form or sets a compilation parameter such as optimize-level or case-sensitive.

For example, assume that a file contains the following two forms:

(define-syntax reverse-define
  (syntax-rules ()
    ((_ v x) (define x v))))

(reverse-define 3 three)

Loading this from source has the effect of defining a new syntactic form, reverse-define, and binding the identifier three to 3. The situation may be different if the file is compiled with compile-file, however. Unless the system or programmer takes steps to assure that the first form is fully executed before the second expression is compiled, the syntax expander will not recognize reverse-define as a syntactic form and will generate code for a procedure call to reverse-define instead of generating code to define three to be 3. When the object file is subsequently loaded, the attempt to reference either reverse-define or three will fail.

As it happens, when a define-syntax, module, import, or import-only form appears at top level, as in the example above, the compiler does indeed arrange to evaluate it before going on to compile the remainder of the file. The compiler also generates the appropriate code so that the bindings will be present as well when the object file is subsequently loaded. This solves most, but not all, problems of this nature, since most are related to the use of define-syntax and modules. Some problems are not so straightforwardly handled, however. For example, assume that the file contains the following definitions for and .

(define nodups?
  (lambda (ids)
    (define bound-id-member?
      (lambda (id ids)
        (and (not (null? ids))
             (or (bound-identifier=? id (car ids))
                 (bound-id-member? id (cdr ids))))))
    (or (null? ids)
        (and (not (bound-id-member? (car ids) (cdr ids)))
             (nodups? (cdr ids))))))

(define-syntax mvlet
  (lambda (x)
    (syntax-case x ()
      [(_ ((x ...) mvexp) e1 e2 ...)
       (and (andmap identifier? #'(x ...))
            (nodups? #'(x ...)))
       #'(call-with-values
           (lambda () mvexp)
           (lambda (x ...) e1 e2 ...))])))

(mvlet ((a b c) (values 1 2 3))
  (list (* a a) (* b b) (* c c)))

When loaded directly, this results in the definition of nodups? as a procedure and mvlet as a syntactic abstraction before evaluation of the mvlet expression. Because nodups? is defined before the mvlet expression is expanded, the call to nodups? during the expansion of mvlet causes no difficulty. If instead this file were compiled, using compile-file, the compiler would arrange to define mvlet before continuing with the expansion and evaluation of the mvlet expression, but it would not arrange to define nodups?. Thus the expansion of the mvlet expression would fails.

In this case it does not help to evaluate the syntactic extension alone. A solution in this case would be to move the definition of nodups? inside the definition for mvlet, just as the definition for bound-id-member? is placed within nodups?, but this does not work for help routines shared among several syntactic definitions.

A somewhat simpler problem occurs when setting parameters that affect compilation, such as optimize-level and case-sensitive?. If not set prior to compilation, their settings usually will not have the desired effect.

eval-when offers a solution to these problems by allowing the programmer to explicitly control what forms should or should not be evaluated during compilation. eval-when is a syntactic form and is handled directly by the expander. The action of eval-when depends upon the situations argument and whether or not the forms form1 form2 ... are being compiled via compile-file or are being evaluated directly. We will consider each of the possible situation specifiers compile, load, and eval in turn.

compile:
The compile specifier is relevant only when the eval-when form appears in a file currently being compiled. (Its presence is simply ignored otherwise.) Its presence forces form1 form2 ... to be expanded and evaluated immediately.

load:
The load specifier is also relevant only when the eval-when form appears in a file currently being compiled. Its presence causes form1 form2 ... to be expanded and this expansion to be included in the expansion of the eval-when form. Thus, the forms will be compiled as if not contained within an eval-when form and executed when the resulting object file is subsequently loaded.

eval:
The eval specifier is relevant only when the eval-when form is being evaluated directly, i.e., if it is typed at the keyboard or loaded from a source file. Its presence causes form1 form2 ... to be expanded and this expansion to be included in the expansion of the eval-when form. Thus, the forms will be evaluated directly as if not contained within an eval-when form.

Outside of any eval-when form, the system treats all top-level forms as if they are wrapped in an eval-when with situations load and eval. This means that, by default, forms typed at the keyboard or loaded from a source file are evaluated, and forms appearing in a file to be compiled are not evaluated directly but are compiled for execution when the resulting object file is subsequently loaded. Top-level syntax definitions, module forms, and import forms are an exception to this rule; these are by default treated as if wrapped in an eval-when with situations compile, load, and eval, forcing direct evaluation during compilation as well as execution at load and direct evaluation time. (This behavior may be adjusted by altering the value of the parameter eval-syntax-expanders-when, described below.)

We could solve the second problem above by enclosing the definition of nodups? in an eval-when as follows:

(eval-when (compile load eval)
  (define nodups?
    (lambda (ids)
      (define bound-id-member?
        (lambda (id ids)
          (and (not (null? ids))
               (or (bound-identifier=? id (car ids))
                   (bound-id-member? id (cdr ids))))))
      (or (null? ids)
          (and (not (bound-id-member? (car ids) (cdr ids)))
               (nodups? (cdr ids)))))))

thus forcing it to be evaluated before it is needed during the expansion of the mvlet expression.

Just as it is useful to add compile to the default load and eval situations, omitting options is also useful. Omitting one or more of compile, load, and eval has the effect of preventing the evaluation at the given time. Omitting all of the options has the effect of inhibiting evaluation altogether.

One common combination of situations is (compile eval), which by the inclusion of compile causes the expression to be evaluated at compile time, and by the omission of load inhibits the generation of code by the compiler for execution when the file is subsequently loaded. This is typically used for the definition of syntactic extensions used only within the file in which they appear; in this case their presence in the object file is not necessary. It is also used to set compilation parameters that are intended to be in effect whether the file is loaded from source or compiled via compile-file

Another common situations list is (compile), which might be used to set compilation options to be used only when the file is compiled via compile-file.

Finally, one other common combination is (load eval), which might be useful for inhibiting the double evaluation (during the compilation of a file and again when the resulting object file is loaded) of syntax definitions when the syntactic extensions are not needed within the file in which their definitions appear.


syntax: eval-syntax-expanders-when

This parameter must be set to a list representing a set of eval-when situations, e.g., a list containing at most one occurrence of each of the symbols compile, load, and eval. It is used to determine the evaluation time of syntax definitions, module forms, and import forms are expanded. (See the discussion of eval-when above.) The default value is (compile load eval), which causes syntax definitions in a file to be defined when the file is loaded from source, when it is compiled via compile-file, and when a compiled version of the file is loaded.


Section 10.3. Compiler Controls


parameter: optimize-level

Chez Scheme provides four levels of optimization, selectable by changing the value of the parameter optimize-level. The parameter can take on one of the four values

0:
(the default) partial optimization, no inlining, fully safe;
1:
full optimization, no inlining, fully safe;
2:
full optimization, primitives inlined, fully safe; and
3:
full optimization, primitives inlined, unsafe.

At level 0, the compiler attempts to perform simple optimizations only in order to minimize compilation time. Code generated by the compiler at level 0 performs full type and bounds checking. At level 1, the compiler attempts to perform more optimizations (with full type and bounds checking), but does not inline system primitives so that tracing or redefining, for example, car will have the desired effect. At level 2, the compiler assumes that any system primitive referenced in the code will never be redefined, and it may choose to generate more efficient inline code (with full type and bounds checking) for the primitive. At level 3, the compiler makes the same assumptions as at level 2 and may choose to generate even more efficient, but unsafe (that is, without full type and bounds checking), inline code for any system primitive.

Because primitives are inlined at optimize levels 2 and 3, the compiler issues warnings about assignments to variables that name system primitives within code compiled at these levels.

The most common way to use optimize levels is on a per-file basis, using eval-when to force the use of a particular optimize level at compile time. For example, placing:

(eval-when (compile) (optimize-level 2))

at the front of a file will cause all of the forms in the file to be compiled at optimize level 2 when the file is compiled (using compile-file) but does not affect the optimize level used when the file is loaded from source. Since compile-file parameterizes optimize-level (see parameterize), the above expression does not permanently alter the optimize level in the system in which the compile-file is performed.

Optimization level 3 should be used with caution and only on small sections of well-tested code that must run as quickly as possible. Without type and bounds checking, it is possible to corrupt the Scheme heap, something that often results in strange behavior far removed from the source of the problem.


syntax: (\#primitive variable)
syntax: #%variable
syntax: (\#primitive 2 variable)
syntax: #2%variable
syntax: (\#primitive 3 variable)
syntax: #3%variable
returns: the primitive value for variable

variable must name a primitive procedure. The \#primitive syntactic form allows control over the optimize level at the granularity of individual primitive references, and it can be used to access the original value of a primitive, regardless of the lexical context or the current top-level binding for the variable originally bound to the primitive.

Since printed variable names cannot start with the character "#", it is necessary to prepend a slash to the name of this syntactic keyword, e.g., \#primitive, or to enclose the name in vertical bars, e.g., |#primitive|.

The expression (\#primitive symbol) may be abbreviated as #%symbol. The reader expands #% followed by an object into a \#primitive expression, much as it expands 'object into a quote expression.

If a 2 or 3 appears in the form or between the # and % in the abbreviated form, the compiler treats an application of the primitive as if it were compiled at the corresponding optimize level (see the optimize-level parameter). If no number appears in the form, an the application of the primitive is treated as an optimize-level 3 application if the current optimize level is 3; otherwise, it is treated as an optimize-level 2 application.

(#%car '(a b c))  a
(let ([car cdr]) (car '(a b c)))  (b c)
(let ([car cdr]) (#%car '(a b c)))  a
(begin (set! car cdr) (#%car '(a b c)))  a


parameter: generate-interrupt-trap

To support interrupts, including keyboard, timer, and collect request interrupts, the compiler inserts a short sequence of instructions at the entry to each nonleaf procedure. (See the introduction to Section 10.1.) This small overhead may be eliminated by setting generate-interrupt-trap to #f. The default value of this parameter is #t.

It is rarely a good idea to compile code without interrupt trap generation, since a tight loop in the generated code may completely prevent interrupts from being serviced, including the collect request interrupt that causes garbage collections to occur automatically. Disabling trap generation may be useful, however, for routines that act simply as "wrappers" for other routines for which code is presumably generated with interrupt trap generation enabled. It may also be useful for short performance-critical routines with embedded loops or recursions that are known to be short running and that make no other calls.


parameter: compile-interpret-simple

At all optimize levels, when the value of compile-interpret-simple is set to a true value (the default), compile interprets simple expressions. A simple expression is one that creates no procedures. This can save a significant amount of time over the course of many calls to compile or eval (with current-eval set to compile, its default value). When set to false, compile compiles all expressions.


parameter: generate-inspector-information

When this parameter is set to a true value (the default), information about the source and contents of procedures and continuations is generated during compilation and retained in tables associated with each code segment. This information allows the inspector to provide more complete information, at the expense of using more memory and producing larger object files (via compile-file). Although compilation and loading may be slower when inspector information is generated, the speed of the compiled code is not affected. If this parameter is changed during the compilation of a file, the original value will be restored. For example, if:

(eval-when (compile) (generate-inspector-information #f))

is included in a file, generation of inspector information will be disabled only for the remainder of that particular file.


parameter: compile-profile

When this parameter is set to a true value, the compiler instruments the code it generates with instructions that count the number of times each section of code is executed. This information may be viewed with profview profile viewer distributed with Chez Scheme. Setting this parameter also forces source information to be retained, regardless of the setting of generate-inspector-information, since this information is required by the profile viewer. The default value of this parameter is #f. Because the code generated when compile-profile is set to #t is not only larger but less efficient, this parameter should be set only when profile information is needed.


procedure: (profile-clear)
returns: unspecified

Calling this procedure causes profile information to be cleared, i.e., the counts associated with each section of code are set to zero.


parameter: run-cp0
parameter: cp0-effort-limit
parameter: cp0-score-limit
parameter: cp0-outer-unroll-limit

These parameters control the operation of cp0, a source optimization pass that runs after macro expansion and prior to most other compiler passes. cp0 performs procedure inlining, in which the code of one procedure is inlined at points where it is called by other procedures, as well as copy propagation, constant folding, useless code elimination, and several related optimizations. The algorithm used by the optimizer is described in detail in the paper "Fast and effective procedure inlining" [23].

The value of run-cp0 must be a procedure. Whenever the compiler is invoked on a Scheme form, the value p of this parameter is called to determine whether and how cp0 is run. p receives two arguments: cp0, the entry point into cp0, and x, the form being compiled. The default value of run-cp0 simply invokes cp0 on x:

(run-cp0 (lambda (cp0 x) (cp0 x)))

Interesting variants include

(run-cp0 (lambda (cp0 x) x))

which bypasses cp0, and

(run-cp0 (lambda (cp0 x) (cp0 (cp0 x))))

which runs cp0 twice, which can result in some improvement in the generated code but obviously costs more than running it once. A more complex example is the following

(run-cp0
  (lambda (cp0 x)
    (let ((x (cp0 x)))
      (parameterize ((cp0-effort-limit 0))
        (cp0 x)))))

which runs cp0 a second time with inlining effectively disabled. This can be valuable, since inlining can sometimes produce opportunities for other optimizations that are not detected during a single cp0 pass.

The parameters cp0-effort-limit and cp0-score-limit must be set to nonnegative fixnum values. The value of cp0-effort-limit determines the maximum amount effort spent on each inlining attempt. The time spent optimizing a program is a linear function of this limit and the number of calls in the program's source, so small values for this parameter enforce a tighter bound on compile time. The value of cp0-score-limit determines the maximum amount of code produced per inlining attempt. Small values for this parameter limit the amount of overall code expansion.

The parameter cp0-outer-unroll-limit controls the amount of inlining performed by the optimizer for recursive procedures. With the parameter's value set to the default value of 0, recursive procedures are not inlined. A nonzero value for the outer unroll limit allows calls external to a recursive procedure to be inlined. For example, the expression

(letrec ((fact (lambda (x) (if (zero? x) 1 (* x (fact (- x 1)))))))
  (fact 10))

would be left unchanged with the outer unroll limit set to zero, but would be converted into

(letrec ((fact (lambda (x) (if (zero? x) 1 (* x (fact (- x 1)))))))
  (* 10 (fact 9)))

with the outer unroll limit set to one.

Interesting effects can be had by varying several of these parameters at once. For example, setting the effort and outer unroll limits to large values and the score limit to 1 has the effect of inlining even complex recursive procedures whose values turn out to be constant at compile time without risking any code expansion. For example,

(letrec ((fact (lambda (x) (if (zero? x) 1 (* x (fact (- x 1)))))))
  (fact 10))

would be reduced to 3628800, but

(letrec ((fact (lambda (x) (if (zero? x) 1 (* x (fact (- x 1)))))))
  (fact z))

would be left unchanged, although the optimizer may take a while to reach this decision if the effort and outer unroll limits are very large.


Section 10.4. Waiter Customization


procedure: (new-cafe)
procedure: (new-cafe eval-proc)
returns: see below

Chez Scheme interacts with the user through a waiter, or read-eval-print loop. The waiter operates within a context called a café. When the system starts up, the user is placed in a café and given a waiter. new-cafe opens a new Scheme café, stacked on top of the old one. In addition to starting the waiter, new-cafe sets up the café's reset and exit handlers (see reset-handler and exit-handler). Exiting a café resumes the continuation of the call to new-cafe that created the café. Exiting from the initial café leaves Scheme altogether. A café may be exited from either by an explicit call to exit or by receipt of end-of-file ("control-D" on Unix systems) in response to the waiter's prompt. In the former case, any values passed to exit are returned from new-cafe.

If the optional eval-proc argument is specified, eval-proc is used to evaluate forms entered from the console. Otherwise, the value of the parameter current-eval is used. eval-proc must accept one argument, the expression to evaluate.

Interesting values for eval-proc include expand, which causes the macro expanded value of each expression entered to be printed and (lambda (x) x), which simply causes each expression entered to be printed. An arbitrary procedure of one argument may be used to facilitate testing of a program on a series of input values.

> (new-cafe (lambda (x) x))
>> 3
3
>> (a . (b . (c . ())))
(a b c)

(define sum
  (lambda (ls)
    (if (null? ls)
        0
        (+ (car ls) (sum (cdr ls))))))
> (new-cafe sum)
>> (1 2 3)
6

The default waiter reader (see waiter-prompt-and-read) displays the current waiter prompt (see waiter-prompt-string) to the current value of the parameter console-output-port and reads from the current value of the parameter console-input-port. The default waiter printer (see waiter-write) sends output to the current value of the parameter console-output-port. These parameters, along with current-eval, can be modified to change the behavior of the waiter.


procedure: (transcript-cafe filename)

filename must be a string. transcript-cafe opens a transcript file as with transcript-on (see page 158 of The Scheme Programming Language, Second Edition) enters a new café; exiting from this café (see exit) also ends transcription and closes the transcript file. Invoking transcript-off while in a transcript café ends transcription and closes the transcript file but does not cause an exit from the café.


parameter: waiter-prompt-string

The value of waiter-prompt-string must be a string. It is used by the default waiter prompter (see the parameter waiter-prompt-and-read) to print a prompt. Nested cafés are marked by repeating the prompt string once for each nesting level.

> (waiter-prompt-string)
">"
> (waiter-prompt-string "%")
% (waiter-prompt-string)
"%"
% (new-cafe)
%% (waiter-prompt-string)
"%"


parameter: waiter-prompt-and-read

waiter-prompt-and-read must be set to a procedure. It is used by the waiter to print a prompt and read an expression. The value of waiter-prompt-and-read is called by the waiter with a positive integer that indicates the café nesting level. It should return an expression to be evaluated by the current evaluator (see new-cafe and current-eval). The following example installs a procedure equivalent to the default waiter-prompt-and-read.

(waiter-prompt-and-read
  (lambda (n)
    (unless (and (integer? n) (>= n 0))
      (error 'default-waiter-prompt-and-read
             "~s is not a nonnegative exact integer"
             n))
    (do ([n n (- n 1)])
        ((= n 0)
         (write-char #\space (console-output-port))
         (flush-output-port (console-output-port)))
        (display (waiter-prompt-string) (console-output-port)))
    (let ([x (read (console-input-port))])
      (when (eof-object? x)
        (newline (console-output-port))
        (flush-output-port (console-output-port)))
      x)))


parameter: waiter-write

The value of waiter-write must be a procedure. The waiter uses the value of waiter-write to print the results of each expression read and evaluated by the waiter. The following example installs a procedure equivalent to the default waiter-write:

(waiter-write
  (lambda (x)
    (unless (eq? x (void))
      (pretty-print x (console-output-port)))
    (flush-output-port (console-output-port))))


procedure: (abort)
returns: does not return

abort invokes the current abort handler (see abort-handler).


parameter: abort-handler

The value of this parameter must be a procedure. The current abort handler is called by abort. The default abort handler exits from Scheme.


procedure: (reset)
returns: does not return

reset invokes the current reset handler (see reset-handler).


parameter: reset-handler

The value of this parameter must be a procedure. The current reset handler is called by reset. The default reset handler resets to the current café.


procedure: (exit obj ...)
returns: does not return

exit invokes the current exit handler (see exit-handler), passing along its arguments, if any.


parameter: exit-handler

The value of this parameter must be a procedure. The current exit handler is called by exit and should accept any number of arguments. The default exit handler exits from the current café, returning its arguments as the values of the call to new-cafe that created the current café.


Section 10.5. Saved Heaps

On most systems, the contents of the Chez Scheme heap can be stored in a saved heap file via the use of the command line "-s0" option, e.g.:

scheme -s0 myheap

This starts up Scheme as usual, except that upon successful exit from the system, the contents of the heap are stored in the file named by myheap. A successful exit is one that occurs via the default exit handler with either no arguments or one or more arguments, the first of which is either zero or the void object.

A saved heap can be used in a subsequent session by invoking scheme with the "-h" option:

scheme -h myheap

It is possible to continually reuse and update a heap by using the "-h" and "-s0" options in tandem:

scheme -h myheap -s0 myheap

When the "-s0" option is used, the entire heap is saved. Once such a heap has been created, however, it is possible to create an incremental heap containing only those pages that have changed from the previous saved heap. Incremental heaps are created when the "-s" is followed by a single nonzero digit specifying the heap "level." (The heap created by "-s0" is a level-0 heap.) The following creates a level-1 heap myheap.1, containing only those pages that differ from myheap:

scheme -h myheap -s1 myheap.1

Both the level-0 and level-1 heaps must be specified using separate "-h" options (the order of which is unimportant):

scheme -h myheap -h myheap.1

To reuse and update the level-1 heap, use the "-s1" option again:

scheme -h myheap -h myheap.1 -s1 myheap.1

Care must be taken not to mix a level-0 heap with a level-1 heap created as an increment off a different level-0 heap; doing so nearly always results in immediate failure of the system, often with an invalid memory reference or illegal instruction trap. It is also necessary to use the same base executable image (scheme) that was used when the heap was created.

It is possible to create higher level heaps up through level 9. The following creates a level-2 heap myheap.2 containing only those pages that differ from myheap augmented by myheap.1:

scheme -h myheap -h myheap.1 -s2 myheap.2

On some systems, heaps are implemented as "memory mapped files." On such systems, start-up time is minimal even for large heaps, and any two processes using the same heap file (including incremental heap files) share portions that neither has changed.

On most systems, the base executable (scheme) is actually a shell script that invokes a small executable with an initial "-h" option, so users should rarely need to use the "-s0" option and should not ordinarily need to specify the "-h" option for the base heap.

The parameters scheme-start and suppress-greeting may be used to alter the system's behavior upon start-up from a saved heap.


parameter: scheme-start

The value of scheme-start is a procedure that determines the system's action upon start-up, including start-up from a saved heap. The procedure receives zero or more arguments, which are strings representing the file names given on the command line. The default value first loads the files named by the arguments, then starts up the initial café:

(lambda fns
  (for-each load fns)
  (new-cafe))

scheme-start may be altered to start up an application or to perform customization prior to normal system start-up.


parameter: suppress-greeting

The value of suppress-greeting is a boolean value that determines whether Chez Scheme prints an identifying banner and copyright notice. The parameter defaults to #f but may be set to #t for use in batch processing applications where the banner would be disruptive.


Section 10.6. Timing and Statistics


syntax: (time exp)
returns: value of exp

time evaluates exp and, as a side-effect, prints (to the console-output port) the amount of cpu time, the amount of real time, the number of bytes allocated, and the amount of collection overhead associated with evaluating exp.

> (time (collect))
(time (collect))
    1 collection
    1 ms elapsed cpu time, including 1 ms collecting
    1 ms elapsed real time, including 1 ms collecting
    160 bytes allocated, including 8184 bytes reclaimed


procedure: (display-statistics)
procedure: (display-statistics output-port)
returns: unspecified

This procedure displays a running total of the amount of cpu time, real time, bytes allocated, and collection overhead. If output-port is not supplied, it defaults to the current output port.


procedure: (cpu-time)
returns: the amount of cpu time consumed since system start-up

The amount is in milliseconds. The amount includes "system" as well as "user" time, i.e., time spent in the kernel on behalf of the process as well as time spent in the process itself.


procedure: (real-time)
returns: the amount of real time that has elapsed since system start-up

The amount is in milliseconds.


procedure: (bytes-allocated)
returns: the number of bytes currently allocated


procedure: (statistics)
returns: a sstats structure containing current statistics

statistics packages together various timing and allocation statistics into a single sstats structure. A sstats structure has the following fields:

cpu,
the cpu time consumed,
real,
the elapsed real time,
bytes,
the number of bytes currently allocated,
gc-count,
the number of collections since system start-up,
gc-cpu,
the cpu time consumed during collections,
gc-real,
the elapsed real time during collections, and
gc-bytes,
the number of bytes reclaimed by the collector since system start-up.

All times are calculated in milliseconds since system start-up.

The sstats structure and the corresponding allocation procedure, predicate, accessors, and setters described below are predefined by the system using define-structure as follows.

(define-structure
  (sstats cpu real bytes
    gc-count gc-cpu gc-real gc-bytes))


procedure: (make-sstats cpu real bytes gc-count gc-cpu gc-real gc-bytes)
returns: a sstats structure


procedure: (sstats? obj)
returns: #t if obj is a sstats structure, otherwise #f


procedure: (sstats-cpu s)
procedure: (sstats-real s)
procedure: (sstats-bytes s)
procedure: (sstats-gc-count s)
procedure: (sstats-gc-cpu s)
procedure: (sstats-gc-real s)
procedure: (sstats-gc-bytes s)
returns: the value of the corresponding field of s

s must be a sstats structure.


procedure: (set-sstats-cpu! s obj)
procedure: (set-sstats-real! s obj)
procedure: (set-sstats-bytes! s obj)
procedure: (set-sstats-gc-count! s obj)
procedure: (set-sstats-gc-cpu! s obj)
procedure: (set-sstats-gc-real! s obj)
procedure: (set-sstats-gc-bytes! s obj)
returns: unspecified

s must be a sstats structure. Each procedure sets the value of the corresponding field to obj.


procedure: (sstats-difference s1 s2)
returns: a sstats structure representing the difference between s1 and s2

s1 and s2 must be sstats structures. sstats-difference subtracts each field of s2 from the corresponding field of s1 to produce the result sstats structure. It coerces negative results to zero. sstats-difference is commonly used to measure the time elapsed between two points in the execution of a program. In doing such comparisons, it is often useful to adjust for overhead in the statistics gathering functions by calling statistics twice before timing a computation and once after; the difference in the results of the first two calls is then subtracted from the difference in the results of the last two calls. After adjusting for overhead, small negative results could occur for very fast running computations without the coercion of negative results to zero by sstats-difference.


procedure: (sstats-print s)
procedure: (sstats-print s output-port)
returns: unspecified

s must be a sstats structure. If output-port is not supplied, it defaults to the current output port. sstats-print displays the fields of s in a manner similar to display-statistics and time.


Section 10.7. Parameters

This section describes mechanisms for creating and manipulating parameters. New parameters may be created conveniently with make-parameter. Nothing distinguishes parameters from other procedures, however, except for their behavior. If more complicated actions must be taken when a parameter is invoked than can be accommodated easily through the make-parameter mechanism, the parameter may be defined directly with case-lambda.


procedure: (make-parameter object)
procedure: (make-parameter object procedure)
returns: a parameter (procedure)

make-parameter accepts one or two arguments. The first argument is the initial value of the internal variable, and the second, if present, is a filter applied to the initial value and all subsequent values. The filter should accept one argument. If the value is not appropriate, the filter should signal an error or convert the value into a more appropriate form.

For example, the default value of print-length is defined as follows:

(define print-length
  (make-parameter
    #f
    (lambda (x)
      (unless (or (not x) (and (fixnum? x) (fx>= x 0)))
        (error 'print-length "~s is not a positive fixnum or #f" x))
      x)))

(print-length)   #f
(print-length 3)
(print-length)   3
(format "~s" '(1 2 3 4 5 6))   "(1 2 3 ...)"
(print-length #f)
(format "~s" '(1 2 3 4 5 6))   "(1 2 3 4 5 6)"

The definition of make-parameter is straightforward using case-lambda:

(define make-parameter
  (case-lambda
    [(init guard)
     (let ([v (guard init)])
       (case-lambda
         [() v]
         [(u) (set! v (guard u))]))]
    [(init)
     (make-parameter init (lambda (x) x))]))


syntax: (parameterize ((param val) ...) exp1 exp2 ...)
returns: the value of the last expression

Using the syntactic form parameterize, the values of parameters can be changed in a manner analogous to fluid-let for ordinary variables. Each param is set to the value of the corresponding val while each expression in the body is evaluated. When control leaves the body by normal return or by the invocation of a continuation created outside of the body, the parameters are restored to their original values. If control returns to the body via a continuation created during the execution of the body, the parameters are again set to their temporary values.

(define test
  (make-parameter 0))
(test)   0
(test 1)
(test)   1
(parameterize ([test 2])
  (test))   2
(test)   1
(parameterize ([test 2])
  (test 3)
  (test))   3
(test)   1
(define k (lambda (x) x))
(begin (set! k (call/cc k))
       'k)   k
(parameterize ([test 2])
  (test (call/cc k))
  (test))   k
(test)   1
(k 3)   3
(test)   1

The definition of parameterize is similar to the definition of fluid-let (page 66):

(define-syntax parameterize
  (lambda (x)
    (syntax-case x ()
      [(_ () e1 e2 ...) (syntax (begin e1 e2 ...))]
      [(_ ([x v] ...) e1 e2 ...)
       (andmap identifier? (syntax (x ...)))
       (with-syntax ([(p ...) (generate-temporaries (syntax (x ...)))]
                     [(y ...) (generate-temporaries (syntax (x ...)))])
         (syntax
           (let ([p x] ... [y v] ...)
             (let ([swap (lambda ()
                           (let ([t (p)]) (p y) (set! y t)) ...)])
               (dynamic-wind swap (lambda () e1 e2 ...) swap)))))])))


Section 10.8. Environmental Queries


procedure: (date-and-time)
returns: a string giving the current date and time

(date-and-time)  "Fri Jul 13 13:13:13 2001"


procedure: (getenv string)
returns: environment value of string or #f

getenv returns the operating system shell's environment value associated with string or #f if no environment value is associated with string.

(getenv "HOME")  "/u/freddy"


Section 10.9. Subset Modes


parameter: subset-mode

The value of this parameter must be #f (the default) or the symbol system. Setting subset-mode to system allows the manipulation of various undocumented system variables, data structures, and settings. It is typically used only for system debugging.


Chez Scheme User's Guide
© 1998 R. Kent Dybvig
Cadence Research Systems
http://www.scheme.com
Illustrations © 1998 Jean-Pierre Hébert
about this book