Skip to content

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!