2026-10-01: Menai v0.6

Published: 2026-10-01

Menai v0.6 has a huge amount of new and updated functionality. Big things are changes to the compiler and the evolution of what will become a very thorough standard library.

Menai v0.6

Menai v0.6 touches on a lot of important themes:

  • Adding a standard library
  • Reworking the compiler passes to be pure functions and updating the pass manager to use them more effectively
  • Significantly improved code generation
  • Significantly improved compilation speed
  • Cryptographic hashing and CRC32 capabilities for bytes objects
  • A tracing profiler

New features:

  • Added menai-eval, a tool that compiles and evaluates a .menai file (or an expression from stdin) and prints the result. It can optionally profile the compiler with --cprofile and/or VM execution opcodes with --opcodes, and both may be combined.
  • Removed menai-profile as menai-eval does everything it did and more.
  • Added an approach for implementing negative tests in menai-test.
  • Added bmp-decode and bmp-encode modules to read and write BMP image files.
  • Added deflate-compress and deflate-decompress modules. These compress and decompress using DEFLATE (RFC 1951) compression.
  • Added zip-create, zip-entries and zip-extract modules. These write/read a ZIP archive's central directory and can process stored and deflated entries.
  • Added zlib-compress and zlib-decompress modules. These compress and decompress a zlib stream (RFC 1950).
  • Added png-decode and png-encode modules. These read/write non-interlaced 8-bit PNG image files.
  • Added a json-encode module to serialize JSON and renamed json_parser to json-decode.
  • Added a bytes-crc32 primitive. This computes the CRC-32/ISO-HDLC checksum of a bytes value as an integer.
  • Added floating-point bytes primitives: bytes-read-f32-le/-be, bytes-read-f64-le/-be, bytes-append-f32-le/-be, bytes-append-f64-le/-be, bytes-write-f32-le/-be, and bytes-write-f64-le/-be. These mirror the multi-byte integer operations, encoding and decoding IEEE-754 values.
  • Reworked the type propagation optimizations. Removed the old implementation and added a new one based on interprocedural analysis. This allows return types to be back propagated to callers and to remove type guards that are provably not necessary.
  • Replaced the concept of the "prelude" functions being special global symbols and instead made them a letrec around the user's program. This removes a number of idiosyncracies in the internal design.
  • Improved the slot allocator so it eliminates more redundant MOVE opcodes in self-recursive loops.
  • Added a loop rotation CFG pass. A self-recursive loop with its test at the top is rotated so the test is evaluated at the bottom.
  • Improved the constant type annotations in the disassembler.
  • Added cryptographic hashing of bytes values: bytes-hash-sha2-256, bytes-hash-sha2-512, bytes-hash-sha2-512-256 (FIPS Pub 180-4) and bytes-hash-sha3-256 (FIPS Pub 202). Each takes a bytes value and returns the raw digest as bytes.
  • Added binary floating point read, append, and write operations for bytes.
  • Added a peephole optimization that inlines an unconditional jump targeting a label immediately followed by a RETURN.
  • Added the RETURN_IF_FALSE and RETURN_IF_TRUE opcodes and a peephole optimization that fuses a conditional jump leading directly to a RETURN into a single conditional-return instruction. A RETURN block that becomes unreachable after the fusion is removed.
  • Added a struct instance folding optimization. A struct-is-instance? test whose receiver is proven to be a struct of exactly the tested type is folded to #t and its branch re-wired.
  • Added a tracing/annotating profiler. menai-eval --annotate renders every function's disassembly with per-instruction execution counts and shares.
  • Added support for disassembling a module with menai-disassemble.
  • Made constant folding able to create vectors.
  • Added missing prelude functions.
  • Improved compiler diagnostics and parsing error messages.
  • Reworked the module system and introduced the :: special form.
  • Removed struct-ref and struct-set-ref from the language.
  • Renamed the element access operations for consistency and to remove the mutation connotation of set, add, and remove. Positional reads are now -nth (string-nth, list-nth, bytes-nth, vector-nth), keyed reads are -get (dict-get, struct-get), additions and replacements are -with (vector-with, dict-with, struct-with, set-with), and removals are -without (set-without, dict-without, list-without). This replaces string-ref, list-ref, bytes-ref, vector-ref, vector-set, dict-set, struct-set, set-add, set-remove, dict-remove, and list-remove.
  • Improved the inliner so it can inline letrec-containing bodies.
  • Improved performance of integer bitwise VM operations.
  • Improved the algorithmic performance of deflate, inflate and the Sudoku and Rubik's cube benchmarks.

Bug fixes:

  • Fixed a slot allocation bug that could emit a branch on a register in the outgoing argument zone.
  • Fixed a VM crash when apply is used with a large argument list. reserved slots corrupted memory.
  • Fixed a VM stack overflow when freeing a long list. The list finalizer released the tail recursively, using one C stack frame per element, so freeing a list of a few hundred thousand elements overflowed the C stack. Long lists are now freed iteratively.
  • Fixed a CFG bug where branch constant propagation could remove a phi node whose result was still used by a branch target.
  • Fixed a CFG bug where dead capture elimination failed to remove orphaned PATCH_CLOSURE instructions.
  • Dictionaries created with duplicate keys retained the first value, but should have retained the last one.
  • Sets created with dynamic duplicate elements must not contain duplicates!
  • Fixed a crash in the closure cycle collector when a dead closure was destroyed twice in one sweep.
  • Fixed a problem where desugaring did not correctly honour shadowing of operation names.
  • Struct type recognition is now lexically scoped. Two struct types with the same name in different scopes are distinct, and a struct type is not visible outside the binder that declares it.
  • Fixed a compiler register usage bug.
  • Fixed several soundness bugs in the type analysis, including a phi type-fact bug and a struct field-access rewrite that could omit a required guard.
  • Fixed desugaring of vector-slice.
  • Fixed inliner shadowing and recursion-checker bugs.
  • Fixed a VM crash from deep recursion.
  • Fixed non-determinism in the compiler.
  • Fixed a type guard problem and regressions in loop-invariant code motion, MOVE removal, loop rotation and type propagation.

Internal structure changes:

  • Made the compiler purely functional. Every phase is now a pure function over immutable values, and the AST, IR, CFG, VCode and bytecode models are immutable. Each layer has an immutability test.
  • Made the CFG an immutable value. Terminators reference blocks by id, predecessors are derived rather than stored, and passes receive a context carrying cross-pass state.
  • Reworked the CFG pass manager to run each pass to its own fixed point.
  • Renamed the standard library modules to a format-operation convention.
  • Removed the last elements of compiler global state.
  • Added ADRs recording the inliner recursion rule, letrec-to-loop conversion, CFG immutability, compiler purity, function provenance through containers, and predicate folding over interprocedural facts.