My attempt at a proof-of-concept of calling constructors and destructors directly from the C library has effectively turned into a largely complete implementation.
The head commit here is presented as a merge commit between the current tip of master and a rebase onto master of your CppSupport branch. The reasoning behind that is so that we can do a three-way diff in the git viewer of your choice, so you can see where I've regressed changes, where I've hung onto them, and where I've added new ones. Clearly we'll want to flatten the structure prior to merging upstream!
Tested with both applications and modules. Tested with static linking (to ansilib or ansilibm as appropriate). Tested with dynamic linking - the functionality only appears if you use both new stubs and a new shared library, but you at least don't get a crash if you pair either new stubs and old shared library or old stubs and new shared library. Of course, if
main relies on the constructors being called, that might be a problem, so perhaps it would be worth the stubs doing an appropriate
RMEnsure if they detect they've been linked with code that uses constructors...
Some points of note:
I haven't copied the
ddtorsfunctionality from SDT. From the context, these appear to be "dynamic destructors" and are effectively
atexitroutines, but severely limited by the fact that they all have to fit into an area that's fixed at link time. I haven't seen any other runtime library offer a similar feature, so I'm doubtful that it's required for C++ conformance.
There is currently no support for constructors/destructors in the shared library (as opposed to in its clients), although they would spring to life if such code was present in ansilib (because ansilib's areas are merged with its clients' at link time). This is partly because it's tempting to wait until we've extended the C compiler to be able to reference area limit symbols, like just about every other compiler can, and then use that, especially since there's currently no use-case for it.
For module clients, constructors are currently called twice - once on module initialisation, and once on module entry. This is probably a bad thing because it could lead to resource leaks. Obviously a constructor needs calling if its object is used from privileged-mode parts of a module, but it's also not good from a security point of view to be initialising objects on module initialisation if they're only used from the user-mode part of the module. Also, I note destructors are only called once; I'm guessing that's on user-mode exit if the module was ever entered, which would mean that privileged-mode parts of the module would probably malfunction. Yuck.
Ordering of static initialisers... I gather that the standard defines that within a single compilation unit, static variables are initialized in the same order as they are defined in the source, but that the ordering between compilation units is undefined. Because we have no visibility of the compilation unit boundaries once they have been merged into the
C$$dtorvecareas, I think we have to follow the herd and iterate across the constructors in increasing address and destructors in decreasing address order. (IMHO, that's a massive missed opportunity, as if the order ever matters, it's probably in the reverse of the order in which the linker pulls dependencies out of static libraries, not the forward order.) This is an area where ELF constructor naming wins over
C$$ctorvecbecause there are variants of
.init_arrayareas with a priority-level suffix. We also currently lack any scripting interface to our linker in which we could specify the relative order of compilation units within
C$$ctorvec. Truly a can of worms.
There probably ought to be some form of documentation update, as we're modifying various parts of the public API: the layout of the language description struct, the prototype of the
InitProcmember thereof, and the prototype of the
- Further squash the flags into a bitfield to make room for a destructors-called flag without increasing memory requirements.
- Implement dispatch of construtors and destructors.
- Extend the C description of the language description struct.
- Remove the new comments about the registers on entry. People are allowed to roll their own stubs (it's even encouraged by the PRM!) so we can't assume these will be true - it's only documented as a void function. I'm now accepting a pointer to the language description struct in
a1, but I'm doing a fair bit of validation to detect old clients.
- Only one additional static variable now required. Cancels out the reduction in variables declared from C source files, and so means that the size in
clib.s.cl_spareno longer needs tweaking.
rtskBaseis exported for the benefit of
ansilibm(for other targets, it's in the same compilation unit already).
- Add all the new fields for the language description struct.
Initialise, pass the a1 (pointer to language description struct) set up by our caller (
_kernel_init) through to
_clib_initialise. Delete the code that for some reason was setting up a1-a3 - it was undocumented and unused.
- Complete the language desciption struct for the shared library, even though the constructor/destructor fields are currently unused.
- Delete this file, it appears to be unused and only serves to confuse the already complex static data handling!
- Adding a backdoor for
_clib_initialisemoduleto grab a pointer to the client's language description block seems preferable to changing its arguments, which would probably require a corresponding change to CMHG.
- Added a couple of asserts that - at least in the stubs, if not in the initialisers in the shared library - the kernel and C static data areas don't diverge from the magic fixed values documented in the PRM.
- Move the assembly definitions of the langauge description struct to
s.h_workspso it can be shared with
- Add a bunch of asserts where words are being read/written at the start of a struct using
STMso that the relevant offset symbols show up in searches.
- No longer any need to squirrel away a pointer to the kernel init block for later use, clawing back a word of static data.
- Rework the code that converts from addresses to languages in
FindHandler. Upper limit addresses are now correctly treated as exclusive rather than inclusive, there's no problem if the area crosses address 0x80000000, and constructor and destructor areas are included in the search as well as the main code area (assuming they're specified in the language description struct).
- Add test application and module to exercise constructors and destructors.
P.S. I'm unavailable for the next few days, so I'm afraid any questions will have to wait.