Skip to content

Commit

Permalink
Merge pull request #151 from 0xPolygonMiden/greenhat/i144-native-felt
Browse files Browse the repository at this point in the history
[2/x] Native Miden VM felt type support as `Felt(f64)`
  • Loading branch information
greenhat authored Apr 16, 2024
2 parents e9a1bec + dc43184 commit c1c7961
Show file tree
Hide file tree
Showing 32 changed files with 1,094 additions and 298 deletions.
4 changes: 3 additions & 1 deletion docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

- [Rust To WebAssembly](guides/rust_to_wasm.md)
- [WebAssembly To Miden Assembly](guides/wasm_to_masm.md)

- [Developing Miden Programs In Rust](guides/develop_miden_in_rust.md)
- [Developing Miden Rollup Accounts And Note Scripts In Rust](guides/develop_miden_rollup_accounts_and_note_scripts_in_rust.md)

# Usage

- [midenc]()
Expand Down
33 changes: 33 additions & 0 deletions docs/src/guides/develop_miden_in_rust.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Developing Miden Programs In Rust

This chapter will walk through how to develop Miden programs in Rust using the prelude and standard library provided by the `miden-prelude` crate (see the [README](https://github.com/0xPolygonMiden/compiler/sdk/prelude/README.md)).

## Getting Started

Import the prelude and standard library from the `miden-prelude` crate:

```rust
use miden_prelude::*;
```

## Using `Felt` (field element) type

The `Felt` type is a field element type that is used to represent the field element values of the Miden VM.

To initialize a `Felt` value from an integer constant checking the range at compile time, use the `felt!` macro:

```rust
let a = felt!(42);
```

Otherwise, use the `Felt::new` constructor:

```rust
let a = Felt::new(some_integer_var).unwrap();
```

The constructor returns an error if the value is not a valid field element, e.g. if it is not in the range `0..=M` where `M` is the modulus of the field (2^64 - 2^32 + 1).

The `Felt` type implements the standard arithmetic operations, e.g. addition, subtraction, multiplication, division, etc. which are accessible through the standard Rust operators `+`, `-`, `*`, `/`, etc. All arithmetic operations are wrapping, i.e. performed modulo `M`.

TODO: Add examples of using operations on `Felt` type and available functions (`assert*`, etc.).
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Developing Miden Rollup Accounts And Note Scripts In Rust

This chapter will walk through how to develop Miden rollup accounts and note scripts in Rust using the Miden SDK crate.
23 changes: 18 additions & 5 deletions frontend-wasm/src/code_translator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use wasmparser::{MemArg, Operator};

use crate::{
error::{WasmError, WasmResult},
intrinsics::{convert_intrinsics_call, is_miden_intrinsics_module},
miden_abi::{is_miden_sdk_module, transform::transform_miden_abi_call},
module::{
func_translation_state::{ControlStackFrame, ElseData, FuncTranslationState},
Expand Down Expand Up @@ -102,6 +103,7 @@ pub fn translate_operator(
let (arg1, arg2, cond) = state.pop3();
// if cond is not 0, return arg1, else return arg2
// https://www.w3.org/TR/wasm-core-1/#-hrefsyntax-instr-parametricmathsfselect%E2%91%A0
// cond is expected to be an i32
let cond_i1 = builder.ins().neq_imm(cond, Immediate::I32(0), span);
state.push1(builder.ins().select(cond_i1, arg1, arg2, span));
}
Expand All @@ -119,7 +121,7 @@ pub fn translate_operator(

/**************************** Branch instructions *********************************/
Operator::Br { relative_depth } => translate_br(state, relative_depth, builder, span),
Operator::BrIf { relative_depth } => translate_br_if(*relative_depth, builder, state, span),
Operator::BrIf { relative_depth } => translate_br_if(*relative_depth, builder, state, span)?,
Operator::BrTable { targets } => translate_br_table(targets, state, builder, span)?,
Operator::Return => translate_return(state, builder, diagnostics, span)?,
/************************************ Calls ****************************************/
Expand Down Expand Up @@ -192,9 +194,11 @@ pub fn translate_operator(
}
Operator::I32Load { memarg } => translate_load(I32, memarg, state, builder, span),
Operator::I64Load { memarg } => translate_load(I64, memarg, state, builder, span),
Operator::F64Load { memarg } => translate_load(Felt, memarg, state, builder, span),
/****************************** Store instructions ***********************************/
Operator::I32Store { memarg } => translate_store(I32, memarg, state, builder, span),
Operator::I64Store { memarg } => translate_store(I64, memarg, state, builder, span),
Operator::F64Store { memarg } => translate_store(Felt, memarg, state, builder, span),
Operator::I32Store8 { memarg } | Operator::I64Store8 { memarg } => {
translate_store(U8, memarg, state, builder, span);
}
Expand Down Expand Up @@ -650,14 +654,19 @@ fn translate_call(
let wasm_sig = module_state.signature(function_index);
let num_wasm_args = wasm_sig.params().len();
let args = func_state.peekn(num_wasm_args);
if is_miden_sdk_module(func_id.module.as_symbol()) {
if is_miden_intrinsics_module(func_id.module.as_symbol()) {
let results = convert_intrinsics_call(func_id, args, builder, span);
func_state.popn(num_wasm_args);
func_state.pushn(&results);
} else if is_miden_sdk_module(func_id.module.as_symbol()) {
// Miden SDK function call, transform the call to the Miden ABI if needed
let results = transform_miden_abi_call(func_id, args, builder, span, diagnostics);
assert_eq!(
wasm_sig.results().len(),
results.len(),
"Adapted function call results quantity are not the same as the original Wasm \
function results quantity"
function results quantity for function {}",
func_id
);
assert_eq!(
wasm_sig.results().iter().map(|p| &p.ty).collect::<Vec<&Type>>(),
Expand All @@ -666,7 +675,8 @@ fn translate_call(
.map(|r| builder.data_flow_graph().value_type(*r))
.collect::<Vec<&Type>>(),
"Adapted function call result types are not the same as the original Wasm function \
result types"
result types for function {}",
func_id
);
func_state.popn(num_wasm_args);
func_state.pushn(&results);
Expand Down Expand Up @@ -735,18 +745,20 @@ fn translate_br_if(
builder: &mut FunctionBuilderExt,
state: &mut FuncTranslationState,
span: SourceSpan,
) {
) -> WasmResult<()> {
let cond = state.pop1();
let (br_destination, inputs) = translate_br_if_args(relative_depth, state);
let next_block = builder.create_block();
let then_dest = br_destination;
let then_args = inputs;
let else_dest = next_block;
let else_args = &[];
// cond is expected to be a i32 value
let cond_i1 = builder.ins().neq_imm(cond, Immediate::I32(0), span);
builder.ins().cond_br(cond_i1, then_dest, then_args, else_dest, else_args, span);
builder.seal_block(next_block); // The only predecessor is the current block.
builder.switch_to_block(next_block);
Ok(())
}

fn translate_br_if_args(
Expand Down Expand Up @@ -897,6 +909,7 @@ fn translate_if(
) -> WasmResult<()> {
let blockty = BlockType::from_wasm(blockty, mod_types)?;
let cond = state.pop1();
// cond is expected to be a i32 value
let cond_i1 = builder.ins().neq_imm(cond, Immediate::I32(0), span);
let next_block = builder.create_block();
let (destination, else_data) = if blockty.params.eq(&blockty.results) {
Expand Down
4 changes: 2 additions & 2 deletions frontend-wasm/src/code_translator/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1490,14 +1490,14 @@ fn select_i32() {
r#"
i64.const 3
i64.const 7
i32.const 42
i32.const 1
select
drop
"#,
expect![[r#"
(let (v0 i64) (const.i64 3))
(let (v1 i64) (const.i64 7))
(let (v2 i32) (const.i32 42))
(let (v2 i32) (const.i32 1))
(let (v3 i1) (neq v2 0))
(let (v4 i64) (select v3 v0 v1))
"#]],
Expand Down
16 changes: 0 additions & 16 deletions frontend-wasm/src/code_translator/tests_unsupported.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,6 @@ const UNSUPPORTED_WASM_V1_OPS: &[Operator] = &[
memory: 0,
},
},
F64Load {
memarg: MemArg {
align: 0,
max_align: 0,
offset: 0,
memory: 0,
},
},
F32Store {
memarg: MemArg {
align: 0,
Expand All @@ -75,14 +67,6 @@ const UNSUPPORTED_WASM_V1_OPS: &[Operator] = &[
memory: 0,
},
},
F64Store {
memarg: MemArg {
align: 0,
max_align: 0,
offset: 0,
memory: 0,
},
},
/****************************** Nullary Operators ************************************/

// Cannot construct since Ieee32 fields are private
Expand Down
126 changes: 126 additions & 0 deletions frontend-wasm/src/intrinsics/felt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use std::vec;

use miden_hir::{Felt, FunctionIdent, Immediate, InstBuilder, SourceSpan, Type::*, Value};

use crate::module::function_builder_ext::FunctionBuilderExt;

pub(crate) const PRELUDE_INTRINSICS_FELT_MODULE_NAME: &str = "miden:prelude/intrinsics_felt";

/// Convert a call to a felt op intrinsic function into instruction(s)
pub(crate) fn convert_felt_intrinsics(
func_id: FunctionIdent,
args: &[Value],
builder: &mut FunctionBuilderExt<'_, '_, '_>,
span: SourceSpan,
) -> Vec<Value> {
match func_id.function.as_symbol().as_str() {
// Conversion operations
"from_u64_unchecked" => {
assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id);
let inst = builder.ins().cast(args[0], Felt, span);
vec![inst]
}
"as_u64" => {
assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id);
// we're casting to i64 instead of u64 because Wasm doesn't have u64
// and this value will be used in Wasm ops or local vars that expect i64
let inst = builder.ins().cast(args[0], I64, span);
vec![inst]
}
// Arithmetic operations
"add" => {
assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id);
let inst = builder.ins().add_unchecked(args[0], args[1], span);
vec![inst]
}
"sub" => {
assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id);
let inst = builder.ins().sub_unchecked(args[0], args[1], span);
vec![inst]
}
"mul" => {
assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id);
let inst = builder.ins().mul_unchecked(args[0], args[1], span);
vec![inst]
}
"div" => {
assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id);
let inst = builder.ins().div_unchecked(args[0], args[1], span);
vec![inst]
}
"neg" => {
assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id);
let inst = builder.ins().neg(args[0], span);
vec![inst]
}
"inv" => {
assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id);
let inst = builder.ins().inv(args[0], span);
vec![inst]
}
"pow2" => {
assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id);
let inst = builder.ins().pow2(args[0], span);
vec![inst]
}
"exp" => {
assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id);
let inst = builder.ins().exp(args[0], args[1], span);
vec![inst]
}
// Comparison operations
"eq" => {
assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id);
let inst = builder.ins().eq(args[0], args[1], span);
let cast = builder.ins().cast(inst, I32, span);
vec![cast]
}
"gt" => {
assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id);
let inst = builder.ins().gt(args[0], args[1], span);
let cast = builder.ins().cast(inst, I32, span);
vec![cast]
}
"ge" => {
assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id);
let inst = builder.ins().gte(args[0], args[1], span);
let cast = builder.ins().cast(inst, I32, span);
vec![cast]
}
"lt" => {
assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id);
let inst = builder.ins().lt(args[0], args[1], span);
let cast = builder.ins().cast(inst, I32, span);
vec![cast]
}
"le" => {
assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id);
let inst = builder.ins().lte(args[0], args[1], span);
let cast = builder.ins().cast(inst, I32, span);
vec![cast]
}
"is_odd" => {
assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id);
let inst = builder.ins().is_odd(args[0], span);
let cast = builder.ins().cast(inst, I32, span);
vec![cast]
}
// Assert operations
"assert" => {
assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id);
builder.ins().assert_eq_imm(Immediate::Felt(Felt::new(1)), args[0], span);
vec![]
}
"assertz" => {
assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id);
builder.ins().assert_eq_imm(Immediate::Felt(Felt::new(0)), args[0], span);
vec![]
}
"assert_eq" => {
assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id);
builder.ins().assert_eq(args[0], args[1], span);
vec![]
}
_ => panic!("No felt op intrinsics found for {}", func_id),
}
}
36 changes: 36 additions & 0 deletions frontend-wasm/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
mod felt;

