Self-unloading With Modules.Free?

General discussions about using the Astrobe IDE to program the FPGA RISC5 cpu used in Project Oberon 2013
Post Reply
gray
Posts: 73
Joined: Tue Feb 12, 2019 2:59 am
Location: Mauritius

Self-unloading With Modules.Free?

Post by gray » Tue Jun 01, 2021 9:23 am

Is there any reason why, or a situation when, this would not work?

Code: Select all

MODULE M;

  IMPORT Modules, Oberon;

  PROCEDURE Run*;
  BEGIN
    Modules.Free("M");
    Oberon.Collect(0)
  END Run;

END M.
That is, a module unloading itself. The reasoning here is that Modules.Free just sets the module name to the empty string, and adjusts the references, but the module stays in memory unchanged otherwise, so 'Run' in the example terminates correctly. (In fact, the logic of Modules.Load even makes use of this fact for the module number, ie. the index into the module table, if I read the code correctly[1].)

Based on studying the code, and running tests, I am pretty confident that this self-unloading works. However, I might be overlooking something.

Background: my use case is a memory-constrained controller, which has major modes of operation, each with its set of processes (or tasks)[2]. To switch to another mode, the current control program starts a next one, unloading itself to make space. The first program's main process terminates and removes all its (sub/child) processes, sets up the successor, uses Modules.Free on itself (and possibly on other modules belonging to the program, in the right order), terminates and removes itself, and then a loader process permanently in memory will start the new program with its processes. Obviously, the above test case is not a representation of this machinery. ;)

As an aside, I have modified Modules.Free to keep no slots of freed modules "on the top" of the linked modules list (Modules.root), so the above "program chaining" can be done forever with careful module loading and unloading, without module memory space fragmentation.

Code: Select all

PROCEDURE Free*(name: ARRAY OF CHAR);
  VAR mod, imp: Module; p, q: INTEGER;
BEGIN
  mod := root; res := 0;
  WHILE (mod # NIL) & (mod.name # name) DO mod := mod.next END;
  IF mod # NIL THEN
    IF mod.refcnt = 0 THEN
      mod.name[0] := 0X; p := mod.imp; q := mod.cmd;
      WHILE p < q DO SYSTEM.GET(p, imp); DEC(imp.refcnt); INC(p, 4) END;
      IF mod = root THEN
        WHILE root.name[0] = 0X DO
          AllocPtr := SYSTEM.VAL(INTEGER, root); root := root.next
        END
      END
    ELSE
      res := 1
    END
  END
END Free;
[1] Search for 'mod.num' in Modules.Load.
[2] Not having "dormant", ie. currently inactive processes is also useful for autonomous error recovery, even without said memory restrictions.

cfbsoftware
Site Admin
Posts: 441
Joined: Fri Dec 31, 2010 12:30 pm
Contact:

Re: Self-unloading With Modules.Free?

Post by cfbsoftware » Tue Jun 01, 2021 9:59 pm

Free should be used with great care and only when necessary. A situation where it is necessary and generally OK is when you are in the process of developing a new module and want to test it after making changes and recompiling. In that case you should have a good understanding of how the module works and know what you have to do to ensure it is safe to unload it. That applies to your proposal.

If other circumstances, there are a number of issues to be aware of. Here are some; there may well be others

1. The value of Modules.res can be checked after calling Modules.Free. 0 = success, 1 = failure.

2. It is not spelt out in the latest Project Oberon documentation but the following is stated in the original book:
Unloading a module is a tricky operation. One must make sure that no references to the unloaded module exist in the remaining modules and data structures. Unfortunately, this is not at all easy and is not guaranteed in the Oberon System. The violating culprits are procedure variables. If a procedure of a module A is assigned to a variable of a module B, and if A is unloaded, the procedure variable holds a dangling reference to the unloaded module's code block.
3. If the module has opened windows (and / or files?) these should be closed before freeing the module. The system will free the module regardless which might result in subsequent problems.

gray
Posts: 73
Joined: Tue Feb 12, 2019 2:59 am
Location: Mauritius

Re: Self-unloading With Modules.Free?

Post by gray » Wed Jun 02, 2021 4:28 am

Thanks for your information and insights. Your general words of caution are useful in any case, as in case "self-freeing" would not work, the unloading could easily be delegated to an extended loader process/task that would free the first program before loading the subsequent one.

My current design rules for using Modules.Free at run-time are (work in progress):

a) Only to be used by control programs that run "on top" of the modules list, that is, no imports are allowed from the corresponding modules outside the program itself. Only one such program, representing a "major control mode" is allowed at any one time. (System processes are not part of a major mode.)

b) All system resources must be released properly, or left behind in a defined state. This applies to files, possibly some device in the FPGA, or even pure software "resources" such as a system-level semaphore. [1]

c) Since no imports are allowed, no variables in other modules can hold references to procedures in the program modules unless they have been installed by the program itself. An obvious example would be the references to the processes/tasks. They must be properly removed.

d) Checking for Modules.res = 0 is mandatory during development, and a hard ASSERT will usually serve the purpose to detect a wrong unloading order. After testing and releasing, the proper unloading of all related modules will work under normal circumstances. Nonetheless, for the operational system, an appropriate check and error handling must be integrated with the overall error detection and recovery policy and framework chosen for the specific application case.


[1] Semaphores are rarely needed with cooperative scheduling in the first place. Here's a use case: a process (or task) uses a shared IO device with a buffer. It fills the buffer, then, with no busy-waiting permitted, yields control until the buffer has been emptied by the device, to then be activated again to continue until all output has been done. This requires gating with a semaphore (or similar mechanism), if another process also requires access to the same IO device.

Post Reply