[Oberon] Safe module unloading

Andreas Pirklbauer andreas_pirklbauer at yahoo.com
Sun Dec 4 12:09:46 CET 2016


Given that - thanks also to the input received from this community - the semantics of "safe module unloading", as implemented in Experimental Oberon, has somewhat evolved over time, I have been asked to provide an updated description of the current status quo:

Safe module unloading

The semantics of unloading a module has been refined as follows. If clients exist, a module is never unloaded. If no references to the module exist in the remaining modules and data structures, it is unloaded and its associated memory is released. If there are references, however, the specified module is initially removed only from the list of loaded modules, but will later be automatically removed from memory as soon as there are no more references to it. Such references can be in the form of type tags (addresses of type descriptors) in dynamic (heap) objects of other modules pointing to descriptors of types declared in the specified module, or in the form of procedure variables installed in static or dynamic objects of other modules referring to procedures declared in that module.

Modules that have been removed only from the list of loaded modules (hidden modules) are marked with an asterisk in the output of the user command System.ShowModules. To achieve their automatic final removal from memory, the Oberon background task handling garbage collection includes a call to the command Modules.Collect. Thus, module data is kept in memory as long as needed and is removed from it as soon as possible.

In sum, unloading a module affects only future references to it, while past references from other modules remain completely unaffected, For example, older versions of the module’s code can still be executed if they are referenced by procedure variables in other loaded modules, even if a newer version of the module has been loaded in the meantime.

A simple mark-scan scheme is used to check dynamic references to a module. In the mark phase, dynamic records reachable by all other loaded modules are marked. This excludes records that are reachable only by the module itself. The scan phase scans the heap element by element, unmarks marked objects and verifies whether the type tags of the encountered marked records point to descriptors of types declared in the module to be unloaded, or whether procedure variables in these records refer to procedures declared in that module. The latter check is also performed for all static procedure variables.

In order to make such a validation pass possible, type descriptors for dynamic records and descriptors of global module data have been extended with a list of procedure variable offsets, adopting an approach employed in one of the earlier implementations of Oberon. These additional offsets are simply appended to the existing fields of each descriptor, i.e. their offsets are greater than those of the fields of the pointer variables needed for the garbage collector, in order not to impact its performance. The compiler generating these modified descriptors, the format of the Oberon object file containing them and the module loader transferring them into memory have been adjusted accordingly.

The following code excerpt (of procedure Modules.Check) shows a possible realization of such a reference-checking scheme:

  PROCEDURE Check*(mod: Module; VAR res: INTEGER);    VAR M: Module; pref, pvadr, r: LONGINT;  BEGIN (*mod # NIL*) M := root;    WHILE M # NIL DO      IF (M # mod) & (M.name[0] # 0X) THEN Kernel.Mark(M.ptr) END ;      M := M.next    END ;    Kernel.Check(mod.data, mod.var, mod.code, mod.imp, res); (*dynamic refs*)    IF res = 0 THEN M := root;      WHILE (M # NIL) & (res = 0) DO (*static refs*)        IF (M # mod) & (M.name[0] # 0X) THEN          pref := M.pvar; SYSTEM.GET(pref, pvadr);          WHILE pvadr # 0 DO (*procedure variables*) SYSTEM.GET(pvadr, r);            IF (mod.code <= r) & (r < mod.imp) THEN res := 3 END ;            INC(pref, 4); SYSTEM.GET(pref, pvadr)          END        END ;        M := M.next      END    END  END Check;

where procedure Kernel.Check implements the scan phase for dynamic references to the module, i.e. references from objects located in the heap:

  PROCEDURE Check*(type0, type1, code0, code1: LONGINT; VAR res: INTEGER);    VAR p, r, mark, tag, size, offadr, offset: LONGINT;  BEGIN p := heapOrg; res := 0;    REPEAT SYSTEM.GET(p+4, mark);      IF mark < 0 THEN (*free*) SYSTEM.GET(p, size)      ELSE (*allocated*) SYSTEM.GET(p, tag); SYSTEM.GET(tag, size);        IF mark > 0 THEN SYSTEM.PUT(p+4, 0); (*unmark*)          IF (type0 <= tag) & (tag < type1) THEN (*types*) res := 1          ELSIF res = 0 THEN offadr := tag + 16; SYSTEM.GET(offadr, offset);            WHILE offset # -1 DO (*pointers*) INC(offadr, 4); SYSTEM.GET(offadr, offset) END ;            INC(offadr, 4); SYSTEM.GET(offadr, offset);            WHILE offset # -1 DO (*procedure variables*) SYSTEM.GET(p+8+offset, r);              IF (code0 <= r) & (r < code1) THEN res := 2 END ;              INC(offadr, 4); SYSTEM.GET(offadr, offset)            END          END        END      END ;      INC(p, size)    UNTIL p >= heapLim  END Check;

Complete source code (modules Kernel, Modules):
http://github.com/andreaspirklbauer/Oberon-experimental











-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.inf.ethz.ch/pipermail/oberon/attachments/20161204/3dbd9ede/attachment.html>


More information about the Oberon mailing list