Shared library implementation?


I’m just learning Rust and think that sharing reusable code would be a boon for cache performance. Libraries can be thought of as applications with multiple entry points so if applications could be made reentrant, the job would be half done!

The AROS-style library is implemented with an atomic reference counted access to the handle of the library base structure. The library base would be implemented with a mutex protected heap that has a vtable of closures containing calls to the actual code implementation wrapped in the structure to simulate a jump table.

I’ll continue this discussion later.


The implementation of the vtable could be just a nested structure at the end of the heap with a constant reference at the beginning. There are 2 ways to implement the heap/base of the library:
per-opener or static.

Per-opener makes a new heap allocation every time the library is opened. This is how Posix OS’s generally do it as do Mac and Windows.

Static is faster and more Rust-like. It’s how Amiga and similar OS designs like MorphOS and AROS do most of their shared libraries. Static library implementations still allow box allocations so it’s generally the best of both worlds. I would suggest supporting both kinds of library.

There is one final matter for a dlopen/Posix style library compatibility: querying which functions are available and how many parameters they each take.

Since the AmigaOS offshoots had a revision number constant publicly available to insure that their OpenLibrary command had a new enough version to implement all the jump table entries, there was no need for DLL Hell.

The more mainstream OS designs were not so lucky or insightful and had to put versions into the filenames of their .so/.dylib/.dll to avoid version incompatibility. Thus if a function becomes obsolete a whole new library had to be created while an Amiga-style .library just redirected the old jump table entry to an error code and put the new implementation at the end of the jump table as a new entry in the structure.


Update: After reading more about ELF sections it appears that the Amiga-style of shared library is referred to in ELF documentation as Position Independent Code (PIC). It works well in register rich environments such as the AMD64 and the earlier M68000 series and most RISC architectures. Unless we plan on implementing suppport for 32-bit x86, which I doubt is necessary at this point, we should be in the clear by implementing PIC by default if not always.

The AmigaOS 4 model for shared libraries changed from the one that earlier versions used as well as AROS and MorphOS, the spinoffs. AmigaOS 4 was the first version of AmigaOS to be implemented fully on PowerPC and leave the 680x0 behind. It changed the per-opener aspect of the shared library to the interface built on top of the internal static table. It allowed the implementation of ELF style shared objects in a limited sense but didn’t use the PIC model as effectively. Since libraries on AmigaOS can be ROM resident, they needed the ability to be patched in the header in case of errors in the ROM version of the library. With the advent of Flash memory, this patching and the associated double-indirection should not be used.

I still have to look up more information about MMU relocation of memory pages (which should be avoided in my opinion) or a flat-memory model using relocation tables at load-time. The reason I think that the Position Independent Executable (PIE) is preferred over the MMU-based context switches is that switching between processes at runtime is cheaper time-wise when implemented with minimal impact on the memory map. Rust seems to prefer usage of generics over pointer indirection anyway so why should the MMU do all the grunt work at run time when it can be done once at load time?