Skip to content

Commit

Permalink
feat: impl immediate values support for field comparison instructions (
Browse files Browse the repository at this point in the history
  • Loading branch information
Fumuran authored Jun 7, 2024
1 parent 02d153f commit 1a65242
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [BREAKING] Changed fields type of the `StackOutputs` struct from `Vec<u64>` to `Vec<Felt>` (#1268).
- [BREAKING] Migrated to `miden-crypto` v0.9.0 (#1287).
- Added error codes support for the `mtree_verify` instruction (#1328).
- Added support for immediate values for `lt`, `lte`, `gt`, `gte` comparison instructions (#1346).

## 0.8.0 (02-26-2024)

Expand Down
42 changes: 42 additions & 0 deletions assembly/src/assembler/instruction/field_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,16 @@ pub fn lt(span_builder: &mut SpanBuilder) {
set_result(span_builder);
}

/// Appends a sequence of operations to pop the top element off the stack and do a "less than"
/// comparison with a provided immediate value. The stack is expected to be arranged as [a, ...]
/// (from the top). A value of 1 is pushed onto the stack if a < imm. Otherwise, 0 is pushed.
///
/// This operation takes 15 VM cycles.
pub fn lt_imm(span_builder: &mut SpanBuilder, imm: Felt) {
span_builder.push_op(Push(imm));
lt(span_builder);
}

/// Appends a sequence of operations to pop the top 2 elements off the stack and do a "less
/// than or equal" comparison. The stack is expected to be arranged as [b, a, ...] (from the top).
/// A value of 1 is pushed onto the stack if a <= b. Otherwise, 0 is pushed.
Expand All @@ -377,6 +387,17 @@ pub fn lte(span_builder: &mut SpanBuilder) {
set_result(span_builder);
}

/// Appends a sequence of operations to pop the top element off the stack and do a "less than or
/// equal" comparison with a provided immediate value. The stack is expected to be arranged as
/// [a, ...] (from the top). A value of 1 is pushed onto the stack if a <= imm. Otherwise, 0 is
/// pushed.
///
/// This operation takes 16 VM cycles.
pub fn lte_imm(span_builder: &mut SpanBuilder, imm: Felt) {
span_builder.push_op(Push(imm));
lte(span_builder);
}

/// Appends a sequence of operations to pop the top 2 elements off the stack and do a "greater
/// than" comparison. The stack is expected to be arranged as [b, a, ...] (from the top). A value
/// of 1 is pushed onto the stack if a > b. Otherwise, 0 is pushed.
Expand All @@ -401,6 +422,16 @@ pub fn gt(span_builder: &mut SpanBuilder) {
set_result(span_builder);
}

/// Appends a sequence of operations to pop the top element off the stack and do a "greater than"
/// comparison with a provided immediate value. The stack is expected to be arranged as [a, ...]
/// (from the top). A value of 1 is pushed onto the stack if a > imm. Otherwise, 0 is pushed.
///
/// This operation takes 16 VM cycles.
pub fn gt_imm(span_builder: &mut SpanBuilder, imm: Felt) {
span_builder.push_op(Push(imm));
gt(span_builder);
}

/// Appends a sequence of operations to pop the top 2 elements off the stack and do a "greater
/// than or equal" comparison. The stack is expected to be arranged as [b, a, ...] (from the top).
/// A value of 1 is pushed onto the stack if a >= b. Otherwise, 0 is pushed.
Expand All @@ -425,6 +456,17 @@ pub fn gte(span_builder: &mut SpanBuilder) {
set_result(span_builder);
}

/// Appends a sequence of operations to pop the top element off the stack and do a "greater than
/// or equal" comparison with a provided immediate value. The stack is expected to be arranged as
/// [a, ...] (from the top). A value of 1 is pushed onto the stack if a >= imm. Otherwise, 0 is
/// pushed.
///
/// This operation takes 17 VM cycles.
pub fn gte_imm(span_builder: &mut SpanBuilder, imm: Felt) {
span_builder.push_op(Push(imm));
gte(span_builder);
}

/// Checks if the top element in the stack is an odd number or not.
///
/// Vm cycles: 5
Expand Down
4 changes: 4 additions & 0 deletions assembly/src/assembler/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,13 @@ impl Assembler {
Instruction::Neq => span_builder.push_ops([Eq, Not]),
Instruction::NeqImm(imm) => field_ops::neq_imm(span_builder, imm.expect_value()),
Instruction::Lt => field_ops::lt(span_builder),
Instruction::LtImm(imm) => field_ops::lt_imm(span_builder, imm.expect_value()),
Instruction::Lte => field_ops::lte(span_builder),
Instruction::LteImm(imm) => field_ops::lte_imm(span_builder, imm.expect_value()),
Instruction::Gt => field_ops::gt(span_builder),
Instruction::GtImm(imm) => field_ops::gt_imm(span_builder, imm.expect_value()),
Instruction::Gte => field_ops::gte(span_builder),
Instruction::GteImm(imm) => field_ops::gte_imm(span_builder, imm.expect_value()),
Instruction::IsOdd => field_ops::is_odd(span_builder),

// ----- ext2 instructions ------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions assembly/src/ast/instruction/deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ impl Deserializable for Instruction {
OpCode::NeqImm => Ok(Self::NeqImm(Felt::read_from(source)?.into())),
OpCode::Eqw => Ok(Self::Eqw),
OpCode::Lt => Ok(Self::Lt),
OpCode::LtImm => Ok(Self::LtImm(Felt::read_from(source)?.into())),
OpCode::Lte => Ok(Self::Lte),
OpCode::LteImm => Ok(Self::LteImm(Felt::read_from(source)?.into())),
OpCode::Gt => Ok(Self::Gt),
OpCode::GtImm => Ok(Self::GtImm(Felt::read_from(source)?.into())),
OpCode::Gte => Ok(Self::Gte),
OpCode::GteImm => Ok(Self::GteImm(Felt::read_from(source)?.into())),
OpCode::IsOdd => Ok(Self::IsOdd),

// ----- ext2 operations --------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions assembly/src/ast/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ pub enum Instruction {
NeqImm(ImmFelt),
Eqw,
Lt,
LtImm(ImmFelt),
Lte,
LteImm(ImmFelt),
Gt,
GtImm(ImmFelt),
Gte,
GteImm(ImmFelt),
IsOdd,

// ----- ext2 operations ---------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions assembly/src/ast/instruction/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,13 @@ pub enum OpCode {
NeqImm,
Eqw,
Lt,
LtImm,
Lte,
LteImm,
Gt,
GtImm,
Gte,
GteImm,
IsOdd,

// ----- ext2 operations ---------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions assembly/src/ast/instruction/print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,13 @@ impl PrettyPrint for Instruction {
Self::NeqImm(value) => inst_with_felt_imm("neq", value),
Self::Eqw => const_text("eqw"),
Self::Lt => const_text("lt"),
Self::LtImm(value) => inst_with_felt_imm("lt", value),
Self::Lte => const_text("lte"),
Self::LteImm(value) => inst_with_felt_imm("lte", value),
Self::Gt => const_text("gt"),
Self::GtImm(value) => inst_with_felt_imm("gt", value),
Self::Gte => const_text("gte"),
Self::GteImm(value) => inst_with_felt_imm("gte", value),
Self::IsOdd => const_text("is_odd"),

// ----- ext2 operations --------------------------------------------------------------
Expand Down
16 changes: 16 additions & 0 deletions assembly/src/ast/instruction/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,25 @@ impl Serializable for Instruction {
}
Self::Eqw => OpCode::Eqw.write_into(target),
Self::Lt => OpCode::Lt.write_into(target),
Self::LtImm(v) => {
OpCode::LtImm.write_into(target);
v.expect_value().write_into(target);
}
Self::Lte => OpCode::Lte.write_into(target),
Self::LteImm(v) => {
OpCode::LteImm.write_into(target);
v.expect_value().write_into(target);
}
Self::Gt => OpCode::Gt.write_into(target),
Self::GtImm(v) => {
OpCode::GtImm.write_into(target);
v.expect_value().write_into(target);
}
Self::Gte => OpCode::Gte.write_into(target),
Self::GteImm(v) => {
OpCode::GteImm.write_into(target);
v.expect_value().write_into(target);
}
Self::IsOdd => OpCode::IsOdd.write_into(target),

// ----- ext2 operations --------------------------------------------------------------
Expand Down
6 changes: 4 additions & 2 deletions assembly/src/ast/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@ where
| U32AssertWWithError(ref code)
| MTreeVerifyWithError(ref code) => visitor.visit_immediate_error_code(code),
AddImm(ref imm) | SubImm(ref imm) | MulImm(ref imm) | DivImm(ref imm) | ExpImm(ref imm)
| EqImm(ref imm) | NeqImm(ref imm) | Push(ref imm) => visitor.visit_immediate_felt(imm),
| EqImm(ref imm) | NeqImm(ref imm) | LtImm(ref imm) | LteImm(ref imm) | GtImm(ref imm)
| GteImm(ref imm) | Push(ref imm) => visitor.visit_immediate_felt(imm),
U32WrappingAddImm(ref imm)
| U32OverflowingAddImm(ref imm)
| U32WrappingSubImm(ref imm)
Expand Down Expand Up @@ -737,7 +738,8 @@ where
| U32AssertWWithError(ref mut code)
| MTreeVerifyWithError(ref mut code) => visitor.visit_mut_immediate_error_code(code),
AddImm(ref mut imm) | SubImm(ref mut imm) | MulImm(ref mut imm) | DivImm(ref mut imm)
| ExpImm(ref mut imm) | EqImm(ref mut imm) | NeqImm(ref mut imm) | Push(ref mut imm) => {
| ExpImm(ref mut imm) | EqImm(ref mut imm) | NeqImm(ref mut imm) | LtImm(ref mut imm)
| LteImm(ref mut imm) | GtImm(ref mut imm) | GteImm(ref mut imm) | Push(ref mut imm) => {
visitor.visit_mut_immediate_felt(imm)
}
U32WrappingAddImm(ref mut imm)
Expand Down
32 changes: 28 additions & 4 deletions assembly/src/parser/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -427,16 +427,12 @@ Inst: Instruction = {
"ext2neg" => Instruction::Ext2Neg,
"ext2sub" => Instruction::Ext2Sub,
"fri_ext2fold4" => Instruction::FriExt2Fold4,
"gt" => Instruction::Gt,
"gte" => Instruction::Gte,
"hash" => Instruction::Hash,
"hperm" => Instruction::HPerm,
"hmerge" => Instruction::HMerge,
"ilog2" => Instruction::ILog2,
"inv" => Instruction::Inv,
"is_odd" => Instruction::IsOdd,
"lt" => Instruction::Lt,
"lte" => Instruction::Lte,
"mem_stream" => Instruction::MemStream,
"mtree_get" => Instruction::MTreeGet,
"mtree_merge" => Instruction::MTreeMerge,
Expand Down Expand Up @@ -598,6 +594,34 @@ FoldableInstWithFeltImmediate: SmallOpsVec = {
None => smallvec![Op::Inst(Span::new(span, Instruction::Neq))],
}
},
<l:@L> "lt" <imm:MaybeImm<Felt>> <r:@R> => {
let span = span!(l, r);
match imm {
Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::LtImm(imm)))],
None => smallvec![Op::Inst(Span::new(span, Instruction::Lt))],
}
},
<l:@L> "lte" <imm:MaybeImm<Felt>> <r:@R> => {
let span = span!(l, r);
match imm {
Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::LteImm(imm)))],
None => smallvec![Op::Inst(Span::new(span, Instruction::Lte))],
}
},
<l:@L> "gt" <imm:MaybeImm<Felt>> <r:@R> => {
let span = span!(l, r);
match imm {
Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::GtImm(imm)))],
None => smallvec![Op::Inst(Span::new(span, Instruction::Gt))],
}
},
<l:@L> "gte" <imm:MaybeImm<Felt>> <r:@R> => {
let span = span!(l, r);
match imm {
Some(imm) => smallvec![Op::Inst(Span::new(span, Instruction::GteImm(imm)))],
None => smallvec![Op::Inst(Span::new(span, Instruction::Gte))],
}
},
<l:@L> "add" <imm:MaybeImm<Felt>> <r:@R> => {
let span = span!(l, r);
match imm {
Expand Down
12 changes: 6 additions & 6 deletions docs/src/user_docs/assembly/field_operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ The arithmetic operations below are performed in a 64-bit [prime filed](https://

| Instruction | Stack_input | Stack_output | Notes |
| ---------------------------------------------------------- | ----------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| eq <br> - *(1 cycle)* <br> eq.*b* <br> - *(1-2 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a=b \\ 0, & \text{otherwise}\ \end{cases}$ |
| neq <br> - *(2 cycle)* <br> neq.*b* <br> - *(2-3 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a \ne b \\ 0, & \text{otherwise}\ \end{cases}$ |
| lt <br> - *(14 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a < b \\ 0, & \text{otherwise}\ \end{cases}$ |
| lte <br> - *(15 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a \le b \\ 0, & \text{otherwise}\ \end{cases}$ |
| gt <br> - *(15 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a > b \\ 0, & \text{otherwise}\ \end{cases}$ |
| gte <br> - *(16 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a \ge b \\ 0, & \text{otherwise}\ \end{cases}$ |
| eq <br> - *(1 cycle)* <br> eq.*b* <br> - *(1-2 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a=b \\ 0, & \text{otherwise}\ \end{cases}$ |
| neq <br> - *(2 cycle)* <br> neq.*b* <br> - *(2-3 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a \ne b \\ 0, & \text{otherwise}\ \end{cases}$ |
| lt <br> - *(14 cycles)* <br> lt.*b* <br> - *(15 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a < b \\ 0, & \text{otherwise}\ \end{cases}$ |
| lte <br> - *(15 cycles)* <br> lte.*b* <br> - *(16 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a \le b \\ 0, & \text{otherwise}\ \end{cases}$ |
| gt <br> - *(15 cycles)* <br> gt.*b* <br> - *(16 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a > b \\ 0, & \text{otherwise}\ \end{cases}$ |
| gte <br> - *(16 cycles)* <br> gte.*b* <br> - *(17 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a \ge b \\ 0, & \text{otherwise}\ \end{cases}$ |
| is_odd <br> - *(5 cycles)* | [a, ...] | [b, ...] | $b \leftarrow \begin{cases} 1, & \text{if}\ a \text{ is odd} \\ 0, & \text{otherwise}\ \end{cases}$ |
| eqw <br> - *(15 cycles)* | [A, B, ...] | [c, A, B, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a_i = b_i \; \forall i \in \{0, 1, 2, 3\} \\ 0, & \text{otherwise}\ \end{cases}$ |

Expand Down
37 changes: 37 additions & 0 deletions miden/tests/integration/operations/field_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,19 @@ fn eq() {
test.expect_stack(&[0]);
}

#[test]
fn eq_b() {
let build_asm_op = |param: u64| format!("eq.{param}");

// --- test when two elements are equal ------------------------------------------------------
let test = build_op_test!(build_asm_op(100), &[100]);
test.expect_stack(&[1]);

// --- test when two elements are unequal ----------------------------------------------------
let test = build_op_test!(build_asm_op(25), &[100]);
test.expect_stack(&[0]);
}

#[test]
fn eqw() {
let asm_op = "eqw";
Expand Down Expand Up @@ -848,6 +861,9 @@ proptest! {
/// both the high and low 32 bits, the upper 32 bits must not be all 1s. Therefore, for testing
/// it's sufficient to use elements with one high bit and one low bit set.
fn test_felt_comparison_op(asm_op: &str, expect_if_lt: u64, expect_if_eq: u64, expect_if_gt: u64) {
// create an operation with an immediate value
let build_asm_op = |param: u64| format!("{asm_op}.{param}");

// create vars with a variety of high and low bit relationships for testing
let low_bit = 1;
let high_bit = 1 << 48;
Expand All @@ -865,30 +881,51 @@ fn test_felt_comparison_op(asm_op: &str, expect_if_lt: u64, expect_if_eq: u64, e
// a is smaller in the low bits (equal in high bits)
let test = build_op_test!(asm_op, &[smaller, hi_eq_lo_gt]);
test.expect_stack(&[expect_if_lt]);
// run the same test using instruction with an immediate value
let test = build_op_test!(build_asm_op(hi_eq_lo_gt), &[smaller]);
test.expect_stack(&[expect_if_lt]);

// a is smaller in the high bits and equal in the low bits
let test = build_op_test!(asm_op, &[smaller, hi_gt_lo_eq]);
test.expect_stack(&[expect_if_lt]);
// run the same test using instruction with an immediate value
let test = build_op_test!(build_asm_op(hi_gt_lo_eq), &[smaller]);
test.expect_stack(&[expect_if_lt]);

// a is smaller in the high bits but bigger in the low bits
let test = build_op_test!(asm_op, &[smaller, hi_gt_lo_lt]);
test.expect_stack(&[expect_if_lt]);
// run the same test using instruction with an immediate value
let test = build_op_test!(build_asm_op(hi_gt_lo_lt), &[smaller]);
test.expect_stack(&[expect_if_lt]);

// --- a = b ----------------------------------------------------------------------------------
// high and low bits are both set
let test = build_op_test!(asm_op, &[hi_gt_lo_eq, hi_gt_lo_eq]);
test.expect_stack(&[expect_if_eq]);
// run the same test using instruction with an immediate value
let test = build_op_test!(build_asm_op(hi_gt_lo_eq), &[hi_gt_lo_eq]);
test.expect_stack(&[expect_if_eq]);

// --- a > b ----------------------------------------------------------------------------------
// a is bigger in the low bits (equal in high bits)
let test = build_op_test!(asm_op, &[hi_eq_lo_gt, smaller]);
test.expect_stack(&[expect_if_gt]);
// run the same test using instruction with an immediate value
let test = build_op_test!(build_asm_op(smaller), &[hi_eq_lo_gt]);
test.expect_stack(&[expect_if_gt]);

// a is bigger in the high bits and equal in the low bits
let test = build_op_test!(asm_op, &[hi_gt_lo_eq, smaller]);
test.expect_stack(&[expect_if_gt]);
// run the same test using instruction with an immediate value
let test = build_op_test!(build_asm_op(smaller), &[hi_gt_lo_eq]);
test.expect_stack(&[expect_if_gt]);

// a is bigger in the high bits but smaller in the low bits
let test = build_op_test!(asm_op, &[hi_gt_lo_lt, smaller]);
test.expect_stack(&[expect_if_gt]);
// run the same test using instruction with an immediate value
let test = build_op_test!(build_asm_op(smaller), &[hi_gt_lo_lt]);
test.expect_stack(&[expect_if_gt]);
}

0 comments on commit 1a65242

Please sign in to comment.