Modules, symbol files and keys

Oberon has no header files. The compiler extracts a module's interface from the module itself and puts it alongside — in a symbol file with the extension .smb. The mechanism is small, but it is what holds separate compilation together, and without understanding keys half the compiler's messages look cryptic.

What compilation produces

From Math.Mod the compiler makes two files:

An .rsc can be inspected with ORTool.DecObj Math.rsc, which prints the header and the disassembled code.

The key

In the header of every object and symbol file sits a key — a number computed over the contents of the interface. The log shows it last:

compiling Math  447     0 32C32F12

The key changes if and only if the interface changes: which names are exported, their types, their order. Editing the body of a procedure while its heading stays the same leaves the key alone.

Every module remembers the keys of everything it imports. On loading, the system compares them against the real ones. If they differ the module does not load, and you get a complaint about a mismatch.

This guards against the nastiest error in separate compilation: you build a module, change a library's interface, rebuild only the library — and end up with a program calling a procedure at the old offset. In Oberon that cannot happen: it simply will not start.

The /s option

Hence the meaning of /s in the compile command. By default the compiler refuses to overwrite a symbol file when the interface has changed — because doing so would silently break everything already built against it.

/s says "yes, I know, overwrite it". Everything that depends on the module must then be rebuilt, in order from those that depend on nothing to those that depend on everything.

Rebuilding an unchanged source leaves the interface alone, so the key stays the same and /s does no harm. That is why it appears everywhere in the labs.

Build order

The order comes from the imports. For this system it works out like this (the beginning):

Kernel  FileDir  Files  Modules  Input  Display  Viewers
Fonts  Texts  Oberon  ...  ORS  ORB  ORG  ORP  ...

There is no need to remember it: it follows from the sources themselves by a topological sort over IMPORT. That is how we do it when rebuilding the whole system.

What a rebuild shows

If the sources and the compiler agree, rebuilding an unchanged module gives a byte-identical object file. That is a strong property and worth checking: it means the compiler is deterministic and that the shipped binaries correspond to the shipped sources.

We rebuilt the whole system inside itself. Thirty-seven object files out of thirty-nine came out byte-identical. The two exceptions turned out to be findings:

You will reproduce the first with your own hands in lab 7: look at the length of Math.rsc before and after.

Three more inconsistencies

It also turned out that three modules from this image do not compile with the compiler from the same image:

modulemessagecause
RISCpos 926 bad divisorthe text has IR DIV 80000000H, and that constant is negative as a signed integer; the divisor must be positive
ORCimport not availableimports a module V24 that is on the image neither as source nor as a symbol file
Netincompatible parameters, nine timesthe signatures of calls to SCC have diverged from the SCC.Mod on the same image

This is not our machine failing and not a build error: the diagnostics come from the Oberon compiler itself, and the Math.rsc divergence shows up in a byte-for-byte comparison of files.

The lesson worth carrying away: a distributed system image need not be consistent with itself, and the only way to find out is to try rebuilding it.