The installation support is a set of procedures that support the primitive operations needed by the installation procedure.
RC Map instantiation
RC maps are used to determine where the references are in objects. Each type has a single RC map. The installation support for RC maps is quite simple:
GetRCIndex: PROC [desc: RCMapPtr] RETURNS [RCMapIndex]
This procedure instantiates a description of an RC map and returns the canonical index for that map.
The current implementation does not use RCMaps, and the fucntion is not provided.
Type instantiation
At runtime each collectible object has a type, which can be retrieved cheaply. The runtime only requires that types can be checked cheaply for equivalence, and that there is an association between types and RC maps. For debugging, however, we also require that each type has a "location", which indicates where the information about the structural description of the type can be found. The operation is declared as:
TypeDescPtr: TYPE = STRING;
GetTypeIndex: PROC
[typeString: TypeDescPtr, rcMap: RCMapIndex]
RETURNS [TypeIndex]
This procedure instantiates a description of a type and returns the canonical index for that type.
In the generated C code, the name of this procedure is XR←GetTypeIndex.
In the current implementation the rcMap is ignored.
When rcMap # unknownRCMapIndex, the rcMap is used to establish the RC Map for objects of the type. If a previous RC Map (# unknownRCMapIndex) existed for the type, and the previous desc is not the same as the current desc, then an error is raised.
Ref literals
Ref literals are either atom literals (type
ATOM), rope literals (type
ROPE) or text literals (type
REF
TEXT). The runtime support simply requires the ability to instantiate such literals, although we provide for literals of any type.
LiteralDescPtr: TYPE = STRING;
GetRefLiteral: PROC
[type: TypeIndex, contents: LiteralDescPtr] RETURNS [REF]
This procedure returns the canonical REF literal of the given type and contents. An error is raised if the type given is not supported in the system.
In the generated C code this procedure is XR←GetRefLiteral.
Global frames
Global frames are named objects in the load state. For debugging and trace and sweep garbage we require that each global frame has a type.
Name: TYPE = POINTER TO CHAR[]; -- that is, pointer to a C string;
GlobalFramePtr: TYPE = POINTER;
DeclareGlobalFrame: PROC
[name: Name, base: GlobalFramePtr,
type: TypeIndex, initialization: PROC]
This procedure declares that the global frame (starting at base) has the given name and type. The name is useful for maintaining the load state, and the type (and RC map) are useful for garbage collection. The initialization procedure is called when the module is started (either by START or a start trap).
We assume that the global frame storage already exists, and has been allocated and cleared by the underlying loader. It is perfectly acceptable to have multiple instances of the same name, possibly with different types, in the same name scope.
In the generated C code this procedure is XRlareGlobalFrame.
Interface records
In PrincOps implementations of Mesa an interface record was an abstract notion. That is, one could define a type for the interface that could be expressed as a record type, but that record did not have an existance.
Actually, the Cedar load state created such instances for debugging purposes, but did not use them for dispatching calls or variable references.
In the Mimosa model, interface records have actual instances that will be used as indirection sites for procedure calls and variable references. We believe that this actually simplifies the overall implementation.
A module can either import or export an interface. The import or export is performed at installation time, although the order of imports and exports is unimportant in the absence of type or name clashes.
The operations to import and export interfaces are:
ImportInterface: PROC
[name: Name, type: TypeIndex, size: INT]
RETURNS [InterfacePtr]
ExportInterface: PROC
[name: Name, type: TypeIndex, size: INT]
RETURNS [InterfacePtr]
In the generated C code they are XR←ImportInterface and XR𡤎xport Interface.
The two procedures in fact have identical semantics: they provided binding to an interface record with the correct type and size in the current scope (one is created if this is the first time). An anomaly is reported if there is already an interface name in the scope with the same name and a different type or size.
The size is given to allow allocation of a new interface record. This is necessary since the type index alone does not have sufficient information to indicate the amount of storage necessary.
A name conflict in importing or exporting an interface may or may not be an error. It is a policy decision. The runtime support leaves that decision to higher levels of software, but reports such conflicts as anomalies.
If different interfaces with the same name are allowed in a name scope version checking is partially defeated. However, this is useful in allowing new versions of modules based on new interfaces to be loaded.
In implementing interface records one must decide how they should be initialized. Every imported variable uses a pointer, which can be initially
NIL (causing an address fault if used). However, calling an unexported procedure should cause a resumable trap. So for each procedure imported, the installation routine calls ImportProc to fill in that slot with the appropriate trapping procedure. The interface is:
ImportProc: PROC [interface: InterfacePtr, index: INT]
Exporting is similar to importing, in that the same name scope rules apply, and in that a new interface record may need to be created.
Exporting a procedure causes the appropriate slot in the interface record to be filled with a procedure that causes a start trap when called. The trapping procedure will cause the initialization procedure of the module to be called, and will also remove the cause of further start traps for procedures exported by the module. Of course, since such procedures can be passed as values as well as being called, the trapping procedures must be benign if stumbled across after the module is started, but this should be rare.
Exported types
Exporting a type causes a global association between an abstract an a concrete type. Unfortunately, since the type space is global, there can only be one concrete type for any given abstract type in the system, although the same concrete type can be exported to many abstract types. The operation to export a type is described as:
ExportType: PROC [name: Name, abstract: TypeIndex, concrete: TypeIndex]
ExportType exports the given concrete type as an implementation for the given abstract type under the given name. An anomaly is reported if the exported concrete type is inconsistent with a previously exported concrete type for the same abstract type. The name is given for debugging purposes.
In the generated C code this procedure is XR𡤎xportType.
Consider the problem if we allowed more than one concrete type for a given abstract type. It might then be possible to take a value of abstract type and pass it to a module that assumed one concrete type when, infact, the abstract type had a completely different concrete type as its implementation. Since there is no runtime checking for coercions between abstract and concrete, this error would be difficult to detect, and could be abused to invalidate the type safety assumptions of Mesa.