Known limitations¶
Tip
See the issue tracker for information on known bugs. This document focuses on missing/incomplete features, rather than bugs.
The compiler is still in its early stages of development, so there are various features that are unimplemented, or only partially implemented, and the test suite is still limited in scope, so we are still finding bugs on a regular basis. We are rapidly improving this situation, but it is important to be aware of this when using the compiler.
The features discussed below are broken up into sections, to make them easier to navigate and reference.
Rust language support¶
Floating point types¶
- Status: Unsupported
- Tracking Issue: N/A
- Release Milestone: N/A
In order to represent Felt
“natively” in Rust, we were forced to piggy-back on the f32
type,
which is propagated through to WebAssembly, and allows us to handle those values specially.
As a result, floating-point types in Rust are not supported at all. Any attempt to use them will result in a compilation error. We considered this a fair design tradeoff, as floating point math is unused/rare in the context in which Miden is used, in comparison to fixed-point or field arithmetic. In addition, implementing floating-point operations in software on the Miden VM would be extraordinarily expensive, which generally works against the purpose for using floats in the first place.
At this point in time, we have no plans to support floats, but this may change if we are able to
find a better/more natural representation for Felt
in WebAssembly.
Function call indirection¶
This feature corresponds to call_indirect
in WebAssembly, and is associated with Rust features
such as trait objects (which use indirection to call trait methods), and closures. Note that the
Rust compiler is able to erase the indirection associated with certain abstractions statically
in some cases, shown below. If Rust is unable to statically resolve all call targets, then midenc
will raise an error when it encounters any use of call_indirect
.
Warning
The following examples rely on rustc
/LLVM inlining enough code to be able to convert indirect
calls to direct calls. This may require you to enable link-time optimization with lto = "fat"
and compile all of the code in the crate together with codegen-units = 1
, in order to maximize
the amount of inlining that can occur. Even then, it may not be possible to remove some forms of
indirection, in which case you will need to find another workaround.
Iterator lowered to loop¶
pub fn is_zeroed(bytes: &[u8; 32]) -> bool {
// Rust is able to convert this to a loop, erasing the closure completely
bytes.iter().copied().all(|b| b == 0)
}
Monomorphization + inlining¶
pub fn call<F, T>(fun: F) -> T
where
F: Fn() -> T,
{
fun()
}
#[inline(never)]
pub fn foo() -> bool { true }
fn main() {
// Rust is able to inline the body of `call` after monomorphization, which results in
// the call to `foo` being resolved statically.
call(foo)
}
Inlined trait impl¶
pub trait Foo {
fn is_foo(&self) -> bool;
}
impl Foo for u32 {
#[inline(never)]
fn is_foo(&self) -> bool { true }
}
fn has_foo(items: &[dyn Foo]) -> bool {
items.iter().any(|item| item.is_foo())
}
fn main() -> u32 {
// Rust inlines `has_foo`, converts the iterator chain to a loop, and is able to realize
// that the `dyn Foo` items are actually `u32`, and resolves the call to `is_foo` to
// `<u32 as Foo>::is_foo`.
let foo: &dyn Foo = &u32::MAX as &dyn Foo;
has_foo(&[foo]) as u32
}
Miden SDK¶
The Miden SDK for Rust, is a Rust crate that provides the implementation of native Miden types, as well as bindings to the Miden standard library and transaction kernel APIs.
Currently, only a very limited subset of the API surface has had bindings implemented. This means that there is a fair amount of native Miden functionality that is not yet available from Rust. We will be expanding the SDK rapidly over the next few weeks and months, but for the time being, if you encounter a missing API that you need, let us know, so we can ensure it is prioritized above APIs which are lesser used.
Rust/Miden FFI (foreign function interface) and interop¶
- Status: Internal Use Only
- Tracking Issue: #304
- Release Milestone: TBD
While the compiler has functionality to link against native Miden Assembly libraries, binding against procedures exported from those libraries from Rust can require glue code to be emitted by the compiler in some cases, and the set of procedures for which this is done is currently restricted to a hardcoded whitelist of known Miden procedures.
This affects any procedure which returns a type larger than u32
(excluding Felt
, which for
this purpose has the same size). For example, returing a Miden Word
from a procedure, a common
return type, is not compatible with Rust’s ABI - it will attempt to generate code which allocates
stack space in the caller, which it expects the callee to write to, inserting a new parameter at
the start of the parameter list, and expecting nothing to be returned by value. The compiler handles
situations like these using a set of ABI “transformation strategies”, which lift/lower differences
between the Rust and Miden ABIs at call boundaries.
To expose the FFI machinery for use with any Miden procedure, we need type signatures for those procedures at a minimum, and in some cases we may require details of the calling convention/ABI. This metadata does not currently exist, but is on the roadmap for inclusion into Miden Assembly and Miden packaging. Once present, we can open up the FFI for general use.
Core Miden functionality¶
Dynamic procedure invocation¶
This is a dependency of Function Call Indirection described above,
and is the mechanism by which we can perform indirect calls in Miden. In order to implement support
for indirect calls in the Wasm frontend, we need underlying support for dynexec
, which is not yet
implemented.
This feature adds support for lowering indirect calls to dynexec
or dyncall
instructions,
depending on the ABI of the callee. dyncall
has an additional dependency on support for
Cross-Context Procedure Invocation.
A known issue with this feature is that dyn(exec|call)
consumes a word on the operand stack
for the hash of the callee being invoked, but this word remains on the stack when entering the
callee, which has the effect of requiring procedures to have a different ABI depending on whether
they expect to be dynamically-invoked or not.
Our solution to that issue is to generate stubs which are used as the target of dyn(exec|call)
,
the body of which drop the callee hash, fix up the operand stack as necessary, and then uses a
simple exec
or call
to invoke the “real” callee. We will emit a single stub for every function
which has its “address” taken, and use the hash of the stub in place of the actual callee hash.
Cross-context procedure invocation¶
This is required in order to support representing Miden accounts and note scripts in Rust, and compilation to Miden Assembly.
Currently, you can write code in Rust that is very close to how accounts and note scripts will
look like in the language, but it is not possible to actually implement either of those in Rust
today. The reasons for this are covered in depth in the tracking issue linked above, but to
briefly summarize, the primary issue has to do with the fact that Rust programs are compiled
for a “shared-everything” environment, i.e. you can pass references to memory from caller to
callee, write to caller memory from the callee, etc. In Miden however, contexts are “shared-nothing”
units of isolation, and thus cross-context operations, such as performing a call
from a note script
to a method on an account, are not compatible with the usual calling conventions used by Rust and
LLVM.
The solution to this relies on compiling the Rust code for the wasm32-wasip2
target, which emits
a new kind of WebAssembly module, known as a component. These components adhere to the rules of
the WebAssembly Component Model. Of primary
interest to us, is the fact that components in this model are “shared-nothing”, and the ABI used to
communicate across component boundaries, is specially designed to enforce shared-nothing semantics
on caller and callee. In addition to compiling for a specific Wasm target, we also rely on some
additional tooling for describing component interfaces, types, and to generate Rust bindings for
those descriptions, to ensure that calls across the boundary remain opaque, even to the linker,
which ensures that the assumptions of the caller and callee with regard to what address space they
operate in are preserved (i.e. a callee can never be inlined into the caller, and thus end up
executing in the caller’s context rather than the expected callee context).
This is one of our top priorities, as it is critical to being able to use Rust to compile code for the Miden rollup, but it is also the most complex feature on our roadmap, hence why it is scheduled for our Beta 2 milestone, rather than Beta 1 (the next release), as it depends on multiple other subfeatures being implemented first.
Packaging¶
Package format¶
This feature represents the ability to compile and distribute a single artifact that contains the compiled MAST, and all required and optional metadata to make linking against, and executing packages as convenient as a dynamic library or executable.
The compiler currently produces, by default, an experimental implementation of a package format that meets the minimum requirements to support libraries and programs compiled from Rust:
- Name and semantic version information
- Content digest
- The compiled MAST and metadata about the procedures exported from it
- Read-only data segments and their hashes (if needed by the program, used to load data into the advice provider when a program is loaded, and to write those segments into linear memory when the program starts)
- Dependency information (optional, specifies what libraries were linked against during compilation)
- Debug information (optional)
However, this package format is not yet understood by the Miden VM itself. This means you cannot,
currently, compile a package and then run it using miden run
directly. Instead, you can use
midenc run
to load and run code from a package, as the compiler ships with the VM embedded for
use with the interactive debugger, and provides native support for packaging on top of it. You can
also use midenc debug
to execute your program interactively in the debugger, depending on your
needs. See Debugging Programs for more information on how to use the
debugger, and midenc help run
for more information on executing programs with the midenc run
command.
While it is possible to emit raw MAST from midenc
, rather than the experimental package format,
the resulting artifact cannot be run without some fragile and error-prone manual setup, in order
to ensure that the advice provider is correctly initialized with any read-only data segments. For
now, it is recommended that you use the midenc
tooling for testing programs, until the format
is stabilized.