All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Native arm64 macOS. mach now compiles, ad-hoc code-signs, and self-hosts
position-independent arm64 Darwin executables that run on Apple Silicon - the
first published Darwin release binary. Also a format-neutral base-relocation
foundation for position independence, project-scoped mach test, mach run
without a rebuild, a riscv64 ELF build-attributes section, and a parser
correction. No breaking changes.
- target: native arm64 Darwin (Apple Silicon). mach emits position-independent
Mach-O executables (
MH_PIE,LC_MAIN, anLC_DYLD_INFOrebase stream, and an ad-hocCS_LINKER_SIGNEDcode signature) that exec and self-host on Apple Silicon; CD publishes anaarch64-darwinarchive (#1679, #1717, #1722). x86_64 Darwin stays cross-compile-only (#1728). - link: a format-neutral image base-relocation set - the linker collects every in-image absolute pointer once and each object format encodes it, the foundation for PIE/ASLR across targets (#1722).
- arm64:
csetin the inline-asm assembler (#1714). - target: a riscv64
.riscv.attributesISA-string ELF section (#1673). - run:
mach runexecutes the already-built artifact without recompiling (#1482). - test:
mach testcollects only the current project's tests by default; pass--include-depsto widen (#1556).
- macho: coalesce object sections by kind so modules with more than 255 sections
link (Mach-O's
n_sectis a single byte) (#1682). - macho: map the mach header into
__TEXTso the Apple Silicon kernel admits the signed image (#1717). - parser: require a block body for
fin; a barefin stmt;was never intended to parse (#1548).
- deps: mach-std v0.16.2 - the arm64 Darwin runtime (
LC_MAINentry, syscall layer).
Darwin executables, the constant-time secret qualifier in the type checker, and the riscv64 backend hardened against real code. macOS now has working dynamic executables, completing both Darwin triples, and a sweep driven by compiling and running real std under qemu closed every riscv64 codegen gap real programs hit. No breaking changes.
- target: a macOS / Darwin OS substrate plus Mach-O dynamic executables (an
emit_dyn_execthat writesLC_LOAD_DYLINKER,LC_LOAD_DYLIB, anLC_DYLD_INFO_ONLYbind stream,LC_SYMTAB/LC_DYSYMTAB,LC_UNIXTHREAD, and per-arch import stubs), completing thex86_64-darwinandaarch64-darwincross-compile triples. Byte-verified static and dynamic for both (#1178). - target: riscv64 RV64A atomic inline-asm mnemonics,
lr/sc, theamo*family, the.aq/.rl/.aqrlordering suffixes, andpause, so atomic read-modify-write can be expressed for the riscv64 std atomics and runtime (#1668). - sema: constant-time secret-qualifier flow typing. The
^qualifier carries a public-to-secret lattice (public coerces up, any secret operand taints the result), the type checker gates what a leakage model can observe (no secret branch condition, memory index, or variable-latency operand),:^is the only downgrade, and secret pointers are welded and non-launderable. Type-checking only, with codegen taint a later step (#1645, epic #1643).
- codegen(riscv64): long-branch relaxation. An out-of-range B-type conditional
becomes an inverted guard plus a
jal, and an out-of-rangejalbecomes anauipc+jalrtrampoline, resolved by a per-function relaxation fixpoint. Large functions no longer fail to encode (#1666). - codegen(riscv64): local frame slots no longer overlap the saved ra/s0 record, fixing a silent SIGSEGV for any function with an address-taken or spilled local (#1670).
- codegen(riscv64): 32-bit
and/or/xornow encode full-register ops instead of nonexistent word-group instructions, fixing a SIGILL (#1672).
The first working bare-metal build, plus a riscv64 codegen fix surfaced by the
new backend. The freestanding raw object format now has a real flat-image
build path, so an os=freestanding, of=raw target builds end to end.
- build: a flat-image build path for image-producing object formats. An object
format declares through a
produces_imagepredicate whether it round-trips through relocatable.ofiles, and an image format (the freestandingrawwriter) now links straight from the in-memory codegen output, bypassing the per-module.oemit/parse round-trip. This is the last gap to a realos=freestanding, of=rawbuild (#1616).
- codegen: a riscv64 variable-amount shift with a constant value operand
(
1 << node) used as a call argument now materializes the constant into a register before the shift instead of failing to encode. The shift was the lone reg-reg ALU encoder not materializing an immediate operand (#1657). - diag: the unknown-os diagnostic now lists
freestandingamong the expected values (#1617).
First targets on the retargeting foundation. riscv64 lands end to end as the register-machine validator (substrate authoring plus vtable hooks, no shared-backend edits), Mach-O joins ELF and COFF as an object format, and the constant-time work begins with the secret type qualifier in the front end. Releases now ship optimized (opt=2) binaries. No breaking changes, and existing x86_64 and aarch64 builds are unaffected.
- target: a full riscv64 (RV64) isa backend (machine model, instruction
selection, encoder, relocations) and an lp64/lp64d ABI, composing the
riscv64-linuxcross-compile target. Byte-verified against llvm-mc and run to its exit code under qemu (#1172). - target: a riscv64 inline-asm assembler so
asm riscv64 { ... }blocks encode to RV64 bytes, includingecall. The cross lane now runs a qemu exit-code e2e (#1642). - target: a Mach-O object format (writer, reader, static executable) for x86_64 and arm64, keyed on the isa with no new shared backend hook. Cross-compile byte-verified for the darwin triples (#1176).
- frontend: the secret type qualifier
^(^u32,*^u8,[N]^u8,^MyRec) and the:^strip-cast token. Lexer, parser, and grammar only, the de-risking first slice of the constant-time guarantees epic (#1643); no type-checking behavior yet (#1644). - build:
[profile.debug]and[profile.release]profiles with profile-segmented output paths, so debug and release builds no longer clobber each other. Releases build with--profile release(opt=2) gated by a self-host fixpoint, and a release-profile CI lane catches -O2 regressions on PRs (#1591, #1592).
- link: the relocation and ELF-class seam is widened onto the substrate, so a new isa owns its reloc packing and object-format mapping rather than editing shared linker and ELF code (#1625, #1635).
- test:
mach testbounds per-test link memory with a per-test scratch arena reset after every test. Memory was O(test count) through the session arena and tripped the windows runner ceiling atcoff: unwind table alloc failedonce the suite grew (#1653). - codegen: riscv64 sub-word loads read the source width rather than the clamped width, fixing a sub-word load miscompile (#1639).
- codegen:
fp_class_indexfinds the float bank by RegClass kind instead of assuming a >=128-bit float bank (#1624). - docs:
doc/language/files.mdno longer misframesmain()as compiler-known (#1631).
Retargeting foundation. A compilation target is now a composition of named substrates (isa, abi, os, object-format) carrying a by-value machine model, so a new register-machine target plugs in as substrate modules plus vtable hooks with no shared-backend edits. Adds the first bare-metal capability: a freestanding os and a raw flat-image object format. No syntax changes, and existing x86_64 and aarch64 builds are unaffected.
- target: a
freestandingos substrate (no syscalls, no OS runtime, custom_start, image base 0) and arawflat-image object-format writer that lays each segment at its virtual address and emits the bytes with no container, entered at the image base (#1613). - target: object-format selection by name. An optional
ofkey on a[target.<name>]table picks the object format explicitly (e.g.of = "raw"); omitting it derives the format from the os default, as before (#1615). - target: a
MachineModelrecord carrying the machine description (widths, register file and classes, addressing modes, frame model) as data, read by the shared backend stages (#1606).
- target: targets compose by name through name-keyed substrate registries. The
legacy os/arch/abi/of ids are derived from the resolved substrates rather than
authored, and the central
canonical_*switches are gone (#1609). - codegen: lowering, register allocation, and frame layout read the widths, the register file, and the index/address width from the machine model instead of shared constants (#1603, #1607, #1608). Behavior-preserving for the existing targets.
- link: each isa owns its relocation packing through an
apply_relocvtable hook and declares its own ELFe_machine, replacing thearch_idswitches in the linker (#1610, #1612). - target: removed the dead
syscall_layeros field and consolidated primitive-type classification behind aprim_desctable (#1604, #1601).
Fixes a latent miscompile: a runtime array/pointer index narrower than the
machine word (e.g. a u8) was passed to the GEP at its source width, so the
back end scaled an index register carrying undefined high bits and computed a
wild address.
- lower:
lower_index_lvaluewidens a sub-word runtime GEP index to the machine-word index type (zext unsigned / sext signed) beforeemit_gep, so the back end scales a clean register. In-tree index sites cast (::u32/::usize) and masked it; an uncast sub-word index miscompiled (#1596). The 64-bit widen lives in the target-agnostic lowering for now; #1598 tracks moving index normalization into the target-aware codegen.
Incremental type-checking (#1164), completing the incremental front-end: parse, name resolution, and now sema all run as memoized queries, so an edit re-checks only the changed module plus dependents whose typed public surface moved.
- sema:
Q_SEMA(owned-handle query keyed by StableModuleId) with aQ_TYPED_EXPORTSfingerprint firewall — an impl-only edit firewalls type dependents while a public-type change re-checks them.Q_MODULE_NUMBERgates re-check on a graph reshape that renumbers a module's nominal TypeIds.
- type: field tables (keyed by TypeId) are rebuilt per build via a field-table epoch, so a record's field-type change on a long-lived session no longer leaves a stale layout (latent; fresh-session builds were unaffected).
Incremental name resolution (#1164). The front-end is now query-engine-driven through resolve: an edit re-resolves only the changed module plus importers whose public surface actually moved, instead of the whole closure.
- query: parsing and name resolution run as memoized queries (
Q_PARSE,Q_RESOLVE) with owned pointer-graph handles; aQ_EXPORTSfingerprint of each module's resolved public surface is the early-cutoff firewall, so a private-body edit does not re-resolve importers while a public-signature or public comptime-const-value change does. Build-stableStableModuleIds key the cached results across rebuilds.
- parser: a function's
DeclIdis reserved before its body is parsed, so a body edit no longer shifts the function's id (which silently staled importers indexing it). - driver: each module's
Q_EXPORTSis refreshed in topological order during the resolve pass, so an importer is never validated against a stale dependency fingerprint.
Incremental parse caching (first increment of #1164): unchanged modules are no longer re-parsed across builds, so a one-file edit's rebuild re-lexes/re-parses only the changed file instead of the whole reachable closure.
- driver: parsed ASTs are cached in a session-owned
AstCachekeyed by(FileId, source revision)and reused across builds;parse_moduleskips tokenize+parse on a cache hit.ModuleEntrynow borrows its AST from the cache (the cache is the sole owner;dnit_projectno longer frees it), so the cache survives a project teardown and rebuild. - source:
updateno longer bumps a file's revision when the new text is byte-identical to the old, so an unchanged file keeps its revision (and stays a cache hit) across rebuilds.
mach doc now reads first-class documentation, so the compiler, the docstring
lint, and editor hover all share one doc source.
- doc:
mach docsources documentation from each decl'sDecl.docspan viamach.lang.fe.doc(walking the parsed AST, recursing into comptime branches) instead of re-scanning raw source text.#[attr]decorator lines no longer leak into rendered docs, and component whitespace is normalized. Content after# ---that is not a validname: desccomponent is dropped, matching the lint and hover.
First-class documentation. Doc-comment blocks are now captured on declarations
and validated by a docstring lint, laying the groundwork for editor hover and
mach doc to share one source of truth.
- fe: capture each declaration's doc-comment block as a span on
Decl(attribute-aware —#[...]lines between the docstring and the decl are skipped), plusfe/doc.mach, an allocation-free doc-block parser exposing the summary and component lines with their name/description spans. - fe: a
pub-scoped docstring lint (fe/doclint.mach) that warns when a documented component names no real parameter/field/generic/ret(misspelled), is out of declaration order (judged over the documented subset, so partial docs are fine), or has an empty description. It does NOT require completeness — undocumented items are never flagged.
- fe: the doc-block parser no longer mistakes a wrapped prose continuation line
beginning
word:for a component head (continuation lines are indented past the# namecolumn).
Front-end performance and visibility. Bumps mach-std to 0.14.1, whose
linear-time str_region_equals removes the quadratic-parse stall that froze the
front-end of stdlib-heavy builds.
- build:
--verbosenow streams per-modulelex/parse/resolvelines during the front-end, alongside the existingsema/lower/codegenstages (#1567).
- resolve: hash-index the module scope for O(1) name lookups instead of a linear scan (#1565).
- driver: hoist the resolve dep-closure scratch out of the per-module loop, removing O(modules²) allocation during the resolve pass (#1565).
- deps: bump mach-std to 0.14.1 (linear
str_region_equals).
Patch — const-string global references now fold correctly.
- lower: a
str/pointer-typed global initialized to a reference to a const-stringval(pub val B: str = A;) emitted the string as raw inline bytes in the pointer slot instead of a pointer-to-rodata relocation. The global-init folder now routes any initializer that comptime-folds to a string through the rodata + relocation path, generalizing the v2.5.1 comptime-path fix (#1559).
Patch — global initializers now fold comptime intrinsic paths (#1557).
- lower: a module-level
val/varinitialized to a comptime intrinsic path ($project.version,$mach.*) that folds to a string is now lowered to a pointer-to-rodata relocation instead of being mis-emitted as raw inline bytes, so a global likepub val X: str = $project.version;folds correctly. The global-init aggregate folder routes string-valued comptime paths throughcomptime.eval, mirroring expression-position folding (#1557).
Minor — a breaking CLI change to project-root resolution (#1545) and a codegen
fix for field access on struct-typed $each pack elements (#1549). The project
commands now resolve the project root one way only: build, run, test, and
doc each take the project root as a required positional (mach build .); a
bare invocation with no path is a user error, collapsing the three redundant
resolution paths (bare-cwd default, positional, and --cwd) that had drifted
in.
- cli:
build,run,test, anddocnow require an explicit project-path positional (mach build <path>). A bare invocation printserror: missing project path; pass the project root (e.g. '.')and exits1.docpreviously took no positional; it now requires one like the others (#1545).
- cli: the
--cwd <path>flag. Pass the project root as the positional instead (mach build .) (#1545).
- lower: field access (
a.x) or address-of (?a) on a struct-typed$eachpack element fabricated an extern global named after the loop variable, failing at link withundefined symbol.lower_ident_lvaluenow resolves a pack element to its expanded-parameter storage slot, mirroring the rvalue path (#1549).
Patch — windows git dependency resolution (#1538) and a multi-target union-build
crash (#1540). The windows git path failed in two distinct ways, both now covered
by the Windows (native) CI lane, which resolves the vendored std with
mach dep pull instead of a manual clone.
- dep:
resolve_cmdsearchedPATHwith POSIX separators (:list,/path), so it never resolved an executable on windows —PATHis;-separated there and drive-letter prefixes (C:\…) embed a:that shattered every entry. it now selects the list/path separators by target os, sogit.exeis found on windows as it is on linux. also fixes runner resolution inmach run/mach test(#1538). - dep: git was spawned on windows with a reconstructed allowlist environment that
omitted
SystemRoot, so its winsock initialization failed withgetaddrinfo() thread failed to startonce git was found. the allowlist exists only for posixexecve(which does not inherit and exposes noenvironhandle); on windowsCreateProcessinherits the full parent environment natively, so git is now spawned with a nil child env there (#1538). - driver:
walk_comptime_if_unioncached a*ModuleEntry(and AST-arena pointers into it) across a recursive load that canreallocatethep.modulesarray, so multi-target union builds spanning ≥3 OS/arch tuples dereferenced freed memory and crashed (SIGSEGV in tooling/LSP). the taken branches are now snapshotted before recursing, mirroringwalk_use_for_load(#1540).
Minor — Phase 2 (collapse) of the #[attr] decorator migration (#1526): #[attr] is now
the only decorator syntax; the backtick form is removed. Treated as pre-stable churn
rather than a breaking major — the backtick form shipped days ago (2.0.0) with ~0 external
adoption, and the change is purely surface-level (same Decorator AST, same codegen).
Byte-reproducible from the v2.3.0 seed; the vendored-std advance to mach-std 0.14.0 is
inert.
- syntax: backtick decorators (
`symbol(...)`,`library(...)`,`inline`,`align(N)`,`section(...)`). Use the#[attr]form (#[symbol("...")],#[inline], …) added in v2.3.0. A backtick at decorator position now reports a migration diagnostic —backtick decorators were removed in v2.4.0; use #[name(...)]— and recovers at the next declaration (#1535).
- deps: the vendored mach-std advances to v0.14.0 — the
#[attr]-migrated standard library.
Minor — Phase 1 of the #[attr] decorator migration (#1526): the parser now accepts
Rust-style #[attr] decorators alongside the existing backtick decorators. Purely
additive and backward-compatible — both surfaces produce the same declaration AST, and
the compiler's own source is unchanged (still backticks), so it stays byte-reproducible
from the v2.2.0 seed. The v2.4.0 collapse migrates all source to #[attr] and removes
the backtick form.
- syntax:
#[symbol("...")],#[library("...")],#[align(N)],#[section("...")],#[inline]decorator forms. The lexer opens an attribute when#is immediately followed by[(otherwise#stays a line comment — a literal comment beginning#[must insert a space).#[...]and the backtick form attach identically; the decorator AST and sema/resolve/validate are unchanged (#1532).
Minor — correctness and cross-arch coverage, hardening the compiler ahead of a manual audit. This release changes how the compiler emits float comparisons (#1446), so it re-seeds the self-host baseline: it is intentionally not byte-reproducible from the 2.1.0 seed (stage1≠stage2 by design) but converges to a byte-identical fixpoint (stage2==stage3) on both x86_64 and aarch64.
- target/aarch64: AAPCS64 HFA/HVA classification — a homogeneous floating-point aggregate (1–4 members of the same FP type, counted recursively, including >16 bytes) is passed and returned in consecutive SIMD/FP registers (V0–V7), with all-or-memory spill when the run doesn't fit. Completes the aarch64 by-value float-aggregate ABI; x86_64 SysV and win64 emission are unchanged (#1174).
- codegen/x86_64: floating-point comparisons now follow IEEE-754 for NaN, matching
aarch64 — an unordered (NaN) operand yields false for
<,<=,>,>=,==and true for!=(</<=via operand-reversedSETA/SETAE;==asSETE ∧ SETNP;!=asSETNE ∨ SETP). Cross-arch NaN comparisons now agree (#1446). - comptime:
$each f in $fields(T)over a generic type parameter no longer errors at template sema — it defers to monomorphization (mirroring variadic packs), so reusable generic derive helpers (debug[T],equals[T], …) compile. A non-record instantiation is diagnosed at instantiation (#1523). - ci/aarch64: the
Test corpus under qemu-aarch64step is un-gated —ut_manifestgained the[target.linux-arm64]stanza sotarget = "native"resolves to the aarch64 host under qemu (#1391) — and the Integration lane installsqemu-user-static, so the aarch64 integration suites (HFA register placement,aarch64run) execute under emulation in CI instead of skipping (#1464).
Minor — comptime type-directed-dispatch ergonomics and multi-artifact tooling
graphs, surfaced while building the mach-std {} formatter and loading
multi-binary projects in mach-lsp.
- driver: the long-lived tooling union (
build_project_union) now unions the import closure over every declared artifact (the full target × artifact matrix), not just one — a multi-artifact project ([bin.*]+[lib.*]) loads its whole module graph in tooling/LSP instead of erroring on artifact ambiguity. Single-artifact builds and themach build/mach runambiguity guard are unchanged (#1505). - comptime:
$error("msg")is now a real compile-time directive — it fails the build with its message when reached on a live path (an unconditional position or a selected$if/$orarm; a dead arm's$errornever fires) and is valid in both declaration and statement scope. It was previously parsed as a silent no-op (#1511).
- comptime/sema: a
$type_of-comparison$if/$orchain now type-checks only the selected arm, pruning provably-dead arms at monomorphization. Type-directed dispatch over a concrete type no longer needs per-arm identity casts and is statically total (#1511).
Patch — parser error-recovery and a grammar-doc fix surfaced while migrating the mach ecosystem (blit, mach-glfw, mach-sieve, …) to 2.0.0.
- parser: a removed-syntax error at declaration scope — a comptime attribute
setter (
$sym.attr = value;) or a C-style...variadic parameter — now recovers at the next declaration instead of skipping it and reparsing its body at module scope, which sprayed a spurious "expected a declaration" for every non-valbody statement.sync_to_declno longer advances past a token that already begins a declaration; a regression suite (tests/recovery) guards both cases.
- grammar.md: removed the stale C-style
...variadic parameter production (rejected since 2.0.0) — the comptime variadic packname: ...is the only declaration form; function pointer types still carry...for FFI (#1518).
BREAKING. The comptime collapse completes the v1.7 metaprogramming arc and is
the first new-only, post-migration release — hence the major version bump from
the 1.7.0 transition, which kept the legacy surfaces resolving for seed
compatibility. Code that still uses $mach.target.*, the top-level $target.*
root, $<sym>.* attribute setters, or C-style varargs must migrate (see Removed).
The self-host fixpoint holds — stage1==stage2==stage3 byte-identical on x86_64 and
aarch64 (the compiler binary shrinks as the legacy paths are deleted, but it
reproduces itself exactly).
- comptime: the legacy
$mach.target.*and top-level$target.*namespace spellings — build facts read through$mach.build.*and the declared target tuple through$project.target.*(#1480). - comptime: the C-style varargs feature — the trailing
...parameter marker, thevariadicsignature flag,va_arg/va_start/va_end, theva_listtype, the OP_VA_* IR opcodes, and the per-ABI register-save callee prologue (superseded by comptime variadic packs in v1.7.0) (#1478). - comptime: the
$<sym>.symbol/$<sym>.library/$main.symbolattribute setters — superseded by`symbol(...)`/`library(...)`backtick decorators (#1476); a stray=after a comptime directive now reports a migration diagnostic (#1478). - ci: the content-based seed-safety guard (#1486) — obsolete now that the seed is
v1.7.0 and the vendored std legitimately uses
va:/$each/$mach.build.*. - abi: the vestigial VaModel register-save geometry left behind by the varargs
removal — the 12 save-area fields, the per-ABI
va_savehelpers, and the never-emittedMIR_VA_FP_SAVEpseudo. The functional call-ABI facts (the SysV vector-count register and the win64 float-duplication) are retained (#1478).
- deps: the vendored mach-std pin advances to v0.12.0 — the migrated, new-only
std (comptime packs +
$mach.build.*) (#1480, #1478). - comptime: float-literal folding imports
std.math.bignumfrom the vendored std instead of the temporary in-tree bignum port added for v1.7.0 seeding (#1483).
Comptime rework and first-class variadics — the last big breaking change before
stability. The C-style va_list surface is gone, replaced by comptime variadic
packs that monomorphize per call; the $ comptime channel gains first-class type
values; and per-declaration attribute directives move onto backtick decorators.
The compiler source uses no new syntax, so the self-host fixpoint converges
byte-identical on x86_64 and aarch64 — except that float-literal folding is now
correctly rounded, which intentionally changes the compiler's own float constants
relative to the v1.6.0 seed (this release re-seeds).
- comptime: first-class type values —
$type_of,$fields(T), field projectionv.[f], bounded$eachexpansion, and type==in$if(#1472, #1473). - variadics: comptime variadic packs — a
va: ...pack parameter consumed by$each arg in va, monomorphized per call-site type-list (noany, no runtimeva_list);va...forward,va.len(#1474, #1475). aarch64 variadics work. - decorators: per-declaration backtick decorators
`symbol`/`library`/`inline`/`align`/`section`(codegen-only;align/sectionemit, including per-function section placement) (#1476). - comptime: provenance-rooted namespaces — resolved
$mach.build.*, fixed$mach.{os,arch,abi,mode}.*tag tables, and$project.*from the manifest (#1471). The legacy$mach.target.*spellings still resolve this release. - comptime: correctly-rounded float-literal folding via exact bignum — large
exponents no longer lose precision (e.g.
1.0e100) (#1483). - ci: a self-host fixpoint lane that byte-diffs seed→stage1 vs stage1→stage2 on
PRs, catching uniform codegen changes the release
cmpcan't see (#1488).
- deps: the vendored mach-std seed freeze is now single-source —
mach.lock+mach dep pullwith an immutablecommit/pin, the redundant submodule removed, and a content-based seed-safety guard (#1486).
- dep:
mach dep pullnow findsgit.exeon Windows (the lookup missed the.exeextension) (#1506).
The aarch64-linux debut: mach now cross-compiles and self-hosts on 64-bit ARM linux (AAPCS64) alongside x86_64 linux and windows. The compiler itself makes no variadic calls, so the self-host fixpoint converges byte-identical on aarch64 under qemu exactly as it does natively on x86_64.
- target/aarch64: a complete aarch64-linux back end (#1431). The A64 fixed-width
encoder emits 32-bit instruction words directly; instruction selection covers
the scalar integer and floating-point paths; integer division and remainder
lower to
sdiv/udiv+msubwith the defined divide-by-zero and signed-overflow trap semantics; and variable (runtime-count) shifts lower to the register-operand shift forms. - target/aarch64: the AAPCS64 calling convention — argument and result
classification across the general-purpose (x0–x7) and SIMD/FP (v0–v7) register
banks, the x8 indirect-result register for large/
sretreturns, and natural-alignment of stack-passed arguments. - target/aarch64: calls, relocations, and frames —
blcalls and theadrp+add/ldrsymbol-address idiom emitting the R_AARCH64_CALL26, ADR_PREL_PG_HI21, ADD_ABS_LO12_NC, and LDST*_ABS_LO12_NC relocation kinds, with the leaf and non-leaf frame prologue/epilogue (stp/ldpof x29/x30). - target/aarch64: inline-asm mnemonic support for the A64 instruction set, so the
mach-std aarch64 runtime entry (
_start) and OS layer assemble through the same per-architecture asm path as the x86_64 runtime. - ci/release: the aarch64-linux release asset is enabled (
RELEASE_AARCH64), with a qemu-aarch64 smoke-test that proves the cross-built aarch64machcan both build and run a real project under emulation before it ships — mirroring the windows/wine compiler smoke. CI's aarch64 lane cross-builds mach to aarch64 and byte-verifies the emitted encodings; the runtime is exercised by that qemu smoke, the aarch64 self-host fixpoint, and the in-source test corpus run under qemu-aarch64 (#1464).
- target/aarch64: f64 and f32 memory loads and stores selected the wrong register
class — addressing a general-purpose register where a SIMD/FP register was
required — so a float loaded from or stored to memory (a struct field, array
element, or pointer dereference) now uses the SIMD
ldr/strforms (#1459). - parser: the bare statement keywords
cnt/brkare disambiguated from identifiers via lookahead — they are keywords only in the barecnt;/brk;statement form, socnt = expr;now parses as an assignment instead of failing with the misleading "expected ';' after 'cnt'" (#1458). This was a prerequisite for aarch64: the aarch64 std runtime's inline asm uses thebrkmnemonic (asm aarch64 { brk 0 }), which the pre-fix parser mis-parsed as the keyword.
- aarch64-linux v1.6.0 ships without variadic formatting (
printf/format/vformat): mach-std gates the variadic surface out of the aarch64 build, with the redesign deferred to v1.6.x as a cross-platform varargs effort. The compiler makes no variadic calls, so self-host is unaffected; only aarch64 programs that call the variadic formatting helpers are impacted.
Tooling-prep patch. Adds reload-friendly source handling so a long-lived session
(e.g. the language server) can rebuild its project graph on every save without
growing without bound, and completes the target-layer interner-elimination by
dropping the now-dead interner parameter from register_all and every target
registrar — the target vtables are now immutable singletons.
- driver/source: reload-friendly source handling for long-lived sessions. The
session
SourceMapnow dedups by path (source.load): re-loading a path returns its existingFileIdand swaps the text in place rather than appending, so a session that reloads its project graph on every save no longer grows without bound.dnit_projectnow also resets the session's per-build module registries (AST/sema/resolve/comptime/fqn and export/import maps), dropping the borrowed references the freed Project leaves behind so one Session is reusable across rebuilds. This is path-dedup + reclamation only; reusing untouched ASTs/resolve results across a rebuild (true incremental rebuild) is tracked separately in #1164 (#1389).
- target:
register_alland the target registrars no longer take an interner (or the vestigial allocator) parameter. The registrars set immutablestrvtable fields and intern nothing, so the parameter had been dead since the OS-vtable interner-elimination — each registrar is now trimmed to exactly the registry it installs into, andmach infono longer builds a throwaway interner to call them. Behavior-preserving, verified by the byte-identical self-host fixpoint; completes the interner-elimination begun in #1402 (#1418).
Stabilization + multi-target-prep release. Adds the multi-target union Project for long-lived tooling (the language server now sees modules reachable only on a non-default target), lands the post-windows compiler-stability audit fixes (object-format parse-leak reclamation + truthful ELF symbol counts, the COFF REL32 addend convention, a distinct unknown-target-name diagnostic, and an arm64 register-id correction), and completes the OS-vtable interner-elimination — readying the target layer for the v1.6 architectures.
- driver:
build_project_unionbuilds the union of every manifest target's import closure into one deduplicated Project, so long-lived multi-target tooling (e.g. the language server) sees modules reachable only on a non-default target. Each$ifchain follows the first active branch of every declared target; the Project resolves under the default (native-resolved) target's tuple — the documented v1 default, "the default target's branches win" — and a module that does not fully resolve there records diagnostics without aborting the build (#1391).
- target: the os/arch name↔id mapping is consolidated to one canonical table per
dimension —
os.os_id_for/os.os_name_forandisa.arch_id_for/isa.arch_name_for— replacing the copies that lived indriver.machandmanifest.mach. An unrecognized manifestos/isaname now reports a distinct "unknown target os/isa name '' (expected: ...)" diagnostic instead of folding to*_UNKNOWNand being mis-reported as an unsupported pair (#1412). - The
OsVTableentry_symbol,syscall_layer,libdir, anddynamic_linkerfields are immutablestrconstants set directly by the OS registrars, no longerStrIdfields re-interned per session; the linker now interns these at the point of use (the entry-symbol lookup and the dynamic interpreter path), completing the interner-elimination from the OS vtable started in #1377. Behavior-preserving, verified by the byte-identical self-host fixpoint (#1402). - target: the dead
IsaVTable.endiannessfield is removed. It was set by the ISA registrars but read by nobody — the object-format writers hardcode little-endian — so the field documented a behavior that did not exist. TheENDIAN_LITTLE/ENDIAN_BIGenum is retained for the eventual big-endian abstraction, and the comments that claimed endianness was honored now state the little-endian assumption plainly (#1411). - objfmt (COFF): the writer/reader resolve every relocation's target symbol through a name→index map built once per pass instead of a linear symbol-table scan per relocation, turning the two reloc passes from O(reloc × symbol) into O(reloc + symbol). Behavior-preserving (same indices resolved, same unresolved-symbol error), verified by the byte-identical self-host fixpoint (#1413).
- target (aarch64):
IsaVTable.fp_scratch_regfor the (stub) arm64 backend is now the composite vector register idregid_make(REG_CLASS_ID_XMM, 31)instead of the raw bank index31, which would have read as a GP register. Dead today (the arm64 backend has no encoder), corrected before it seeds the future backend (#1414). - objfmt: the COFF and ELF object-file parsers allocate the section/symbol/relocation
arrays up front but left each
ObjectImagecount at0until its populate loop finished, so an error return before or within those loops (a truncated or hostile object) leaked the arrays —obj.dnitfrees nothing when the counts are0. Each count is now set to its populated size immediately after the array is allocated (the loops use local write cursors), so teardown reclaims the full arrays on any later parse error; the ELF symbol array is sized and counted to the populated count (excluding the reserved index-0 entry) so allocation, count, and entries all agree. Emitted output is unchanged (#1410). - objfmt (COFF): REL32 relocations are next-byte-relative (
S + A − (P+4)), but the abstract addend uses ELF's field-relative convention (S + A − P), so the writer's on-wire field and the reader's recovered addend were off by 4 — a foreign (MSVC/clang/GAS) COFF read by mach landed calls 4 bytes past target, and mach's emitted.objwas off by −4 under link.exe/lld. The writer now foldsA_coff = A_elf + 4and the reader recoversA_elf = A_coff − 4for REL32 only; ABS64 (ADDR64) and ADDR32NB are unaffected. A mach-emitted.obj's REL32 wire field changes (that is the fix — acall's field is now 0, the COFF convention), but a mach→mach round-trip recovers the same abstract addend and links to the same final binary; only foreign-COFF interop behavior changes (#1409).
Correctness patch for two silent defects in shipped v1.5.2: a relocation
patch-site miscompute that corrupted out-of-imm32 constant stores to globals,
and an extreme float-literal exponent that hung the compiler.
- codegen (x86-64): an 8-byte store of an immediate outside signed-
imm32range to a global is legalized intoMOVABS r11, imm64+ a register store, but the PC32 relocation was patched at the pre-legalization offset — landing 4 bytes early, corrupting the encoding and leaving the store pointed atrip+0. The patch site now tracks the legalizeddisp32(the same fix covers the ALU memory-destination and inline-asmmov [sym], imm64paths). No diagnostic was emitted; ordinary code storing a large constant to a global silently miscompiled (#1407). - comptime: a float literal with an extreme exponent (e.g.
1.0e2000000000) hung the compiler in the unbounded decimal-scale loop and could silently fold to its mantissa on i64 exponent overflow. The exponent accumulator is now clamped past the point f64 saturates, so such literals fold toinf/0.0instead (#1408).
Maintenance patch: path dependencies materialize through the standard library
instead of host ln/rm, the target vtables become immutable str-named
singletons, and the windows CI gains a trailing-import-descriptor regression
guard.
- Path dependencies materialize via the std filesystem primitives (
fs.symlink,fs.remove_all) instead of shelling toln -s/rm -rf, so they no longer require hostln/rm— fixing path-dependency materialization on native Windows, wherelnis not onPATH. Thegittransport is unchanged (#1392).
- The OF/ISA/OS/ABI vtable
nameand register-class names are immutablestrconstants set directly by the registrars, no longerStrIdfields re-interned per session; the deadOfVTable.file_extensionis removed. Behavior-preserving, verified by the byte-identical self-host fixpoint (#1377; the remaining interner-elimination is tracked in #1402). - CI: a
threadsyncwine integration test guards the trailing PE import-descriptor call-thunk against regression (#1388), and the win64 wine tests are bounded with atimeoutso a faulting binary fails fast instead of hanging the lane (#1399, #1404).
Native Windows lane: CI now builds mach.exe and runs the in-source test suite
on real Windows, not just the wine cross-compile path. Two codegen fixes complete
the windows backend end to end — per-page stack probing for frames over a page,
and per-descriptor PE import call-thunks — and the vendored standard library is
updated to its windows-complete release. The native lane passes 468/468.
- Windows function prologues now probe the stack one page at a time for frames
larger than a page, instead of a bare
sub rsp, N. Windows commits the stack one guard page at a time, so a single large subtraction skipped the guard page and the first write near the bottom of the frame faulted on reserved memory (STATUS_ACCESS_VIOLATION); Linux and wine auto-grow the stack on any in-range fault and so never surfaced it. The encoder now emits the inline__chkstkpage-walk (mach links no runtime) when the target OS commits incrementally — a newOsVTable.stack_probeflag (true on Windows, false on Linux/Darwin) gated by the OSpage_size. This was the root cause of the native-Windows exec failures in briar-systems/mach-std#262 (a 32 KiBspawn_redirectedcmdline frame) (#1395). - PE import call-thunks for the last import descriptor jumped through the
previous descriptor's null IAT slot (a
jmp 0access violation on the first call).pe_iat_slot_rvanever advanced its dependency index, so it re-scanned only the first descriptor when mapping an import ordinal to its IAT slot — the trailing descriptor's stubs landed short of itsFirstThunk. Reordering thelibslist moved the breakage to whichever DLL was last. Now every import's thunk targets its own descriptor's slot, so the trailing descriptor's calls (e.g. advapi32'sSystemFunction036) dispatch correctly (#1388).
Inline-asm & foundations: carry-flag mnemonics and numeric local labels in x64
inline assembly, per-symbol DLL attribution for windows imports, the first PE
output the native Windows loader accepts, float % correctness, nil
coercion to function types, materialized path dependencies, declared
foreign-target runners, and session-owned target registries under the hood.
mach testandmach runaccept--runner <cmd>: every child exec becomes<cmd> <binary> <args...>, the declared host-side launcher for foreign-target artifacts (e.g.--target windows --runner wine). The value is a single command name or path (no shell-style word splitting), resolved onPATH. Absent the flag, binaries are exec'd directly and a launch failure is reported as a failure — no auto-detection (#1345).- One-line install scripts:
install.sh(Linux) andinstall.ps1(Windows), shipped in the repo and as release assets. They resolve the latest tag from thereleases/latestredirect, download the versioned archive for the host, verify it againstSHA256SUMS, and install to~/.local/bin(%LOCALAPPDATA%\mach\binon Windows);MACH_VERSIONandMACH_INSTALL_DIRoverride the release and destination (#1352). - Releases now ship versioned archives —
mach-<version>-x86_64-linux.tar.gzandmach-<version>-x86_64-windows.zip, each containing the binary and LICENSE — alongside the existing assets, withSHA256SUMScovering the full set (#1352). - x64 inline assembly accepts the carry-flag mnemonics
jc/jnc(and thejb/jae/jnbaliases) andsetc(and thesetb/setae/setnbaliases), reusing the existing conditional-branch and SETcc encoders. Previously onlyje/jz/jne/jnzwere recognized, forcing carry-flag handling through.byteescape hatches (#1359). - x64 inline assembly accepts NASM-style numeric local labels: a
<digits>:statement defines a block-local label and<digits>f/<digits>bbranch targets resolve to a rel32 within the function (no relocation). Every branch mnemonic takes a symbol or a local-label target; a backward reference binds to the nearest preceding definition and a forward reference to the nearest following one, redefinition of a number is allowed, and an unresolved or malformed reference is a hard compile error. This unblocks block-local forward branches such as thejc 1f/1:shape in std's darwin syscall wrappers (#1365). $<sym>.library = "<dll>"pins anextimport to a specific dependency DLL, giving the PE (Windows) import directory per-symbol attribution. Imports were previously all forced onto the first dependency (kernel32.dll), so extra DLLs in[target.*].libsemitted only empty descriptors; an attributed import now lands under its named library, an unattributed one still binds to the first, and pinning to a library absent from the link's dependencies is a hard link error rather than a silent fallback. Composes with.symbol— the rename sets the imported name,.librarythe DLL it is imported from (#1382).
- Float
%now evaluates to the truncated (Cfmod) remaindera - trunc(a / b) * b, whose result takes the dividend's sign (5.5 % 3.0 == 2.5,-5.5 % 3.0 == -2.5). The operator had no float lowering: the runtime path fell through to the integer IDIV/DIV opcodes, running an integer divide over the raw IEEE-754 bit patterns (a passthrough-below-divisor, near-zero-above shape), and the comptime fold rejected it as non-constant. Both now synthesize the remainder from the existing float divide / truncating conversion / multiply / subtract primitives, exact for|a / b| < 2^63(#1378). nilcoerces to function types, so a fun-typed binding can be explicitly nil-initialised, not only default-initialised. Previouslyvar q: fun(u32) = nilwas rejected withtype mismatch: expected fun(u32), found *u8and the cast spellingvar r: F = nil::Ffailed lowering withglobal initialiser must be a constant expression, even though a fun-typed value already compares== nilandnil::Fwas accepted in argument position. nil now coerces to any pointer-like target —ptr,*T, orfun(...)— uniformly across globals, locals, record fields, array elements, arguments, and return slots, and a nil global initialiser (bare or written through pointer casts) folds to the null constant. nil into a non-pointer slot remains a type error (#1369).mach dep pullnow materialises apathdependency at<dep>/<alias>/as a relative symlink to the resolved source instead of silently doing nothing — previously it printed "mach.lock up to date", exited 0, and never created the dep directory, so any later build failed to resolve the dep's modules. Thepathis resolved relative to the requiring manifest's directory; a stale link is replaced and an already-correct one left in place (idempotent), while a missing directory, a directory without amach.toml, or a vendor location occupied by a real directory is a hard error. A path dep carries no lock entry — its manifestpathis the record (#1370).- Sema reports a teaching diagnostic for every symbol kind that can never be a
value reaching value position — a record, union, or
deftype name (local or imported, bare oralias.member), a generic type parameter, and a member access on an expression with no value (a call with no return type) — instead of silently poisoning and surfacing linkundefined symbolor span-less lowering errors; completes the #1343 silent-poison audit (#1348). - The PE emitter no longer produces executables the native Windows loader
rejects with
ERROR_BAD_EXE_FORMAT. The image base reserved a full 64 KiB below the first segment, placing the first section at RVA0x10000while the headers spanned one page — a 15-page unmapped gap the loader refuses (wine tolerated it). ImageBase now sits exactly one header span below the first segment, so the first section maps immediately after the headers with no gap, matching the layout MSVC emits. Everymach-built Windows binary, including the publishedmach.exe, now launches natively (#1374). - A
$<sym>.symbolrename on anext funis no longer clobbered. The bareextidentifier was re-registered over the rename afterscan_export_directivesrecorded it, so renames on foreign imports silently did nothing and bound under the mach identifier; the bare name is now only a default that an explicit.symboloverride wins, order-independently (#1382). mach initvalidates the project id before scaffolding: a name the manifest grammar would reject (., path separators, spaces) is refused with nothing written, instead of silently scaffolding an ungrammatical[bin..]manifest. The positional name is taken verbatim — no basename derivation (#1355).- A type mismatch against a call with no return type reads
expected i64, found no value(with a clarifying note) instead of rendering the misleading<error>placeholder, and diagnostics consistently say "returns nothing" / "no value" — mach has novoid(#1360). infer_generic_callreports internal generic-substitution and type-argument allocation failures instead of silently poisoning the expression (#1361).
Patch release clearing the open bug board: environment forwarding for spawned
user programs, the -o absolute-path override, fwd module re-export
consumption, and inline-asm comment handling.
- Bumped the vendored mach-std to v0.6.0 (adds
std.process.env.environ(); fixes the thread spawn/join deadlock and json key lookup).
mach testandmach runnow forward the parent environment to spawned user programs instead of execing them with an emptyenvp;getenvin a test or run child sees the inherited variables (mach-std#197).- An absolute
-opath is honored verbatim; previously its leading slash was swallowed by an unconditional join with the project root and the output landed project-relative (#1340). - A consumer can chain through
fwdmodule re-exports in expression position (lib.alpha.answer()), at any depth including afwdof another library'sfwd; a module alias referenced in value position now reportsa module alias is not a valueinstead of silently poisoning and surfacing internal lowering errors (#1343). - Inline-asm comments are now opaque to the instruction parser regardless of
their bytes. A comment containing
;was split into a phantom instruction and one containing{...}was misread as a local binding; comments are now stripped to end-of-line when the asm body is materialized, before any substitution or statement tokenization (#1297).
- The unused test-runner write-primitive machinery (
RunnerWrite,OsVTable.runner_write): since 1.4.0's per-test executables, test binaries perform no OS output — the host process reports — so the vtable hook had no consumers on any OS (#1292).
The clean-break release: one manifest format, the project module, and the test infrastructure the language was designed around.
- The project module:
[project] module = "lib.mach"— a bare project-iduse glfw;/fwd glfw;resolves to the project's declared module. Never inferred; module-less bare imports and dangling declarations error loudly. - OS link overlays:
[os.<name>] libs = [...]— link requirements scoped to one tuple component, cascading to consumers across every ISA/ABI of that OS ([isa.*]/[abi.*]reserved). - Per-test executables: every
testblock builds to its own standalone program under thetestspath template;mach testruns each in its own process — a crashing test reportsFAIL(signal n)and the run continues — with--listand--filter. Test artifacts never touch the project binary path. - The compiler versions itself from its manifest (
$project.version): release bumps are now a one-file change.
- One manifest format. The pre-1.3 manifest system is removed entirely —
[targets.*],dir_*keys, string opt levels, andtype/path/versiondep stanzas no longer parse. Projects update their manifests by hand; doc/manifest.md documents the format. [profile.*] optis an integer (0 | 1 | 2).mach dep pullclones into an empty dependency placeholder directory (plain-clone installs work without--recurse-submodules).- Inline-asm comments are fully opaque to the instruction parser; every audited diagnostic now names its subject and carries a span.
Transitional self-seeding release. Carries the v1.4.0 feature line developed so far and exists so the next release can drop the old manifest format entirely: this binary reads both manifest formats; the next reads only the current one.
- The new
mach.tomlmanifest format: explicit[target.*]/[bin.*]/[lib.*]/[profile.*]/[deps.*]stanzas, path templates,nativetarget resolution, multi-artifact matrix builds, tuple-matched cascading dependency libs, per-target comptimedefines. - The
mach depmodel:pull/updatewith a manifest-is-intent lockfile law,git/pathsource forms, transitive resolution. - Comptime namespace roots
$project.*,$target.*,$bin.*; bare$identis now rejected with a teaching diagnostic. mach info(andmach info --version): compiler version, build host, and the registered target capability surface.- Mixed-numeric comparisons: legal and value-correct across signedness and widths (comptime agrees with runtime); int↔float still requires a cast.
- A diagnostics overhaul: ~70 audited messages now carry spans, name the offending symbol, and say what to do.
- Release-mode builds are ~5x faster (quadratic pass costs removed); the optimizer gained phi simplification, post-inline promotion, and call-graph aware inlining.
- The IR verifier is real (dominance, reachability, type agreement) and CI
enforces
--release --verify-ir. - Relocation kinds are a closed enum; the object-format layer no longer captures per-session interners.
- The inline corpus grew to ~390 tests; comment content inside
asmblocks is now fully opaque to the instruction parser.
Correctness and foundations release. A full-compiler audit (125 findings, 45 confirmed bugs) was executed end to end: every confirmed miscompile is fixed, the ABI abstraction boundary is complete ahead of new targets, and mixed-numeric comparisons joined the language with value-correct semantics.
- Mixed-numeric comparisons: integer comparisons across any signedness
and width are now legal and compare mathematical values, with results
identical in both operand orders (
i64/u64lowers to a sign-test plus unsigned compare). Float widths mix exactly; int↔float still requires an explicit cast. Comptime evaluation agrees with runtime semantics on the same boundary cases. - Variable-index function-pointer dispatch:
table[i]()now parses correctly (the bracket payload is resolved against the callee — generic call vs index-then-call — instead of guessed in the parser). - C interop, win64 variadic, float-argument, conversion-boundary, comptime/ runtime-agreement, and release-verifier integration suites run in CI.
- Miscompiles: FP-register interference tracking (swapped float arguments
collapsing); win64 callee-saved XMM6–13 never preserved (with full
UWOP_SAVE_XMM128unwind coverage); inline-asm clobber inference implemented as documented;u32→float converting as signed; registeri32→i64sign-extension reading only 16 bits; SysV float-bearing aggregates never classified to SSE registers (C FFI divergence); comptimeu64-range constants evaluating as negative. - The program entrypoint entered every callee with an 8-byte misaligned stack (mach-std v0.4.2) — fatal for C callees using aligned SSE accesses.
- The IR verifier now verifies: real dominance (near-linear), reachability,
operand type agreement, dangling-definition detection;
--release --verify-irpasses on the full compiler and is enforced in CI. - Optimization passes: constant folding no longer crashes the compiler on
INT64_MIN / -1; dead phis are eliminated; phi simplification unblocks constant propagation across inlining; the inliner refuses call-graph cycles. - The ELF/COFF writers and linker fail loudly on unresolved relocation symbols, unknown relocation kinds, and malformed objects; weak/strong symbol resolution is order-independent; section/relocation counts are validated before narrowing.
- IR teardown frees operand arrays and aggregate blobs by true capacity — correct under any allocator.
- CLI:
mach run --argument passthrough,mach init --name,mach build <path>, a lockfile-writer heap overflow,--quiet/--colornow functional, unified exit/signal handling. - Frontend: the grammar holes vs the locked spec (multi-line strings,
val/varforms, integer-literal overflow) are rejected with diagnostics; parser OOM can no longer yield a silently corrupt AST. - Generics: cross-module arity checking, deeply nested parameter
substitution,
fwdmodule re-export, duplicate diagnostics.
- The ABI layer is complete and arch-keyed: selection consults (isa, os), classifiers carry an explicit by-reference class, the variadic model is vtable-driven end to end, and the test runner's output primitive comes from the target OS — groundwork for the aarch64 and darwin targets.
miris split into per-concern modules (data model, lowering context, IR→MIR core, calling-convention lowering, variadic expansion) with byte-identical output.mach initscaffolds mach-std pinned tobranch/main(v0.4.x); the vendored std is v0.4.2.- Object-format writers share a common binary-IO layer.
Native Windows release. mach.exe now builds Mach itself on the win64 target
to a byte-identical fixpoint (verified under wine), and releases ship
per-target artifacts.
- Per-target release artifacts:
mach-x86_64-linux,mach-x86_64-windows.zipandSHA256SUMSalongside the baremachseed binary; the windows artifact is gated on a wine smoke test in whichmach.exebuilds and runs a real project. - CI now runs the integration suites (dynlink, extlink, opt, win64byref, win64fnptr) with wine on every pull request, plus a windows cross-build of the compiler.
- Per-function
.pdata/.xdataunwind metadata on win64 executables, with spec-correctUNWIND_INFOencoding.
- Function-to-pointer casts (
fn::*u8) lowered as a 32-bit truncating move, corrupting every type-erased function pointer above 4 GiB — the fault that kept nativemach.exefrom running on win64 image bases. - win64 by-reference aggregates passed as a fifth-or-later argument: caller and
callee now agree on the spilled hidden pointer via an explicit ABI class
(
CLASS_STACK_BYREF), covering sub-pointer (3/5/6/7-byte) aggregates. - win64 unwind metadata: removed the incorrect frame-register declaration,
fixed
UWOP_SAVE_NONVOL_FARto record unscaled offsets, and stopped misreading[r13+disp]stores as frame saves. - The ELF and COFF writers now fail loudly, naming the symbol, when a relocation targets an unresolved symbol instead of silently emitting a corrupt object.
- COFF extern-function inference is scoped to foreign objects, indexed O(n), and propagates allocation failure.
mach dep: unified dependency-root resolution across sync/add/remove, proxy environment variables (ALL_PROXY,all_proxy,no_proxy) forwarded to git, idempotent lockfile writes, andmach initno longer pre-creates the dependency directory.- Release tags are verified against the manifest version before any artifact is built.
Patch release for project scaffolding correctness.
mach initnow scaffolds fully buildable projects with all required manifest entries andmach-stddependency wiring.
Tooling and cross-compilation release. Mach can now emit Windows executables, link dynamically, manage dependencies, and answer editor queries — while the Linux self-host continues to build to a byte-identical fixpoint.
- Windows cross-compilation for
x86_64-windows: the Microsoft x64 calling convention (win64 ABI), a COFF/PE object and executable writer, and kernel32 import linking. Mach builds runnable Windows.exes. (Running the compiler itself natively on Windows is in progress for a later release.) - ELF dynamic linking: link against shared libraries via
-l/-Land[targets.*].libs, with a real PLT/GOT andDT_NEEDED/PT_INTERP. - External linking and static archives: link prebuilt
.o/.ainputs; a Unixararchive reader. mach dep: git-based dependency management (add/remove/sync/vendor) with amach.lock.mach check: single-file diagnostics with no project or link step.- Per-target optimization levels via the manifest, overridable on the CLI.
- Editor query surface (
mach.lang.editor): single-file/unsaved-buffer open, parse, resolve, and diagnostics for tooling and language servers.
fwdre-exports now resolve against the dependency set correctly.- x86-64
imulby a constant outside signed-imm32 range no longer truncates to the low 32 bits (silent miscompile of large-constant multiplies). - A global
valinitialized from a constant cast no longer silently lowers to zero; a non-foldable global initializer is now a hard error. - Several win64 codegen fixes (shadow space, variadic definitions, callee-saved register preservation) and a COFF weak-symbol round-trip.
First stable release of Mach: a self-hosting, dependency-free native compiler for the Mach systems programming language. The compiler builds its own source to a byte-identical fixpoint and emits statically-linked x86-64 ELF directly, with no external backend, assembler, or linker.
- Self-hosting compiler that builds its own source to a byte-identical fixpoint.
- Direct x86-64 (Linux, SysV ABI) native code generation: lexer, parser, resolver, semantic analysis, an SSA mid-end, instruction selection, linear-scan register allocation, and ELF object/executable emission — with no LLVM and no external assembler or linker.
- Optimization pipeline:
mem2reg(stack-to-SSA promotion), constant folding, dead-code elimination, function inlining, algebraic simplification, and local common-subexpression elimination.-O0runs the always-on subset (mem2reg/ constant folding / DCE);-O1and-O2run the full pipeline.
- Records (
rec) and overlapping-layout unions (uni). - Generics with bracket syntax (
T[U]) and monomorphization. - Compile-time evaluation:
$if/$orbranch selection,$mach.*target parameters, comptime value-parameter monomorphization, and value/layout intrinsics ($size_of,$align_of,$offset_of, …). - Two cast operators:
::(value conversion) and:~(same-size bit reinterpret). - Pointers (including
nil), slices, and fixed-size arrays. - Error handling with
ResultandOption. - Modules with
use, module aliases, andpubvisibility. - Inline assembly (
asm) and variadic functions.
mach-std: runtime, allocators, strings, collections, I/O, filesystem, formatting, OS/syscall bindings, and the coreOption/Resulttypes.
machCLI:build,test, andinit.- Differential test harness (optimization-level and cross-compiler miscompile detection), a crash fuzzer, and a compiler compile-time benchmark harness.