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 ~200 MB of compressed toolchain blobs (LLVM, the musl and WASI sysroots, binaryen, wasmtime, qemu) via go:embed, so the resulting binary is large (~225 MB) and the first build pulls those in. On first use mk extracts each piece it needs into a content-addressed cache once (under ~/.cache/makoto/).
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|glibc|glibc.2.<minor>|system |
hermetic static musl (default); dynamic against the vendored glibc, runnable on any glibc at least the targeted release (the newest, or 2.17–2.44 on request; riscv64 from 2.27); or the host’s libc and headers |
--sanitize=address|thread|undefined |
build with sanitizers (a comma-separated list; address and thread exclude each other): the program’s functions, the runtime (which announces its process-stack switches) and the C it links are instrumented. Needs glibc, so without --libc the build links the vendored glibc; not for wasm/JS. address keeps locals on the process stack; thread follows at most 65,536 nested calls per process; undefined is fatal (exit 1) |
--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. All of those tools are embedded, so every build is hermetic.
Embedded in mk (nothing needed from the system) — all static x86_64/musl executables, built from source by internal/compiler/toolchain/rebuild/build-toolchain.sh (versions and licenses: internal/compiler/toolchain/PROVENANCE.txt):
- LLVM 23 — one multicall binary (clang, clang++, ld.lld, wasm-ld, llvm-ar, llvm-nm, …) plus
optandllc. This is the entire native pipeline:mkhands clang the.ll, clang compiles and lld links;optruns the--gc-roots=stackmapsstatepoint rewrite,llcvalidates IR in the test suite. - Linux sysroots for x86_64 (the default hermetic static link), aarch64 and riscv64 — each with musl 1.2.6, compiler-rt and libc++, so C and C++ build hermetically for every target.
- glibc for x86_64, aarch64 and riscv64 — glibc 2.44’s headers, start files and
libc_nonshared.a, the ABI lists of every release from 2.17 (riscv64: 2.27) to 2.44, libc++, and compiler-rt with the sanitizers — for--libc=glibc[.2.<minor>].mkwrites the link stubs of the targeted release from those lists; glibc itself never ships. - WASI sysroot (wasi-sdk 34: wasip1, wasip1-threads, wasip3 + cooperative threads, libc++ with and without exceptions) + wasm32 compiler-rt +
wasm-component-ld— for--target=wasm32. - binaryen (
wasm-opt,wasm-merge,wasm-as) and wasmtime (49, and 46 for wasi-threads) — for building and running wasm. - qemu-user (
qemu-aarch64,qemu-riscv64) —mk run --target=aarch64|riscv64runs the cross-compiled binary under it. - the C runtime (
internal/compiler/runtime/src/*.c/.h/.S) — go:embedded intomk(rebuildmkafter editing runtime source).
NOT embedded — needed only for specific modes:
| Tool | When it’s needed | If missing |
|---|---|---|
node |
only --target=js (the UI JS transpile), to run the output |
JS target only |
cmake / ninja |
only mk export cmake|ninja interop |
export only |
The ThreadSanitizer probes of the runtime (internal/compiler/runtime), like every other test, use only the embedded toolchain: they link against the vendored glibc (TSan supports only a dynamically linked glibc process) and run on the machine’s glibc, so they skip on a machine without one.
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.