Some language features are implemented in the run-time library, including:
The integrated memory manager is described in Memory management. The section The oberonRTS module describes an interface to the Oberon-2 run-time support.
The XDS integrated memory manager implements
The compiler provides the option GCAUTO and the equation HEAPLIMIT to control the memory management. They should be set when the top-level module of the program is compiled / We recommend to set them in the configuration file or a project file./ . The compiler uses their values when generating the RTS initialization call.
The equation HEAPLIMIT specifies the maximum size of the heap in bytes. If that equation is set to zero, the run-time system automatically determines heap size at startup and dynamically adjusts it according to application’s memory use and system load.
The option GCAUTO allows the garbage collector to be called implicitly. If the option is not set the garbage collector must be called explicitly (See The oberonRTS module). The garbage collector is called implicitly by the memory allocation procedure in the following cases:
Note: In a pure Modula-2 program, the garbage collector is never invoked, so you may set the HEAPLIMIT equation to a very large value.
If the option GENHISTORY was set ON when your program was compiled, the run-time system dumps a procedure call stack into a file called errinfo.$$$, which may then be read by the HIS utility to print each item with
To print the history, RTS scans the stack of the coroutine that caused an exception and tries to find procedure calls. This is not a trivial task because of the highly optimized code generated by the compiler. For example, not all procedures have a stack frame.
For each pointer to the code segment on the stack RTS checks the previous command. If this command is a call command, it assumes that this is a procedure call. It is unlikely that RTS misses a procedure call, but it can be cheated by something that looks like a procedure call. As a rule, it is caused by uninitialized local variables, especially character arrays.
The first line of the history is always correct. For each line, except the first one, we recommend to check that the procedure shown in the previous line is called from the given line.
From the other hand, if you turn the GENFRAME option on, the code will be a bit slower, but RTS will scan stack frames of the procedures and the history will show absolutely correct addresses and line numbers. Procedure names are almost always valid except the case of lack of debug information in some modules - probably compiled by foreign compilers or by XDS with not all debug flags set. So you should not rely on procedure names hard.
Turning the GENHISTORY option ON does not slow down your code, as it only adds an extra call to the initialization routine. It should be done when you compile the main module of your program, in its header, compiler command line, or project (we recommend the last approach).
The following example shows a sketch of a program and the procedure stack:
PROCEDURE P1; (* uninitialized variable: *) VAR x: ARRAY [0..50] OF INTEGER; BEGIN i:=i DIV j; (* line 50 *) END P1; PROCEDURE P2; BEGIN i:=i DIV j; (* line 100 *) END P2; PROCEDURE P3; BEGIN P1; (* line 150 *) END P3;
#RTS: No exception handler #6: zero or negative divisor ------------------------------------------------------------ Source file LINE OFFSET PROCEDURE ------------------------------------------------------------ "test.mod" 50 000000DE "test.mod" 100 0000024C "test.mod" 150 0000051D
It is obvious from the source text that the procedure P1 cannot be called from P2. The second line is superfluous.
The run-time support (RTS) is an integral part of the Oberon-2 language implementation. It includes command activation, memory allocation, garbage collection and meta-language facilities. The module oberonRTS (written in Modula-2) provides an interface to these features.
TYPE Module; (* run-time data structure for a module *) Type; (* run-time data structure for a data type *) Command = PROC; (* parameterless procedure *) CARDINAL = SYSTEM.CARD32;
VAR nullModule: Module; (* Null value of type Module *) nullType: Type; (* Null value of type Type *)
Invokes the garbage collector.
PROCEDURE GetInfo(VAR objects, busymem: CARDINAL);
Returns the number of allocated objects and the total size of the allocated memory.
A system with garbage collection has some specific features. Its main difference from systems without garbage collection is that deallocation of any system resource must be postponed until garbage collection. For example, let some data structure contain descriptors of open files. To close a file (i.e. to destroy its descriptor), one needs to know that there are no references to that file. This information becomes known only in the course of garbage collection. The same argument also holds for other kinds of resources.
One immediate implication is that there must be some finalization mechanism: the ability to perform certain operations with an object when there are no more references to it.
XDS allows a finalization procedure to be attached to any dynamically allocated object.
TYPE Finalizer = PROCEDURE (SYSTEM.ADDRESS);
PROCEDURE InstallFinalizer(f: Finalizer;
The procedure sets the finalization procedure f for the object obj. That procedure will be called when the object becomes unreachable.
Note: a finalizer is called on the GC stack (stack size is limited).
TYPE Obj = POINTER TO ObjDesc; ObjDesc = RECORD file: File; (* file handler *) END; PROCEDURE Final(x: SYSTEM.ADDRESS); VAR o: Obj; BEGIN o:=SYSTEM.CAST(Obj,x); IF o.file # NIL THEN Close(file) END; END Final; PROCEDURE Create(): Obj; VAR o: Obj; BEGIN NEW(o); o.file:=NIL; oberonRTS.InstallFinalizer(Final,o); TryOpen(o.file); END Create;
The meta-programming operations can be used to retrieve the type of an object, to create an object of the given type, to get the name of a type and a type by its name, etc.
PROCEDURE Search(name: ARRAY OF CHAR): Module;
Returns a module by its name or nullModule.
PROCEDURE NameOfModule(m: Module;
VAR name: ARRAY OF CHAR);
Returns the name of the Module.
PROCEDURE ThisCommand(m: Module; name: ARRAY OF CHAR; ): Command;
Returns the command (parameterless procedure) named "name" in the module m or NIL, if the command does not exist.
PROCEDURE ThisType(m: Module; name: ARRAY OF CHAR): Type;
Returns the type named "name" declared in the module m or nullType, if there is no such type.
PROCEDURE SizeOf(t: Type): INTEGER;
Returns the size (in bytes) of an object of the type t.
PROCEDURE BaseOf(t: Type; level: INTEGER): Type;
Returns the level-th base type of t.
PROCEDURE LevelOf(t: Type): INTEGER;
Returns a level of the type extension.
PROCEDURE ModuleOf(t: Type): Module;
Returns the module in which the type t was declared.
PROCEDURE NameOfType(t: Type; VAR name: ARRAY OF CHAR);
Returns the name of the record type t.
PROCEDURE TypeOf(obj: SYSTEM.ADDRESS): Type;
Returns the type of the object obj.
PROCEDURE NewObj(type: Type): SYSTEM.ADDRESS;
Creates a new object of the type type.
The module oberonRTS provides procedures which can be used to iterate all loaded modules, all commands, and all object types (i.e., exported record types).
TYPE NameIterator = PROCEDURE ( (*context:*) SYSTEM.ADDRESS, (*name:*) ARRAY OF CHAR ): BOOLEAN;
A procedure of type NameIterator is called by an iterator on each iterated item. An iterator passes the name of the item along with the so-called context word. This allows some context information to be passed to the user-defined procedure (e.g., a file handler). If the procedure returns FALSE, the iteration is terminated.
PROCEDURE IterModules(context: SYSTEM.ADDRESS; iter: NameIterator);
The procedure iterates all Oberon-2 modules.
PROCEDURE IterCommands(mod: Module; context: SYSTEM.ADDRESS; iter: NameIterator);
Iterates all commands implemented in the module mod.
PROCEDURE IterTypes(mod: Module; context: SYSTEM.WORD; iter: NameIterator);
Iterates all record types declared in the module mod.