Writing relocatable modules in C++

As of Acorn Cv5 C++ 3.1 [May 25 1995], No -zM version of the C++ libraries are provided.

This can be worked around by not compiling with -zM at all, and using the /still/ undocumented module-is-not-re-entrant: cmhg tag, which causes cmhg to pass a flag in to the startup routine in stubs to tell it not to copy the data. Very few modules actually need to be re-entrant anyway, so this actually saves having two copies of the static data around. Sadly, older stubs also fail to zero the zero-init region Image$$ZI$$Base to Image$$RW$$Limit.

link v4 used to always put a zero-init region into modules, but sadly didn't actually zero it (thus making squeezed modules larger as well as wasting runtime memory) and also included the relocation routine __RelocCode in the area to be zeroed.

link v5 now never puts the zero-init region in. This behaviour is also wrong - an option is needed. It can be worked around by artificially lengthening the module file after it is linked, but with the sad result that the relocation routine is again in the area to be zeroed. Still, who cares, as since *rmtidy is now obsolete, we only ever need to relocate the module at first initialise time. Another saving.

Worse, though, link versions at least up to 5.06 -C++ fail to generate relocation table entires for the __link structure chain that holds C++ initialisers. This can we worked around by starting at __head, and running down the list of pointers, relocating them manually.

Luckily, due to another stupidity, the C++ initialiser function ___main() is called right at the start of main(), rather than by a language specific initialisation function. It would be nice to see acorn using their own neatly flexable run-time langauge support in SharedCLibrary to properly support C++ - ie to do the initialisers then, and provide proper C++ function name backtraces.

Anyway, for a module, this means that the module initialisation routine can safely relocate the link structures manually. If you want C++ stuff available outside the main() function as well, you can then call ___main manually, and then zero out __head so the call inside main() will be harmless. A module has two sets of atexit handlers - one for user mode and one for svc mode. atexit handlers are called in reverse order, so after zeroing __head, add an atexit handler that restores it so that destructors are run on module shutdown.

Of course, it you are just porting an application to become a module, you may want to leave things all done in user mode.

Another bug to be aware of is that the RO3.1 SharedCLibrary frees up some memory used by standard i/o on the first exit from the run entry of a module. This means that if the svc half of your module uses stdio after that, you will be corrupting the RMA heap. Also, realloc() has a sign wrong in its calculation, resulting in it making memory smaller rather than larger (This applies to calls in svc mode, when memory comes from the RMA heap) I'm not sure what version of RISC OS fixes these problems.

Another sadness is that debugging modules appears to be impossible. Even trying link -rmf -debug crashes the linker, and DDT, along with all its other pathetic lacks, does not have any way to load in symbol tables for modules. Loadable symbols tables would be really useful, BTW - think of having them for the ROM.

Anyway, the result of all this is the following files: module.h module.c procmode.s robase.s