This is a short hands-on tour. It assumes a Linux shell, gcc/gm2,
Coco/R CR, and qbe as described in README.md.
From the repository root:
cd compiler
./build.sh # M2.atg -> scanner/parser/driver -> ./M2
./run_tests.sh # 333/333
M2 reads Modula-2 sources and writes QBE IR to gen_ssa/.
The one-shot mode compiles every unit named on the command line into one image. The program module comes last.
cd compiler
./M2 tests/hello.mod # -> gen_ssa/Hello.ssa
qbe -o gen_ssa/Hello.s gen_ssa/Hello.ssa
cc gen_ssa/Hello.s ../runtime/syslib/shim.c -o /tmp/hello -lm
/tmp/hello
Expected output:
Hello, Modula-2!
A global VAR ExitCode : INTEGER becomes the process exit code; without
one, the program returns 0.
A definition/implementation pair plus a program can still be compiled in one invocation:
./M2 tests/sep_lib.def tests/sep_lib.mod tests/sep_prog.mod
qbe -o gen_ssa/SepProg.s gen_ssa/SepProg.ssa
cc gen_ssa/SepProg.s ../runtime/syslib/shim.c -o /tmp/sep -lm
/tmp/sep
This produces one image and is the mode used by the test suite and the corpus harness.
Separate compilation reads only the imported .def files when compiling a
unit. The library object and the program object are linked with cc; a
.def is never passed to the linker.
M2 -cFrom compiler/:
# Library: Lib.def is found beside Lib.mod automatically.
./M2 -c tests/sep_lib.mod
# Program: pass the imported interface explicitly.
./M2 -c tests/sep_lib.def tests/sep_prog.mod
qbe -o gen_ssa/SepLib.s gen_ssa/SepLib.ssa
qbe -o gen_ssa/SepProg.s gen_ssa/SepProg.ssa
cc -c gen_ssa/SepLib.s -o gen_ssa/SepLib.o
cc -c gen_ssa/SepProg.s -o gen_ssa/SepProg.o
cc gen_ssa/SepProg.o gen_ssa/SepLib.o ../runtime/syslib/shim.c \
-o /tmp/sep -lm
/tmp/sep
M2 -c accepts -I directories and resolves imported interfaces
transitively; the target’s own .def is found automatically. Explicit
interface lists still work.
M2make --separateM2make is the convenient transitive-interface resolver. Build it once:
cd tools/m2make
./build_m2.sh
cd ../..
Then, from the repository root:
tools/m2make/b_v3/M2make --separate --dialect iso \
--m2bin ./compiler/M2 \
--shim runtime/syslib/shim.c \
-o /tmp/hello compiler/tests/hello.mod
/tmp/hello
M2make scans imports transitively, passes the ordered .def files to each
M2 -c, writes gen_ssa/<Module>.o, and links. The one-shot default is
still:
M2make -o /tmp/sep compiler/tests/sep_prog.mod
main.main.<Module>_init; the program calls imported inits in
dependency order, then its own init.export data; module procedures use
<Module>__<name>; vtables use <Module>__<Class>__vt.Libraries are split by dialect:
lib/pim/ classic PIM modules (InOut, RealInOut, …)
lib/iso/ ISO 10514-1 modules (TextIO, IOChan, Files, …)
lib/common/ shared Strings, Math, Storage, SysIO, CharClass
lib/gm2/ optional gm2 compatibility modules
runtime/syslib/ shared runtime/shim
Select one dialect per session:
M2make --dialect iso ... main.mod
or add the folders explicitly with -I lib/iso -I lib/common -I
runtime/syslib. Do not mix lib/pim and lib/iso in one session.
SYSTEM provides ISO byte-address arithmetic:
FROM SYSTEM IMPORT ADDRESS, ADR, ADDADR, SUBADR, DIFADR, VAL;
VAR
buf : ARRAY [0..15] OF CHAR;
p, q : ADDRESS;
diff : INTEGER;
BEGIN
p := ADR(buf[0]); (* raw data address, not the descriptor *)
q := ADDADR(p, 5);
diff := DIFADR(q, p); (* 5 *)
IF SUBADR(q, 5) = p THEN ... END
END
Absolute variables use a constant address and emit no storage:
VAR
port [0x1000] : CARDINAL;
PROCEDURE P;
VAR
slot [0x2000] : CARDINAL;
END P;
ADR(port) is 0x1000; the same syntax works locally. Addresses
above 0x7FFFFFFF use the 64-bit path. The tests only check ADR for
arbitrary addresses; dereferencing an unmapped page will fault. There is
no VOLATILE pragma yet.
String literals up to the 4 KiB class now survive parsing and lowering:
CONST Text = "...."; (* up to 4095 characters including quotes *)
The new showcase demonstrates all three features:
cd compiler
./M2 ../lib/common/sysio.def ../lib/common/sysio.mod tests/showcase26.mod
qbe -o gen_ssa/Showcase26.s gen_ssa/Showcase26.ssa
cc gen_ssa/Showcase26.s ../runtime/syslib/shim.c -o /tmp/showcase26 -lm
/tmp/showcase26
Expected output:
showcase26 arith=ok abs=ok local=ok text=3000
cd compiler && ./run_tests.sh
cd .. && TMP="$PWD/.scratch/fixpoint" ./bootstrap/fixpoint.sh
TMP="$PWD/.scratch/fixpoint-separate" ./bootstrap/fixpoint-separate.sh
tools/v3-corpus/corpus.sh
Current gates: suite 333/333, one-shot fixpoint OK (3,671,466 bytes), per-object fixpoint OK (11 objects), corpus 521/612 compile-OK, 0 crashes.
M2 -I transitive interface scanning is implemented.M2make --separate rebuilds only stale objects, relinks when an object,
the shim, or the output changes, and treats a newer compiler binary as an
ABI event. A small content ABI/flags stamp (gen_ssa/.m2abi) forces
a rebuild when the compiler ABI or --m2flag settings change.bootstrap/fixpoint-separate.sh; compiler units use
-legacyopen1d, while user programs keep pointer views.ADDRESS +/- operator and no
MAKEADR yet. DIFADR narrows to INTEGER; there is no 64-bit or
range-checked result form.VOLATILE; memory-mapped I/O needs a
pragma and backend support that are still future work.For details, see docs/separate-compilation.md and
docs/plan-separate-compilation.md.