No linker needed
It is fascinating that in the Oberon system, a separate program linker is not needed because the operating system loads the source modules directly into memory, as needed, while maintaining only a single copy of each module loaded. This is likely only possible due to programs sharing a single OS-wide address space. This kind of design presents many trade-offs, but it’s exactly the kind of architecture I’m interested in exploring. It’s very difficult for a system to be simply better than everything that came before. It’s a lot more realistic to think of a system as making different trade-offs.
The linking process may require a significant amount of address computations. But they are simple enough and, if the data are organized in an appropriate way, can be executed very swiftly. Nevertheless, and surprisingly, in many operating systems linking needs more time than compilation. The remedy which system designers offer is a separation of linking from loading. A set of compiled modules is first linked; the result is a linked object file with absolute addresses. The loader then merely transfers the object file into main store.
We consider this an unfortunate proposal. Instead of trying to cure an inadequacy with the aid of an additional processing stage and an additional tool, it is wiser to cure the malady at its core, namely to speed up the linking process itself. Indeed, there is no separate linker in the Oberon system. The linker and loader are integrated and fast enough to avoid any desire for pre-linking. Furthermore, the extensibility of a system crucially depends on the possibility to link additional modules to the ones already loaded by calls from any module. This is called dynamic loading. This is not possible with pre-linked object files. Newly loaded modules simply refer to the loaded ones, whereas pre-linked files lead to the presence of multiple copies of the same module code.
—Project Oberon
To explain it a little better, Oberon doesn’t use dynamic linking like Unix systems do: there’s no ELF loader, shared objects, or per-process address space (virtual memory). Code lives in modules, which are self-contained units with explicit imports and exports.
Now, when you call into a module that isn’t yet loaded, the system simply loads it from disk, links its imports on the spot, and runs it. Each module exists exactly once in memory.
So while Unix dynamic linking is a binary-level mechanism, Oberon’s is a language-level one.
Last edited March 30, 2025