Compiling WebAssembly to Miden Assembly¶
This guide will walk you through compiling a WebAssembly (Wasm) module, in binary form
(i.e. a .wasm
file), to Miden Assembly (Masm), both in its binary package form (a .masp
file),
and in textual Miden Assembly syntax form (i.e. a .masm
file).
Setup¶
We will be making use of the example crate we created in Compiling Rust to WebAssembly, which produces a small Wasm module that is easy to examine in Wasm text format, and demonstrates a good set of default choices for a project compiling to Miden Assembly from Rust.
In this chapter, we will be compiling Wasm to Masm using the midenc
executable, so ensure that
you have followed the instructions in the Getting Started with midenc
guide
and then return here.
Note
While we are using midenc
for this guide, the more common use case will be to use the
cargo-miden
Cargo extension to handle the gritty details of compiling from Rust to Wasm
for you. However, the purpose of this guide is to show you what cargo-miden
is handling
for you, and to give you a foundation for using midenc
yourself if needed.
Compiling to Miden Assembly¶
In the last chapter, we compiled a Rust crate to WebAssembly that contains an implementation
of the Fibonacci function called fib
, that was emitted to
target/wasm32-wasip1/release/wasm_fib.wasm
. All that remains is to tell midenc
to compile this
module to Miden Assembly.
Currently, by default, the compiler will emit an experimental package format that the Miden VM does
not yet support. We will instead use midenc run
to execute the package using the VM for us, but
once the package format is stabilized, this same approach will work with miden run
as well.
We also want to examine the Miden Assembly generated by the compiler, so we’re going to ask the compiler to emit both types of artifacts:
midenc compile --emit masm=wasm_fib.masm,masp target/wasm32-wasip1/release/wasm_fib.wasm
This will compile our Wasm module to a Miden package with the .masp
extension, and also emit the
textual Masm to wasm_fib.masm
so we can review it. The wasm_fib.masp
file will be emitted in the
default output directory, which is the current working directory by default.
If we dump the contents of wasm_fib.masm
, we’ll see the following generated code:
export.fib
push.0
push.1
movup.2
swap.1
dup.1
neq.0
push.1
while.true
if.true
push.4294967295
movup.2
swap.1
u32wrapping_add
dup.1
swap.1
swap.3
swap.1
u32wrapping_add
movup.2
swap.1
dup.1
neq.0
push.1
else
drop
drop
push.0
end
end
end
If you compare this to the WebAssembly text format, you can see that this is a fairly faithful translation, but there may be areas where we generate sub-optimal Miden Assembly.
Note
At the moment the compiler does only minimal optimization, late in the pipeline during codegen, and only in an effort to minimize operand stack management code. So if you see an instruction sequence you think is bad, bring it to our attention, and if it is something that we can solve as part of our overall optimization efforts, we will be sure to do so. There are limits to what we can generate compared to what one can write by hand, particularly because Rust’s memory model requires us to emulate byte-addressable memory on top of Miden’s word-addressable memory, however our goal is to keep this overhead within an acceptable bound in the general case, and easily-recognized patterns that can be simplified using peephole optimization are precisely the kind of thing we’d like to know about, as those kinds of optimizations are likely to produce the most significant wins.
Testing with the Miden VM¶
Note
Because the compiler ships with the VM embedded for midenc debug
, you can run your program
without having to install the VM separately, though you should do that as well, as midenc
only
exposes a limited set of commands for executing programs, intended for debugging.
We can test our compiled program like so:
$ midenc run --num-outputs 1 wasm_fib.masp -- 10
============================================================
Run program: wasm_fib.masp
============================================================
Executed program with hash 0xe5ba88695040ec2477821b26190e9addbb1c9571ae30c564f5bbfd6cabf6c535 in 19 milliseconds
Output: [55]
VM cycles: 295 extended to 512 steps (42% padding).
├── Stack rows: 295
├── Range checker rows: 67
└── Chiplets rows: 250
├── Hash chiplet rows: 248
├── Bitwise chiplet rows: 0
├── Memory chiplet rows: 1
└── Kernel ROM rows: 0
Success! We got the expected result of 55
.
Next steps¶
This guide is not comprehensive, as we have not yet examined in detail the differences between compiling libraries vs programs, linking together multiple libraries, packages, or discussed some of the more esoteric compiler options. We will be updating this documentation with those details and more in the coming weeks and months, so bear with us while we flesh out our guides!