Your first compilealpha
How do I turn a .mo file into an installable wasm and see what happened?
A moxzi build is one command that reads a .mo entry file and writes two artifacts next to it: a linked, deployable .wasm and the .did candid interface that describes it. The constraint to know before you start is that an actor must say persistent; a bare actor { … } is rejected, on purpose, because that is how a program declares its state survives an upgrade.
Compile it#
cat > hello.mo <<'EOF'
persistent actor {
var count : Nat = 0;
public func inc() : async Nat { count += 1; count };
public query func greet(name : Text) : async Text { "Hello, " # name # "!" };
};
EOF
moxzi build hello.mo -o hello.wasm
hello.mo -> hello.wasm + hello.did (223251 bytes, 1 file(s), 58 steps, 0.67s)
compiled by /…/compiler.wasm (sha256 7beb08e90ecf6ff21b7a0c92206c01edcc623a7566142b94cad96e31cdceca62), moxzi 0.1.0-alpha.1
Reading the two lines#
| Field | Meaning |
|---|---|
hello.wasm + hello.did | both artifacts were written; --no-idl suppresses the .did |
223251 bytes | size of the linked module — the deployable one |
1 file(s) | how many source files were in the upload closure (see moxzi deps) |
58 steps | driver messages the compiler ran; on-chain this is roughly the round count |
0.67s | wall clock, including loading the compiler from the AOT cache |
compiled by … (sha256 …) | provenance: which compiler wasm produced these bytes |
moxzi 0.1.0-alpha.1 | the host CLI version |
The provenance line is printed, never embedded in the artifact. Embedding it would have to be byte-identical between a local build and an on-chain one for the byte-parity claim to survive, so it stays outside the wasm. When two builds disagree, compare those hashes first: the same compiler hash and the same sources produce the same bytes.
The .did is the candid interface the runtime and any agent needs:
service : {
greet: (name: text) -> (text) query;
inc: () -> (nat);
}
Seeing the phases#
--timings splits the wall clock and adds the compiler's own memory numbers:
moxzi build hello.mo -o hello.wasm --timings
rts: allocated 0.07 GB, reclaimed 0.06 GB, peak live 5 MB, heap 13 MB, committed 201 MB
on-chain estimate: ~79 rounds (~0.02 h at 1s/round, ~0.01 h at 0.4s) | dirty-budget floor 1 rounds
timings: module 0.033s (cached), sources 0.001s, instantiate 0.001s, compile 0.208s, link 0.358s (66825 -> 223251 bytes)
Two things worth noticing. module 0.033s (cached) is the ahead-of-time cache doing its job — the first run on a new machine says (JIT) and costs about a second, because the compiler is a 7.8 MB memory64 module that wasmtime has to compile before it can run your code. And 66825 -> 223251 bytes is the link step: the compiler emits a small unlinked module, and linking adds the runtime system that makes it a canister.
The on-chain estimate is a projection from local step count, not a measurement of your program on the IC. Treat it as an order of magnitude.
Seeing what was compiled#
moxzi deps prints the upload closure — every file a build touches, and the virtual path it is compiled under:
moxzi deps hello.mo
root : /…/demo
pkgs : none (no mops.toml)
entry : /src/hello.mo
files : 1 (180 bytes)
180 /src/hello.mo
<- /…/demo/hello.mo
Those /src/… names are not cosmetic. The compiler records source paths in what it emits, so the same file compiled as /src/hello.mo and as /ext/hello.mo produces different bytes. Machine-independent VFS naming is what lets your build and someone else's agree.
Deploying what you built#
hello.wasm is a real canister module. It installs on the Internet Computer with the IC SDK (dfx canister install <canister> --wasm hello.wasm), runs under moxzid, and runs in a browser tab under moxzi-web — the same bytes in all three places.
When it fails#
Errors are source-located and carry moc's codes, so editor tooling and habits carry over:
Error: compilation failed:
/src/hello.mo:1.0-1.5: type error [M0220], this actor or actor class should be declared `persistent`
Two failure modes surprise people the first time:
| Symptom | What it means |
|---|---|
[M0220] … should be declared 'persistent' | you wrote a bare actor; add persistent, or pass --default-persistent-actors to have it rewritten |
| a successful build printing no warnings at all | expected — moxzi build surfaces diagnostics only when the compile fails; it does not report warnings on success |
Things you cannot do#
- You cannot deploy a
--no-linkartifact. It is unlinked by design: no runtime system, link-only exports still present. Validation failing on it is correct behaviour, not a bug. Runmoxzi linkon it first. - You cannot make the build quieter than it is. There is no
--quiet; the provenance line is the point of the tool. - You cannot get moc's warning controls.
-A,-W,-E,-Werrorand--hide-warningshave no moxzi equivalent, because warnings are not printed.
Next#
- Your first actor — run this wasm, call it, upgrade it, and watch the counter survive.
- Compiling and linking — what build, link and validate actually do, and when you need each.
- Reading diagnostics — what the M-codes mean and what belongs in a bug report.