Status: deferred. Finish the compiler as it is first (corpus, language gaps, robustness). This document records the design so the work can be picked up later without re-deriving it.
Baseline at the time of writing: v3-smallfix; suite 259/259,
fixpoint OK 4,028,447 bytes, corpus 511/612, 0 crashes.
compile time link time
.def (interface) ──► type checking
.mod (body) ──► codegen ──► .o ──┐
├──► cc/ld ──► executable
runtime shim + libc ──────────────────────┘
.def of every imported module
(transitively, because a def can re-export types from another def).
It does not read the imported module's .mod, .o, or any
previous compilation state..def change requires recompiling its importers (like a C header
change). No link-time type checking..o files, the runtime shim and system
libraries. .def files are never passed to the linker.V3 is a single-session, whole-program compiler:
M2.mod parses all units on the command line into one SymTab, then
Lower.EndSession calls QbeGen.EndModule, which writes one
gen_ssa/<program>.ssa.<base>_<uid>, where uid comes from
SymTab.nextUid in session parse order (QbeGen.Mangled). Separate
compilations assign different uids to the same procedure → undefined
symbols at link time.$vt_<typeIndex> (QbeGen.VtRef); type
indices are session-local too.$strN, $setN,
$ctoN.QbeGen.EndModule flushes all pools and emits main; there is no
per-module object boundary.<module>_init (QbeGen.BeginInit)
and main calls the init functions encountered in the same session.
Cross-object init ordering is the one new runtime concern.M2.mod
DoInst).| Entity | Current | Needed |
|---|---|---|
| Module-level procedure | <base>_<uid> |
<module>__<name> |
| Module-level variable | <module>_<name> (already stable) |
keep |
| Nested procedure / method | <base>_<uid> |
<module>__<path> or deterministic local id |
| Class vtable | $vt_<typeIndex> |
<module>__<class>__vt |
| String literal | $strN |
<module>__strN |
| Set constant | $setN / named <unit>_<name> |
module-qualified; exported consts keep global names |
| Constructor | $ctoN |
<module>__ctorN |
| External C procedure | C link name | keep |
Module-level names are unique in Modula-2; module qualification is
enough. Nested procedures need a deterministic path (not the session
counter). Generics use the declared instance name, so
<instance>__<proc> is stable.
M2 -c Foo.mod emits one QBE module for Foo with export function
for public procedures and data for globals.main; only the program module emits
main, the init calls and the ExitCode return.QbeGen.EndModule into “end object” and “end program”.CONSTs are materialised
by the client from the .def value or referenced by stable symbols.BEGIN body/class init exports <module>_init
(already the case).main calls the init functions of all linked
modules once, in dependency order, before the program body..init_array (needs C/asm glue;
less portable with QBE). Start with the generated list.-c (compile one unit) and -I <dir> search paths..mod plus every direct/transitive .def; never the
imported .mod..sym cache: serialise parsed def symbol tables, keyed by
def hash + compiler version + flags. Not required initially; stale
caches are a real risk..def..mod must be on the
search path), orInst.o) with stable
instance names and link it..def alone is enough for type checking, not for emitting code..def; vtables/methods live in the
defining module's object under stable names.CONSTs can be re-materialised from the .def
value or referenced by a stable exported symbol.Normal cc/ld; no compiler-specific linker needed:
M2 -c -I runtime/syslib:lib/iso Lib.mod -o Lib.o
M2 -c -I runtime/syslib:lib/iso Prog.mod -o Prog.o
cc Prog.o Lib.o runtime/syslib/shim.o -lm -o Prog
.o files + shim + libc; no .def at link time.main only in the program object.Lib__P, Lib__x, Lib__Class__vt are ordinary symbols.Lib__init is exported and called from Prog's main via the
generated init list.Lib__P at link time = stale object or .def/object
mismatch → rebuild the importer.shim.c, -lm) is linked once, as today..ssa in one session. Keep the
whole-session driver, emit one .ssa per unit, link them. Validates
symbols, pools, vtables and the init list.-c / -I driver mode. Compile one .mod against transitive
.defs; emit .o; link with cc..o targets, .def
dependencies, relink..ssa or the linked
binary).Lib.def/.mod + Prog.mod) compiled as
separate objects, linked, run.CONSTs, class vtable
dispatch, generic instances..def change.cc link failure on a stale object is expected and documented..ssa” gate must be
redefined for multi-object builds..def
mtimes, no linker plugin, no binary .def format initially..def as the source of truth; a .sym cache is optional.The question “can PIM, M2R10 and ISO libraries live in separate folders?” is independent of separate compilation, but it compounds the same interface-resolution work. A possible layout:
runtime/syslib/ SysShim, FileIO, Utf8 (shared runtime)
lib/pim/ InOut, RealInOut, NumberIO, FpuIO, StrIO, ...
lib/iso/ STextIO, SWholeIO, SRealIO, TextIO, IOChan,
StreamFile, ChanConsts, IOConsts, SIOResult,
ConvTypes, Conversions, RealStr, WholeStr,
LongStr, LongMath, Strings, Storage,
ProgramArgs, SysClock, TERMINATION, ...
lib/m2r10/ future R10 redesigned library (not ISO/PIM)
lib/gm2/ (optional) gm2-only modules (DynamicStrings, Files, ...)
lib/common/ modules genuinely identical across dialects
Rules:
InOut/RealInOut/Storage with different signatures; never put
both folders on the same -I path for one session.lib/pim or lib/iso (or
lib/m2r10), plus runtime/syslib.V3_LIBS; it would need to
compile PIM and ISO corpora with different library paths (or audit
that no colliding module is used across both).stdlib/ as a compatibility aggregate only during the
transition; remove it once the test/corpus scripts use the dialect
folders.SysIO, Files, DynamicStrings, CharClass,
WrapC/WrapClock/WrapTime, TimeString needs an audit: ISO,
gm2-only, or V3-specific.This is a low-risk directory/build change once the compiler is stable; it should be scheduled after the current corpus/language work, not during it.