Runtime support for Modula-3.


Overview

The primary jobs of the Modula-3 runtime support code are conveying type information, linking, module initialization and garbage collection.

Type information

The interfaces which define the representation of type information at runtime are RTType.i3, RTTypeSRC.i3 and RTTypeMap.i3. Type information is stored for all objects on the heap. Here is the initial comment from "RTType.i3", reflecting the original Modula-3 runtime type information:

"RTType" provides access to the runtime type system.

Each reference type is assigned a unique typecode. A typecode is ``proper'' if it lies in the range "[0..MaxTypecode()]". The proper typecodes include all those that correspond to actual types in the running Modula-3 program. Other typecodes, proper and improper, may be used internally by the runtime system and garbage collector.

Although the language requires that typecodes exist only for object types and for traced reference types (including "NULL"), the implementation of "RTType" also provides typecodes for untraced reference types.

The values returned by the builtin operation "TYPECODE" correspond to (a subset of) the proper typecodes.

SPIN changes

Since SPIN can dynamically link a domain which contains novel types, it cannot construct an array of typecells with indices [0..MaxTypecode()] the way that SRC Modula-3 usually does. Instead, we introduce another level of indirection. Every type of object in the program has both a Typecode and a Typeidx. The Typecode is essentially a pointer to a TypeDefn object which contains all of the information known about the type. The TypeDefn's are allocated when a new domain is linked rather than being allocated all at once during startup.

The Typecode enables you to find type information when you are given an object. The Typeidx on the other hand is used to find information about all of the types in the program. It is an integer between 0 and the maximum number of types currently known and there is an array of TypeDefn's indexed by their Typeidx. Every time a new domain is linked, this array is expanded to include the new types.

Modula-3 Linking

By Modula-3 linking, we mean the process of collecting information about all of the types used by the program and filling in jtables for interfaces. These steps are performed by a call to RTLinker.Reinitialize every time a new domain is linked into the SPIN kernel.

Initialization

When SPIN is booting and reaches the Modula-3 initialization phase, it calls the main body of the RTLinker module which is part of m3core_sa. RTLinker initializes the runtime and calls all the main bodies of the modules in the system.

The major steps of Modula-3 initialization are:

The important interfaces for initialization are RTLinker.i3 and RT0.i3.

Garbage collection

The text of the comment from RTCollector.i3 follows.

"RTCollector" provides control over the Modula-3 garbage collector.

The purpose of a garbage collector is to reclaim unreachable nodes on the traced heap; most Modula-3 programs could not run very long without a collector. Even so, automatic garbage collection has some practical drawbacks.

  1. The collector might move heap nodes to different addresses. This is usually unnoticable to programs, but can cause problems when programs must work with the addresses of heap nodes, since it is not guaranteed that "ADR(x^)" is a constant over the lifetime of "x^". There are two main cases when programs must work with such addresses.
    1. To implement hash tables, etc.
    2. To pass addresses to procedures written in other languages, which is inherently unportable.

  2. Unsafe code can put the traced heap temporarily into an inconsistent state. If the collector happens to run then, it might delete nodes that seem unreachable but that in fact are accessible. Of course, unsafe code itself is inherently unportable.
This interface allows the program to control the Modula-3 collector to avoid such problems, as well as to pass hints to improve performance.

Disabling the collector.

The collector is initially enabled; the collector can reclaim storage, and move nodes in memory. While the collector is disabled, there will be no time spent in the collector. Allocation in the traced heap may proceed normally, although the heap will grow without bound. Nodes unreachable by the Modula-3 rules will not be reclaimed, and no nodes will move.

Disabling motion.

Disabling motion gives fewer guarantees than disabling the collector; while motion is disabled, it is guaranteed only that no nodes will move. Disabling motion is no more expensive than disabling the entire collector, and may be cheaper in some implementations.

Collecting.

Calling "Collect" is a hint from the program that now would be a good time for a collection (for example, if a large amount of storage has become unreachable, or if the program expects to wait some time for an external event).

Implementation notes.

This section describes the implementation of the SRC Modula-3 collector, as a guide to SRC Modula-3 programmers and as an indication of how this interface is matched to a particular implementation. Portable programs must not take advantage of implementation details of the SRC Modula-3 collector.

The SRC Modula-3 collector is an incremental, generational, conservative mostly-copying collector that uses VM protection on heap pages to be notified of certain heap accesses.

Because the SRC collector is conservative, an inaccessible node may be considered reachable if a bit-pattern either on a thread's stack or in its registers might be a reference to or into the node. Experience to date has not shown accidental node retention to be a problem.

The SRC collector will not collect or move a node while any thread's stack or registers contains a reference to or into the node. The SRC Modula-3 system guarantees that this will include references passed as value parameters. This guarantee is useful for calling foreign procedures.

"Disable" completes the current incremental collection, if any, and unprotects all heap pages, so that no page faults will occur while collection is disabled. No new collections will start while collection is disabled. The next collection after collection is reenabled will be total, as opposed to partial, since unprotecting the heap loses generational information.

"DisableMotion" disables further collections from beginning. "DisableMotion" does not finish the current incremental collection, since the collector already guarantees that the program will not see addresses in the previous space. No new collections will start while motion is disabled, so that the current space will not become the previous space. It is not necessary to unprotect the heap.

"Collect" completes the current incremental collection, if any, then performs a total collection before returning to the caller.

The SRC collector also supports additional operations for controlling the frequency of collection, disabling and reenabling incremental and generational collection, reporting on collector performance, and so on. These operations are accessible through the implementation-dependent "RTCollectorSRC" interface.

Runtime Hooks

The file src/runtime/common/RTHooks.i3 describes the runtime hooks that the compiler-generated code accesses. This file cannot be changed without a synchronized change to Runtime.i3 and Runtime.m3 in the compiler sources. Also, it should probably be kept in sync with the RTHooks.i3 file in the local libraries. For some reason the compiler looks there for the information on hooks at compile-time.


garrett@cs.washington.edu