Getting started
Build the mk compiler and run Makoto code, in the state the project is in today.
This is the practical guide to building the mk compiler and running Makoto code in the state the project is in today. It is honest about what works, what is rough, and what is design-only. For a tour of the language itself, read MAKOTO-A-GUIDED-TOUR.md; for the full design, docs/english/language-design.md.
Where the project is right now
Section titled “Where the project is right now”Makoto has a native compiler (mk) that lowers Makoto to LLVM IR text and hands it to an embedded clang. The core language compiles and runs: a program using structs, enums, match, loops, Result/errors, processes and channels, @mm, generics, comptime, and arbitrary-precision numbers builds to a native binary today.
Two honesty notes up front:
- This is pre-1.0, closed-beta work. The design docs describe the target; the implementation is behind them in places. The verification layer (
@must_consume,@where, session types,@proof) from the tour is designed, not implemented yet. - There is a known-bug ledger (
BUG-HUNT-INDEX-BY-VEIN.md, ~324 catalogued issues) from an ongoing bug hunt. Many advanced idioms have open bugs. Expect sharp edges off the main path.
Prerequisites
Section titled “Prerequisites”To build the compiler:
- Go 1.26.x (the module declares
go 1.26.3). That is the only hard dependency. - Linux x86_64. The embedded clang is a static x86_64/musl build, so a build host is x86_64 Linux.
To build and run native x86_64 Makoto programs: nothing else. The compiler vendors its own C toolchain (see “Dependencies” below), so no system LLVM, clang, or libc install is required.
Build mk
Section titled “Build mk”go build -o mk ./cmd/mkThat produces the mk binary. The compiler embeds ~100 MB of toolchain blobs (clang, musl, wasi, binaryen, wasmtime) via go:embed, so the resulting binary is large and the first build pulls those in. On first use mk extracts the embedded clang into a content-addressed cache once.
Put mk on your PATH, or call it by path.
Your first program
Section titled “Your first program”use io
fn main() { io.println("hello from makoto")}Save it as hi.mko and run it:
mk run hi.mkomk run compiles and runs in one step (default -O0). To produce a shippable binary instead:
mk build hi.mko # writes ./hi (default -O2 for build)./hiA single .mko file builds that module in isolation. A project is a directory with an mk.project manifest and a main.mko (or main.mkoui) entry:
package demomakoto 0.1.0Then mk build . (or mk run .) builds the enclosing project from its main entry.
The mk CLI
Section titled “The mk CLI”Commands:
| Command | What it does |
|---|---|
mk run [file|.|dir] |
compile and run (default -O0) |
mk build [file|.|dir] |
compile to a native binary (default -O2) |
mk check [file|.|dir] |
typecheck only, no binary |
mk test |
compile and run the project’s @test functions, each in its own process |
mk analyze |
run the memory analyzer and report (no binary) |
mk cc [args...] |
drop-in C compiler using the embedded hermetic toolchain (static musl) |
mk c++ [args...] |
the C++ driver (links the embedded libc++) |
mk get <path>[@version] |
fetch and pin an external dependency via the vendored Git client |
mk export <cmake|ninja> |
emit build-system glue so a C/C++ project can build and link a .mko library |
mk <task> |
run a task declared in mk.project |
mk (no args) |
start the REPL |
Note: bare mk --help currently drops into the REPL; get flag help from mk build --help or mk run --help.
Useful build flags (from mk build --help):
| Flag | Meaning |
|---|---|
-o <path> |
output path for the binary |
-O0 / -O2 |
optimization level (run defaults to -O0, build to -O2) |
--release |
optimized build (-O2), the artifact you ship |
--emit-llvm |
keep the generated .ll next to the binary |
--emit=lib |
build a C-callable static library plus a generated C header |
--header=<path> |
where to write that header (with --emit=lib) |
--scheduler=throughput|deterministic |
work-stealing (default) or BEAM-style rigid time-slicing |
--libc=musl|system |
hermetic static musl (default) or dynamic host libc |
--target=x86_64|aarch64|riscv64|wasm32 |
cross-compile target (default x86_64) |
--gc=linear|wasmgc |
GC backing (wasmgc is --target=wasm32 only) |
--gc-roots=shadow|stackmaps |
root-finding: portable shadow stack (default) or precise stackmaps |
--gc-collector=marksweep|moving |
mark-sweep (default) or compacting collector |
--autofree=default|conservative|optimized / --no-autofree |
free-at-last-use optimization (on by default) |
-Wmm / --strict-mm / -Whints |
engage the memory analyzer (advisory / hard errors / hint tier) |
--ext=<name> |
turn on a compiler extension, e.g. --ext=ui |
-- |
everything after is passed to the program (run only) |
Dependencies: what is embedded vs what the system provides
Section titled “Dependencies: what is embedded vs what the system provides”The core design decision: the compiler emits LLVM IR as text and shells out to tools, rather than binding the LLVM C API. Almost all of those tools are embedded, so the default native build is fully hermetic.
Embedded in mk (nothing needed from the system):
- clang + ld.lld — one static, musl-linked LLVM multicall binary (
blob/llvm.gz, ~46 MB). This is the entire default native x86_64 pipeline:mkhands it the.ll, clang compiles and lld links. Verified working with no system LLVM present. - musl sysroot — x86_64 (default hermetic static link), plus cross sysroots for aarch64 and riscv64.
- wasi sysroot + compiler-rt builtins — for
--target=wasm32. - binaryen (
wasm-opt,wasm-merge,wasm-as) and wasmtime — for building and running wasm. - the C runtime (
internal/compiler/runtime/src/*.c/.h/.S) — go:embedded intomk(rebuildmkafter editing runtime source).
NOT embedded — best-effort PATH lookup, needed only for specific modes:
| Tool | When it’s needed | If missing |
|---|---|---|
opt (LLVM) |
only --gc-roots=stackmaps (precise GC roots), to run rewrite-statepoints-for-gc |
that mode fails loudly; the default --gc-roots=shadow never needs it |
llc (LLVM) |
only to validate emitted IR in the test suite | never blocks a build |
node |
only --target=js (the UI JS transpile), to run the output |
JS target only |
qemu-aarch64 / qemu-riscv64 |
only to cross-run aarch64/riscv64 binaries locally | cross-build still works; you just can’t run the result on an x86_64 host without it |
cmake / ninja |
only mk export cmake|ninja interop |
export only |
So opt and llc are the only LLVM tools the compiler expects from outside, and both are off the default path: opt only for the stackmaps GC mode, llc only for test-time IR checks. A normal mk build / mk run needs neither. (The hermetic clang can be rebuilt to ship opt beside it, which is the fully-hermetic route for the stackmaps mode; a system opt on PATH also satisfies it.)
Overrides: MAKOTO_CLANG=<abs-path> forces a specific clang; MAKOTO_CLANG=system forces the host clang on PATH (used by the differential tests, and as an escape hatch if the embedded blob is stripped).
What exists, and what is rough
Section titled “What exists, and what is rough”Solid enough to build real programs (the bug hunt saturated these veins):
- Values,
let/var/const,Optional(no null), the numeric tower up toBigInt/Decimal/BigFloat. - Functions,
match(all three forms), unifiedloop, ternary,defer. declstructs/enums/interfaces, pattern matching,@embeds.Result+error{...},catch/match |e|trailers.- Processes, typed channels, the subjectless
match(select),@supervisorand the restart strategies. - Memory: colorless GC default,
@mm(arena|none|c),@transfer/@promote, the memory analyzer (mk analyze). - Generics (
[T], value-params), comptime,reflect(T), auto-derivedserialize/hash. - C FFI (
@cimport), inline@asm, cross-compile targets, the wasm and (UI) JS backends.
Rough or open (see BUG-HUNT-INDEX-BY-VEIN.md for the catalogued list):
- Still actively yielding bugs:
@cimport/FFI-ABI edges, interfaces+generics dispatch corners,@repr/layout with non-scalar fields, defer+generator interactions, the REPL, some@asmandunsafe/unioncases, and a few compiler hangs on pathological input. - Collections construction has known parse bugs (e.g.
List[T].new()turbofish); prefer slice literals ([]T{...}) on the happy path. - Not implemented yet: the whole verification layer (
@must_consume,@consume_once,@where,@protocol/session types,@proof/dependent proofs,@specmodel checking). It is designed (docs/english/verification.md), not built. - The showcase file
cmd/mk/hello.mkocurrently trips a parse error with the checked-inmk; the minimal hello above is the reliable starting point.
Ground truth for behavior is the design docs plus hand-computed values and cross-target consistency, not the Phase-1 interpreter/oracle (which over-simulates and is no longer authoritative).
Where to go next
Section titled “Where to go next”MAKOTO-A-GUIDED-TOUR.md— the language, feature by feature, in one running example.docs/english/language-design.md— the normative design (the north star).docs/english/verification.md— the verification spectrum (designed, not yet built).BUG-HUNT-INDEX-BY-VEIN.md— the current known-issue map, grouped by subsystem.mk test— run the@testsuite to see the compiler exercised on a large corpus.