Pular para o conteúdo

Start here

Getting started

Build the mk compiler and run Makoto code, in the state the project is in today.

GETTING-STARTED.md · 162 lines · 6 min read

Este conteúdo não está disponível em sua língua ainda.

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.

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.

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.

Terminal window
go build -o mk ./cmd/mk

That 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.

use io
fn main() {
io.println("hello from makoto")
}

Save it as hi.mko and run it:

Terminal window
mk run hi.mko

mk run compiles and runs in one step (default -O0). To produce a shippable binary instead:

Terminal window
mk build hi.mko # writes ./hi (default -O2 for build)
./hi

A 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:

mk.project
package demo
makoto 0.1.0

Then mk build . (or mk run .) builds the enclosing project from its main entry.

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: mk hands 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 into mk (rebuild mk after 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).

Solid enough to build real programs (the bug hunt saturated these veins):

  • Values, let/var/const, Optional (no null), the numeric tower up to BigInt/Decimal/BigFloat.
  • Functions, match (all three forms), unified loop, ternary, defer.
  • decl structs/enums/interfaces, pattern matching, @embeds.
  • Result + error{...}, catch / match |e| trailers.
  • Processes, typed channels, the subjectless match (select), @supervisor and 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-derived serialize/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 @asm and unsafe/union cases, 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, @spec model checking). It is designed (docs/english/verification.md), not built.
  • The showcase file cmd/mk/hello.mko currently trips a parse error with the checked-in mk; 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).

  • 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 @test suite to see the compiler exercised on a large corpus.