use std::{collections::HashSet, sync::OnceLock};

use miden_hir::{FunctionIdent, SourceSpan, Symbol, Value};

use crate::module::function_builder_ext::FunctionBuilderExt;

/// Check if the given module is a Miden module that contains intrinsics
pub fn is_miden_intrinsics_module(module_id: Symbol) -> bool {
modules().contains(module_id.as_str())
}

fn modules() -> &'static HashSet<&'static str> {
static MODULES: OnceLock<HashSet<&'static str>> = OnceLock::new();
MODULES.get_or_init(|| {
let mut s = HashSet::default();
s.insert(felt::PRELUDE_INTRINSICS_FELT_MODULE_NAME);
s
})
}

/// Convert a call to a Miden intrinsic function into instruction(s)
pub fn convert_intrinsics_call(
func_id: FunctionIdent,
args: &[Value],
builder: &mut FunctionBuilderExt,
span: SourceSpan,
) -> Vec<Value> {
match func_id.module.as_symbol().as_str() {
felt::PRELUDE_INTRINSICS_FELT_MODULE_NAME => {
felt::convert_felt_intrinsics(func_id, args, builder, span)
}
_ => panic!("No intrinsics found for {}", func_id),
}
}
1 change: 1 addition & 0 deletions frontend-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod code_translator;
mod component;
mod config;
mod error;
mod intrinsics;
mod miden_abi;
mod module;
mod ssa;
Expand Down
Loading

0 comments on commit c1c7961

Please sign in to comment.