From 1a65242185fdf2cda0ca639cc1abb5a4b5586e71 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Fri, 7 Jun 2024 15:12:56 +0300 Subject: [PATCH] feat: impl immediate values support for field comparison instructions (#1348) --- CHANGELOG.md | 1 + .../src/assembler/instruction/field_ops.rs | 42 +++++++++++++++++++ assembly/src/assembler/instruction/mod.rs | 4 ++ assembly/src/ast/instruction/deserialize.rs | 4 ++ assembly/src/ast/instruction/mod.rs | 4 ++ assembly/src/ast/instruction/opcode.rs | 4 ++ assembly/src/ast/instruction/print.rs | 4 ++ assembly/src/ast/instruction/serialize.rs | 16 +++++++ assembly/src/ast/visit.rs | 6 ++- assembly/src/parser/grammar.lalrpop | 32 ++++++++++++-- .../user_docs/assembly/field_operations.md | 12 +++--- .../tests/integration/operations/field_ops.rs | 37 ++++++++++++++++ 12 files changed, 154 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 607d74d322..86b42610ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - [BREAKING] Changed fields type of the `StackOutputs` struct from `Vec` to `Vec` (#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) diff --git a/assembly/src/assembler/instruction/field_ops.rs b/assembly/src/assembler/instruction/field_ops.rs index 40c34d3bed..38db35980e 100644 --- a/assembly/src/assembler/instruction/field_ops.rs +++ b/assembly/src/assembler/instruction/field_ops.rs @@ -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. @@ -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. @@ -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. @@ -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 diff --git a/assembly/src/assembler/instruction/mod.rs b/assembly/src/assembler/instruction/mod.rs index 286173b8b8..0a7a69ef2d 100644 --- a/assembly/src/assembler/instruction/mod.rs +++ b/assembly/src/assembler/instruction/mod.rs @@ -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 ------------------------------------------------------------ diff --git a/assembly/src/ast/instruction/deserialize.rs b/assembly/src/ast/instruction/deserialize.rs index 88189e6911..805f710cbe 100644 --- a/assembly/src/ast/instruction/deserialize.rs +++ b/assembly/src/ast/instruction/deserialize.rs @@ -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 -------------------------------------------------------------- diff --git a/assembly/src/ast/instruction/mod.rs b/assembly/src/ast/instruction/mod.rs index 0aa83c2088..57bb52084c 100644 --- a/assembly/src/ast/instruction/mod.rs +++ b/assembly/src/ast/instruction/mod.rs @@ -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 --------------------------------------------------------------------- diff --git a/assembly/src/ast/instruction/opcode.rs b/assembly/src/ast/instruction/opcode.rs index 4620d5e69f..456da0742b 100644 --- a/assembly/src/ast/instruction/opcode.rs +++ b/assembly/src/ast/instruction/opcode.rs @@ -46,9 +46,13 @@ pub enum OpCode { NeqImm, Eqw, Lt, + LtImm, Lte, + LteImm, Gt, + GtImm, Gte, + GteImm, IsOdd, // ----- ext2 operations --------------------------------------------------------------------- diff --git a/assembly/src/ast/instruction/print.rs b/assembly/src/ast/instruction/print.rs index 5364121f03..a178119807 100644 --- a/assembly/src/ast/instruction/print.rs +++ b/assembly/src/ast/instruction/print.rs @@ -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 -------------------------------------------------------------- diff --git a/assembly/src/ast/instruction/serialize.rs b/assembly/src/ast/instruction/serialize.rs index 6f851d931e..a8cb98922a 100644 --- a/assembly/src/ast/instruction/serialize.rs +++ b/assembly/src/ast/instruction/serialize.rs @@ -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 -------------------------------------------------------------- diff --git a/assembly/src/ast/visit.rs b/assembly/src/ast/visit.rs index a8025d59c6..b0853e8199 100644 --- a/assembly/src/ast/visit.rs +++ b/assembly/src/ast/visit.rs @@ -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) @@ -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) diff --git a/assembly/src/parser/grammar.lalrpop b/assembly/src/parser/grammar.lalrpop index 5843474fd2..1b69842dc4 100644 --- a/assembly/src/parser/grammar.lalrpop +++ b/assembly/src/parser/grammar.lalrpop @@ -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, @@ -598,6 +594,34 @@ FoldableInstWithFeltImmediate: SmallOpsVec = { None => smallvec![Op::Inst(Span::new(span, Instruction::Neq))], } }, + "lt" > => { + 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))], + } + }, + "lte" > => { + 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))], + } + }, + "gt" > => { + 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))], + } + }, + "gte" > => { + 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))], + } + }, "add" > => { let span = span!(l, r); match imm { diff --git a/docs/src/user_docs/assembly/field_operations.md b/docs/src/user_docs/assembly/field_operations.md index d3425c17a3..11e69da086 100644 --- a/docs/src/user_docs/assembly/field_operations.md +++ b/docs/src/user_docs/assembly/field_operations.md @@ -45,12 +45,12 @@ The arithmetic operations below are performed in a 64-bit [prime filed](https:// | Instruction | Stack_input | Stack_output | Notes | | ---------------------------------------------------------- | ----------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| eq
- *(1 cycle)*
eq.*b*
- *(1-2 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a=b \\ 0, & \text{otherwise}\ \end{cases}$ | -| neq
- *(2 cycle)*
neq.*b*
- *(2-3 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a \ne b \\ 0, & \text{otherwise}\ \end{cases}$ | -| lt
- *(14 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a < b \\ 0, & \text{otherwise}\ \end{cases}$ | -| lte
- *(15 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a \le b \\ 0, & \text{otherwise}\ \end{cases}$ | -| gt
- *(15 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a > b \\ 0, & \text{otherwise}\ \end{cases}$ | -| gte
- *(16 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a \ge b \\ 0, & \text{otherwise}\ \end{cases}$ | +| eq
- *(1 cycle)*
eq.*b*
- *(1-2 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a=b \\ 0, & \text{otherwise}\ \end{cases}$ | +| neq
- *(2 cycle)*
neq.*b*
- *(2-3 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a \ne b \\ 0, & \text{otherwise}\ \end{cases}$ | +| lt
- *(14 cycles)*
lt.*b*
- *(15 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a < b \\ 0, & \text{otherwise}\ \end{cases}$ | +| lte
- *(15 cycles)*
lte.*b*
- *(16 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a \le b \\ 0, & \text{otherwise}\ \end{cases}$ | +| gt
- *(15 cycles)*
gt.*b*
- *(16 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a > b \\ 0, & \text{otherwise}\ \end{cases}$ | +| gte
- *(16 cycles)*
gte.*b*
- *(17 cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow \begin{cases} 1, & \text{if}\ a \ge b \\ 0, & \text{otherwise}\ \end{cases}$ | | is_odd
- *(5 cycles)* | [a, ...] | [b, ...] | $b \leftarrow \begin{cases} 1, & \text{if}\ a \text{ is odd} \\ 0, & \text{otherwise}\ \end{cases}$ | | eqw
- *(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}$ | diff --git a/miden/tests/integration/operations/field_ops.rs b/miden/tests/integration/operations/field_ops.rs index 4d7aee242b..13d3c54998 100644 --- a/miden/tests/integration/operations/field_ops.rs +++ b/miden/tests/integration/operations/field_ops.rs @@ -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"; @@ -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; @@ -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]); }