Skip to content

M9 0.4.0

Choose a tag to compare

@atverm atverm released this 02 Sep 12:59

The M9 compiler m9c, the runtime libm9rt.a, the standard library as M9 source, the module reference, the compiler's man page and the VS Code extension files. gcc is the only toolchain any of them needs.

One package per distribution, x86-64, each built ON that distribution (the Ubuntu 26.04 one on a real 26.04 machine rather than a VM) from the one source tarball attached here (m9-0.4.0.tar.gz, sha256 c60fd267cf40ef7998681d8baedf00739047312833a40bde23d7ed509b684cfb), then installed there and made to compile and run an M9 program with an empty environment before it was allowed to leave the build machine.

That tarball is the same set of files as this repository -- the compiler, runtime, standard library, gates, man page and module reference -- and the release tooling refuses to cut one that is not, checked both against the list and against an independent deny-list of paths that must never be published.

distribution package install
Ubuntu 24.04 LTS m9_0.4.0-1_amd64.ubuntu24.04.deb sudo apt install ./m9_0.4.0-1_amd64.ubuntu24.04.deb
Ubuntu 26.04 LTS m9_0.4.0-1_amd64.ubuntu26.04.deb sudo apt install ./m9_0.4.0-1_amd64.ubuntu26.04.deb
Debian 13 m9_0.4.0-1_amd64.debian13.deb sudo apt install ./m9_0.4.0-1_amd64.debian13.deb
Fedora 43 m9-0.4.0-1.fc43.x86_64.rpm sudo dnf install ./m9-0.4.0-1.fc43.x86_64.rpm
Rocky 9 (RHEL 9, Alma 9) m9-0.4.0-1.el9.x86_64.rpm sudo dnf install ./m9-0.4.0-1.el9.x86_64.rpm
Arch m9-0.4.0-1-x86_64.pkg.tar.zst sudo pacman -U ./m9-0.4.0-1-x86_64.pkg.tar.zst

What changed since the previous release

  • FRAME POOLS (report par 2.3): every procedure body allocates its intermediates from an implicit arena freed on exit, carried in the err slot rather than the signature, so the C ABI keeps one trailing parameter. + concatenates strings into the frame -- it no longer allocates from the never-freed HEAP -- and a function can build and RETURN a string: the result lands in the CALLER's arena, with tail calls forwarding the destination. s := s + x in a loop is linear (the arena extends its top allocation in place); the fast path is not a guarantee and DynStr remains the accumulation API, as par 2.3 states.
  • RETENTION IS DECLARED, CHECKED, AND TRACED (report par 4.1). KEPT, the 61st keyword, follows a parameter's mode and declares that the callee stores the reference somewhere that outlives the call. The checker computes, per procedure, where every store's destination escapes to -- module state, the caller's storage through a parameter, the RETURN value, a callee -- and refuses an undeclared retention naming what it reaches and what to write. The declaration composes upward the way RAISES does: a borrow passed to a KEPT parameter must itself be declared, a concatenation passed to one is refused outright (it dies with the frame), and a borrow laundered through a local copy, an IS SOME or CASE-pattern binder, or a sub-slice is traced to its origin ("borrowed msg (carried by t) reaches module state"). A KEPT parameter never seen retained is reported back as the kept-unseen ledger class, so a signature cannot quietly overstate. The standard library declares its own retentions: about 120 KEPT marks, every one demanded by the checker rather than guessed. Both checkers are held to byte-identical diagnostics and ledgers over the whole corpus (semdiff, 62 probes).
  • m9_err is m9_state, because it stopped being only about errors: the per-call slot now carries the RESULT ARENA of the frame-pool machinery -- where + builds, and where a string-returning function leaves its answer for the caller -- beside the exception it always carried. C drivers written against 0.3.1's m9rt.h must rename the type; the generated C did the same.
  • A CONST may be declared inside a procedure; a local CONST that shadows a module CONST is refused rather than resolved.
  • m9c -g emits #line directives, so gdb breaks, steps and lists in the M9 source rather than the C it became. Implies --keep-c; changes no other flag (not -O0: silently lowering optimisation under a debug flag would be flag-dependent semantics).
  • PURE is checked: no write through VAR/OWN, no module-variable write, no allocation from a caller's pool, and -- the rule that carries the weight -- a PURE procedure may call only PURE procedures, which makes "no I/O" true without the checker knowing what I/O is.
  • MONITOR fields are reachable only through the bound first parameter, enforced; the check found four programs reaching in from module bodies and improved all four.
  • A repeated scalar CASE label is refused at compile time, where it is decided, instead of surfacing as a C error against generated code the author never wrote.
  • The public repository now carries everything its gates compare against -- the must-not-compile museum, the checker probes, the parse probes, the benchmark programs, the Modula-2 and Object Pascal reference implementations -- so the differential claims can be re-run from a public checkout rather than taken.

Each .receipt records the distribution the package was built on, its sha256, the tarball's sha256, the gcc that built it, and the smoke line. Nothing here was modified after it came back from the build machine except the 3 debs' file names, which carry their distribution because they would otherwise be the same name (dpkg reads the control file, not the name).

Chapter 0 of the tutorial (M9Tutorial, tutorial.modula9.net) covers installation from these and from source.