Chez Scheme Documentation

repository·main·Indexed 27 days ago

https://github.com/cisco/chezscheme

A high-performance implementation of the Scheme programming language and a superset of the R6RS standard. It includes a compiler, a run-time system with generational garbage collection, and a comprehensive development environment. The system supports a wide range of platforms including Windows, macOS, Linux, FreeBSD, OpenBSD, NetBSD, Solaris, GNU/Hurd, Haiku, Android, iOS, and WebAssembly (bytecode interpreter only).

Tokens
8.3K
Snippets
11
Records
44
Agent score
91%

What's inside Chez Scheme

  1. Overview of Chez Scheme

    main

    Chez Scheme is a high-performance Scheme programming language and implementation that serves as a superset of the R6RS (Revised^6 Report on the Algorithmic Language Scheme) standard. It features first-class procedures, proper tail calls, continuations, user-defined records, libraries, exceptions, and hygienic macro expansion.

    Key capabilities include:

    • Language Interfacing: Extensive support for interfacing with C and other languages.
    • Concurrency: Support for multiple threads, potentially running on multiple cores.
    • I/O: Non-blocking I/O and support for both binary and textual (Unicode) I/O.
    • Memory Management: Automatic storage management using dynamic memory allocation and generational garbage collection.
    • Compilation: A high-performance compiler that produces optimized machine code. It supports on-the-fly compilation, precompilation into binary form, and whole-program compilation for full cross-library optimization.
  2. Understand the Chez Scheme compilation pipeline

    main

    Chez Scheme uses a nanopass compiler infrastructure that converts S-expressions into machine code through several intermediate stages:

    1. S-expression to Syntax Object: Initial conversion including source location annotations.
    2. Macro Expansion: Produces an Lsrc representation consisting of core forms.
    3. Front-end Optimizations: Transformations performed on the Lsrc representation (e.g., via cp0.ss, cptypes.ss).
    4. Machine Code Generation: A series of passes (defined in cpnanopass.ss and cpprim.ss) that convert the representation through various intermediate forms (defined in np-language.ss) into machine code.

    Key Technical Details:

    • Direct Machine Code: Chez Scheme produces machine code directly without relying on a system assembler.
    • Custom Linker: It implements its own linker to connect compiled code to the runtime kernel and shared symbols.
    • Lsrc to S-expression: The Lsrc intermediate form can be converted back to an S-expression using uncprep.ss (undo-compilerpass-representation).
  3. Understand the Chez Scheme Stack and Continuation Model

    main

    Chez Scheme does not use the C stack for Scheme code. Instead, the Scheme continuation is a heap-allocated, linked list of stack segments. The Scheme stack grows up in the heap.

    Key concepts:

    • SFP (Scheme Frame Pointer): A virtual register that must be assigned to a real machine register. It points to the beginning (low address) of the current stack frame.
    • Stack Frame Structure: The first word of a stack frame (SFP[0]) is the return address. Subsequent words contain local variables.
    • Control Flow: Function calls and returns in Scheme use 'jumps' rather than machine 'call' or 'return' instructions. It is the caller's responsibility to reset the SFP upon return.
    • Non-tail calls: A non-tail call moves the SFP past the current function's live variables, installs the return address in the current frame, and jumps to the target.
  4. Understand the Chez Scheme implementation structure

    main

    The Chez Scheme codebase is split into two main directories:

    • s/: Contains the majority of the compiler and libraries, implemented in Scheme.
    • c/: Contains the run-time kernel (garbage collector, OS interaction, and complex math support), implemented in C.

    Key files in s/ include:

    • cmacros.ss: Object layouts and global constants used by both the compiler and kernel.
    • syntax.ss: The macro expander.
    • cpnanopass.ss and cpprim.ss: The main compiler components.
    • cp0.ss, cptypes.ss, cpletrec.ss, etc.: Source-to-source passes.
    • x86_64.ss, arm64.ss, etc.: Architecture-specific backends.
    • *.def files: Platform-specific constants used by cmacros.ss to select backends.
  5. Optimize register allocation by avoiding variable reuse

    main

    To assist the register allocator in managing variable lifetimes, avoid reusing the same variable name for different purposes if their extents do not overlap. Instead of reusing a variable, use a new variable name to allow the allocator to map them to different registers or frame locations.

    Recommended pattern:

    (set! var1 ...)
    ... var1 ...
    ... code that doesn't use var1 ...
    (set! var2 ...)
    ... var2 ...
  6. Write and run individual test sets

    main

    Tests are written in .ms files within the mats directory. Each test is an expression that must evaluate to #t for success. To test expressions that are expected to raise exceptions, wrap them in the error? form.

    To run a specific test file, navigate to your build's *machine-type*/mats directory and use the zuo command with a .mo target. You can pass configuration arguments (like o=3 for unsafe mode) to control the execution environment.

    Note: A test failure is recorded in the resulting .mo file if it contains Bug, Error, or invalid memory.

  7. Run comprehensive test suites

    main

    The mats/main.zuo script provides several predefined test targets in increasing order of complexity and coverage:

    • zuo . test-one: Smallest set.
    • zuo . test-some-fast: A subset of test that omits interpreter mode.
    • zuo . test-some: A standard subset of tests.
    • zuo . test: The recommended default set of configurations.
    • zuo . test-more: Includes aggressive checking and slower configurations.
    • zuo . test-experr: Includes every combination of options that might result in different expected errors.

    Parallel Execution: You can run configurations in parallel using the -j flag or the ZUO_JOBS environment variable.

    Interpreting Results: Results are stored in output-*config*-*name* directories. Check the report-*config* files within those directories. A successful run will print a summary containing only configuration names (no errors).

  8. Create pbchunk Builds

    main

    A pbchunk build allows you to turn fragments of static Scheme code into C code that is compiled and plugged back into the kernel. This is done using the pbchunk-convert-file function.

    To create a directory containing adjusted boot files and the necessary C code, use the zuo . bootpbchunk <machine-type>-<tag> command.

    Options:

    • --petite: Extract pbchunks only from petite.boot (skips scheme.boot).
    • --only <file>: Extract pbchunks only from the specified boot file.

    Workflow:

    1. Generate the chunks: zuo . bootpbchunk <machine-type>-<tag>.
    2. If the machine-type differs from your host, create a cross-compiler first using zuo . bootquick <machine-type>.
    3. Configure the build using the generated boot files: ./configure --boot=<machine-type>-<tag> --pbarch.
    4. Build using zuo for the kernel and zuo . run to execute.
  9. Add new functionality to Chez Scheme

    main

    New functionality can often be added by writing Scheme code in existing s/*...*.ss files.

    Implementation Rules

    • Bindings: All new bindings must be declared in primdata.ss. Most additions go into the [libraries] group (the chezscheme library).
    • Helper Functions: If a helper function is defined in a different source file than its usage, prefix the name with $ and register it in the [flags system proc] group in primdata.ss.
    • Safety: Implement new functionality as 'safe' by fully checking arguments. Note that the implementation itself is compiled as 'unsafe'.
    • Debugging: To compile in 'safe mode' for testing and debugging, use zuo . o=0 within the *machine-type*/s workarea space.
    • Bootstrapping: If your new functionality is required to compile Chez Scheme itself, you must implement a copy of it in s/reboot.ss.
    zuo . o=0
  10. Structure a backend implementation

    main

    A backend implementation (e.g., x86_64.ss or arm64.ss) must be organized into three distinct S-expressions:

    1. define-registers: Defines the register set.
    2. Primitive Module: Implements primitives that convert to instructions, installed via primitive-handler-set!.
    3. Instruction Module (The "Assembler"): Implements instructions that convert to machine code. This module must also implement asm-foreign-call and asm-foreign-callable for the Foreign Function ABI.
  11. Run tests with expected-error checking

    main

    To verify that exceptions are raised correctly across different configurations, use the zuo . all target. This compares the error messages produced in .mo files against a set of expected error messages.

    1. Run zuo . all to generate .mo files in a configuration-specific subdirectory (e.g., compile-0-f-f-f).
    2. The tool compares output against expected messages using diff and writes a report to report-*config*. An empty report indicates success.
    3. If you add new exception tests, you must update the expected error files. Instead of manual editing, run zuo . experr to automatically generate new root-experr-*config* and patch-*config* files based on your current test runs.