diff --git a/CHANGELOG.md b/CHANGELOG.md index d5988dd69d..e0573ea661 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Introduced the `procref.` assembly instruction (#1113). - Added the ability to use constants as counters in `repeat` loops (#1124). - All `checked` versions of the u32 instructions were removed. All `unchecked` versions were renamed: this mode specification was removed from their titles (#1115). +- Introduced the `u32clz`, `u32ctz`, `u32clo`, `u32cto` and `ilog2` assembly instructions (#1176). - Added support for hexadecimal values in constants (#1199). - Added the `RCombBase` instruction (#1216). diff --git a/assembly/src/assembler/instruction/field_ops.rs b/assembly/src/assembler/instruction/field_ops.rs index ca038bce94..1f3be07ac2 100644 --- a/assembly/src/assembler/instruction/field_ops.rs +++ b/assembly/src/assembler/instruction/field_ops.rs @@ -3,6 +3,7 @@ use super::{ ZERO, }; use crate::MAX_EXP_BITS; +use vm_core::AdviceInjector::ILog2; /// Field element representing TWO in the base field of the VM. const TWO: Felt = Felt::new(2); @@ -235,6 +236,49 @@ fn perform_exp_for_small_power(span: &mut SpanBuilder, pow: u64) { } } +// LOGARITHMIC OPERATIONS +// ================================================================================================ + +/// Appends a sequence of operations to calculate the base 2 integer logarithm of the stack top +/// element, using non-deterministic technique (i.e. it takes help of advice provider). +/// +/// This operation takes 44 VM cycles. +/// +/// # Errors +/// Returns an error if the logarithm argument (top stack element) equals ZERO. +pub fn ilog2(span: &mut SpanBuilder) -> Result, AssemblyError> { + span.push_advice_injector(ILog2); + span.push_op(AdvPop); // [ilog2, n, ...] + + // compute the power-of-two for the value given in the advice tape (17 cycles) + span.push_op(Dup0); + append_pow2_op(span); + // => [pow2, ilog2, n, ...] + + #[rustfmt::skip] + let ops = [ + // split the words into u32 halves to use the bitwise operations (4 cycles) + MovUp2, U32split, MovUp2, U32split, + // => [pow2_high, pow2_low, n_high, n_low, ilog2, ...] + + // only one of the two halves in pow2 has a bit set, drop the other (9 cycles) + Dup1, Eqz, Dup0, MovDn3, + // => [drop_low, pow2_high, pow2_low, drop_low, n_high, n_low, ilog2, ...] + CSwap, Drop, MovDn3, CSwap, Drop, + // => [n_half, pow2_half, ilog2, ...] + + // set all bits to 1 lower than pow2_half (00010000 -> 00011111) + Swap, Pad, Incr, Incr, Mul, Pad, Incr, Neg, Add, + // => [pow2_half * 2 - 1, n_half, ilog2, ...] + Dup1, U32and, + // => [m, n_half, ilog2, ...] if ilog2 calculation was correct, m should be equal to n_half + Eq, Assert(0), + // => [ilog2, ...] + ]; + + span.add_ops(ops) +} + // COMPARISON OPERATIONS // ================================================================================================ diff --git a/assembly/src/assembler/instruction/mod.rs b/assembly/src/assembler/instruction/mod.rs index a0140c8e43..d2771b2e65 100644 --- a/assembly/src/assembler/instruction/mod.rs +++ b/assembly/src/assembler/instruction/mod.rs @@ -62,6 +62,7 @@ impl Assembler { Instruction::Exp => field_ops::exp(span, 64), Instruction::ExpImm(pow) => field_ops::exp_imm(span, *pow), Instruction::ExpBitLength(num_pow_bits) => field_ops::exp(span, *num_pow_bits), + Instruction::ILog2 => field_ops::ilog2(span), Instruction::Not => span.add_op(Not), Instruction::And => span.add_op(And), @@ -145,6 +146,10 @@ impl Assembler { Instruction::U32Rotr => u32_ops::u32rotr(span, None), Instruction::U32RotrImm(v) => u32_ops::u32rotr(span, Some(*v)), Instruction::U32Popcnt => u32_ops::u32popcnt(span), + Instruction::U32Clz => u32_ops::u32clz(span), + Instruction::U32Ctz => u32_ops::u32ctz(span), + Instruction::U32Clo => u32_ops::u32clo(span), + Instruction::U32Cto => u32_ops::u32cto(span), Instruction::U32Lt => u32_ops::u32lt(span), Instruction::U32Lte => u32_ops::u32lte(span), diff --git a/assembly/src/assembler/instruction/u32_ops.rs b/assembly/src/assembler/instruction/u32_ops.rs index c194c73c11..c322dcbe16 100644 --- a/assembly/src/assembler/instruction/u32_ops.rs +++ b/assembly/src/assembler/instruction/u32_ops.rs @@ -5,6 +5,7 @@ use super::{ SpanBuilder, ZERO, }; use crate::{MAX_U32_ROTATE_VALUE, MAX_U32_SHIFT_VALUE}; +use vm_core::AdviceInjector::{U32Clo, U32Clz, U32Cto, U32Ctz}; // ENUMS // ================================================================================================ @@ -167,6 +168,53 @@ pub fn u32divmod( handle_division(span, imm) } +// ARITHMETIC OPERATIONS - HELPERS +// ================================================================================================ + +/// Handles U32ADD, U32SUB, and U32MUL operations in wrapping, and overflowing modes, including +/// handling of immediate parameters. +/// +/// Specifically handles these specific inputs per the spec. +/// - Wrapping: does not check if the inputs are u32 values; overflow or underflow bits are +/// discarded. +/// - Overflowing: does not check if the inputs are u32 values; overflow or underflow bits are +/// pushed onto the stack. +fn handle_arithmetic_operation( + span: &mut SpanBuilder, + op: Operation, + op_mode: U32OpMode, + imm: Option, +) -> Result, AssemblyError> { + if let Some(imm) = imm { + push_u32_value(span, imm); + } + + span.push_op(op); + + // in the wrapping mode, drop high 32 bits + if matches!(op_mode, U32OpMode::Wrapping) { + span.add_op(Drop) + } else { + Ok(None) + } +} + +/// Handles common parts of u32div, u32mod, and u32divmod operations, including handling of +/// immediate parameters. +fn handle_division( + span: &mut SpanBuilder, + imm: Option, +) -> Result, AssemblyError> { + if let Some(imm) = imm { + if imm == 0 { + return Err(AssemblyError::division_by_zero()); + } + push_u32_value(span, imm); + } + + span.add_op(U32div) +} + // BITWISE OPERATIONS // ================================================================================================ @@ -310,48 +358,52 @@ pub fn u32popcnt(span: &mut SpanBuilder) -> Result, AssemblyEr span.add_ops(ops) } -/// Handles U32ADD, U32SUB, and U32MUL operations in wrapping, and overflowing modes, including -/// handling of immediate parameters. +/// Translates `u32clz` assembly instruction to VM operations. `u32clz` counts the number of +/// leading zeros of the value using non-deterministic technique (i.e. it takes help of advice +/// provider). /// -/// Specifically handles these specific inputs per the spec. -/// - Wrapping: does not check if the inputs are u32 values; overflow or underflow bits are -/// discarded. -/// - Overflowing: does not check if the inputs are u32 values; overflow or underflow bits are -/// pushed onto the stack. -fn handle_arithmetic_operation( - span: &mut SpanBuilder, - op: Operation, - op_mode: U32OpMode, - imm: Option, -) -> Result, AssemblyError> { - if let Some(imm) = imm { - push_u32_value(span, imm); - } +/// This operation takes 37 VM cycles. +pub fn u32clz(span: &mut SpanBuilder) -> Result, AssemblyError> { + span.push_advice_injector(U32Clz); + span.push_op(AdvPop); // [clz, n, ...] - span.push_op(op); + calculate_clz(span) +} - // in the wrapping mode, drop high 32 bits - if matches!(op_mode, U32OpMode::Wrapping) { - span.add_op(Drop) - } else { - Ok(None) - } +/// Translates `u32ctz` assembly instruction to VM operations. `u32ctz` counts the number of +/// trailing zeros of the value using non-deterministic technique (i.e. it takes help of advice +/// provider). +/// +/// This operation takes 34 VM cycles. +pub fn u32ctz(span: &mut SpanBuilder) -> Result, AssemblyError> { + span.push_advice_injector(U32Ctz); + span.push_op(AdvPop); // [ctz, n, ...] + + calculate_ctz(span) } -/// Handles common parts of u32div, u32mod, and u32divmod operations, including handling of -/// immediate parameters. -fn handle_division( - span: &mut SpanBuilder, - imm: Option, -) -> Result, AssemblyError> { - if let Some(imm) = imm { - if imm == 0 { - return Err(AssemblyError::division_by_zero()); - } - push_u32_value(span, imm); - } +/// Translates `u32clo` assembly instruction to VM operations. `u32clo` counts the number of +/// leading ones of the value using non-deterministic technique (i.e. it takes help of advice +/// provider). +/// +/// This operation takes 36 VM cycles. +pub fn u32clo(span: &mut SpanBuilder) -> Result, AssemblyError> { + span.push_advice_injector(U32Clo); + span.push_op(AdvPop); // [clo, n, ...] - span.add_op(U32div) + calculate_clo(span) +} + +/// Translates `u32cto` assembly instruction to VM operations. `u32cto` counts the number of +/// trailing ones of the value using non-deterministic technique (i.e. it takes help of advice +/// provider). +/// +/// This operation takes 33 VM cycles. +pub fn u32cto(span: &mut SpanBuilder) -> Result, AssemblyError> { + span.push_advice_injector(U32Cto); + span.push_op(AdvPop); // [cto, n, ...] + + calculate_cto(span) } // BITWISE OPERATIONS - HELPERS @@ -379,6 +431,304 @@ fn prepare_bitwise( Ok(()) } +/// Appends relevant operations to the span block for the correctness check of the `U32Clz` +/// injector. +/// The idea is to compare the actual value with a bitmask consisting of `clz` leading ones to +/// check that every bit in `clz` leading bits is zero and `1` additional one to check that +/// `clz + 1`'th leading bit is one: +/// ```text +/// 000000000...000100...10 <-- actual value +/// └─ clz zeros ─┘ +/// +/// 1111111111...11100...00 <-- bitmask +/// └─ clz ones ─┘│ +/// └─ additional one +/// ``` +/// After applying a `u32and` bit operation on this values the result's leading `clz` bits should +/// be zeros, otherwise there were some ones in initial value's `clz` leading bits, and therefore +/// `clz` value is incorrect. `clz + 1`'th leading bit of the result should be one, otherwise this +/// bit in the initial value wasn't one and `clz` value is incorrect: +/// ```text +/// 0000...00|1|10...10 +/// & +/// 1111...11|1|00...00 +/// ↓↓↓↓ ↓↓ ↓ +/// 0000...00|1|00...00 +/// ``` +/// +/// --- +/// The stack is expected to be arranged as follows (from the top): +/// - number of the leading zeros (`clz`), 1 element +/// - value for which we count the number of leading zeros (`n`), 1 element +/// +/// After the operations are executed, the stack will be arranged as follows: +/// - number of the leading zeros (`clz`), 1 element +/// +/// `[clz, n, ... ] -> [clz, ... ]` +/// +/// VM cycles: 36 +fn calculate_clz(span: &mut SpanBuilder) -> Result, AssemblyError> { + // [clz, n, ...] + #[rustfmt::skip] + let ops_group_1 = [ + Swap, Push(32u8.into()), Dup2, Neg, Add // [32 - clz, n, clz, ...] + ]; + span.push_ops(ops_group_1); + + append_pow2_op(span); // [pow2(32 - clz), n, clz, ...] + + #[rustfmt::skip] + let ops_group_2 = [ + Push(Felt::new(u32::MAX as u64 + 1)), // [2^32, pow2(32 - clz), n, clz, ...] + + Dup1, Neg, Add, // [2^32 - pow2(32 - clz), pow2(32 - clz), n, clz, ...] + // `2^32 - pow2(32 - clz)` is equal to `clz` leading ones and `32 - clz` + // zeros: + // 1111111111...1110000...0 + // └─ `clz` ones ─┘ + + Swap, Push(2u8.into()), U32div, Drop, // [pow2(32 - clz) / 2, 2^32 - pow2(32 - clz), n, clz, ...] + // pow2(32 - clz) / 2 is equal to `clz` leading + // zeros, `1` one and all other zeros. + + Swap, Dup1, Add, // [bit_mask, pow2(32 - clz) / 2, n, clz, ...] + // 1111111111...111000...0 <-- bitmask + // └─ clz ones ─┘│ + // └─ additional one + + MovUp2, U32and, // [m, pow2(32 - clz) / 2, clz] + // If calcualtion of `clz` is correct, m should be equal to + // pow2(32 - clz) / 2 + + Eq, Assert(0) // [clz, ...] + ]; + + span.add_ops(ops_group_2) +} + +/// Appends relevant operations to the span block for the correctness check of the `U32Clo` +/// injector. +/// The idea is to compare the actual value with a bitmask consisting of `clo` leading ones to +/// check that every bit in `clo` leading bits is one and `1` additional one to check that +/// `clo + 1`'th leading bit is zero: +/// ```text +/// 11111111...111010...10 <-- actual value +/// └─ clo ones ─┘ +/// +/// 111111111...11100...00 <-- bitmask +/// └─ clo ones ─┘│ +/// └─ additional one +/// ``` +/// After applying a `u32and` bit operation on this values the result's leading `clo` bits should +/// be ones, otherwise there were some zeros in initial value's `clo` leading bits, and therefore +/// `clo` value is incorrect. `clo + 1`'th leading bit of the result should be zero, otherwise this +/// bit in the initial value wasn't zero and `clo` value is incorrect: +/// ```text +/// 1111...11|0|10...10 +/// & +/// 1111...11|1|00...00 +/// ↓↓↓↓ ↓↓ ↓ +/// 1111...11|0|00...00 +/// ``` +/// +/// --- +/// The stack is expected to be arranged as follows (from the top): +/// - number of the leading ones (`clo`), 1 element +/// - value for which we count the number of leading ones (`n`), 1 element +/// +/// After the operations are executed, the stack will be arranged as follows: +/// - number of the leading ones (`clo`), 1 element +/// +/// `[clo, n, ... ] -> [clo, ... ]` +/// +/// VM cycles: 35 +fn calculate_clo(span: &mut SpanBuilder) -> Result, AssemblyError> { + // [clo, n, ...] + #[rustfmt::skip] + let ops_group_1 = [ + Swap, Push(32u8.into()), Dup2, Neg, Add // [32 - clo, n, clo, ...] + ]; + span.push_ops(ops_group_1); + + append_pow2_op(span); // [pow2(32 - clo), n, clo, ...] + + #[rustfmt::skip] + let ops_group_2 = [ + Push(Felt::new(u32::MAX as u64 + 1)), // [2^32, pow2(32 - clo), n, clo, ...] + + Dup1, Neg, Add, // [2^32 - pow2(32 - clo), pow2(32 - clo), n, clo, ...] + // `2^32 - pow2(32 - clo)` is equal to `clo` leading ones and `32 - clo` + // zeros: + // 11111111...1110000...0 + // └─ clo ones ─┘ + + Swap, Push(2u8.into()), U32div, Drop, // [pow2(32 - clo) / 2, 2^32 - pow2(32 - clo), n, clo, ...] + // pow2(32 - clo) / 2 is equal to `clo` leading + // zeros, `1` one and all other zeros. + + Dup1, Add, // [bit_mask, 2^32 - pow2(32 - clo), n, clo, ...] + // 111111111...111000...0 <-- bitmask + // └─ clo ones ─┘│ + // └─ additional one + + MovUp2, U32and, // [m, 2^32 - pow2(32 - clo), clo] + // If calcualtion of `clo` is correct, m should be equal to + // 2^32 - pow2(32 - clo) + + Eq, Assert(0) // [clo, ...] + ]; + + span.add_ops(ops_group_2) +} + +/// Appends relevant operations to the span block for the correctness check of the `U32Ctz` +/// injector. +/// The idea is to compare the actual value with a bitmask consisting of `ctz` trailing ones to +/// check that every bit in `ctz` trailing bits is zero and `1` additional one to check that +/// `ctz + 1`'th trailing bit is one: +/// ```text +/// 10..001000000000000000 <-- actual value +/// └─ ctz zeros ─┘ +/// +/// 00..0011111111111...11 <-- bitmask +/// │└─ ctz ones ─┘ +/// └─ additional one +/// ``` +/// After applying a `u32and` bit operation on this values the result's trailing `ctz` bits should +/// be zeros, otherwise there were some ones in initial value's `ctz` trailing bits, and therefore +/// `ctz` value is incorrect. `ctz + 1`'th trailing bit of the result should be one, otherwise this +/// bit in the initial value wasn't one and `ctz` value is incorrect: +/// ```text +/// 10...10|1|00...00 +/// & +/// 00...00|1|11...11 +/// = ↓ ↓↓ ↓↓ +/// 00...00|1|00...00 +/// ``` +/// +/// --- +/// The stack is expected to be arranged as follows (from the top): +/// - number of the trailing zeros (`ctz`), 1 element +/// - value for which we count the number of trailing zeros (`n`), 1 element +/// +/// After the operations are executed, the stack will be arranged as follows: +/// - number of the trailing zeros (`ctz`), 1 element +/// +/// `[ctz, n, ... ] -> [ctz, ... ]` +/// +/// VM cycles: 33 +fn calculate_ctz(span: &mut SpanBuilder) -> Result, AssemblyError> { + // [ctz, n, ...] + #[rustfmt::skip] + let ops_group_1 = [ + Swap, Dup1, // [ctz, n, ctz, ...] + ]; + span.push_ops(ops_group_1); + + append_pow2_op(span); // [pow2(ctz), n, ctz, ...] + + #[rustfmt::skip] + let ops_group_2 = [ + Dup0, // [pow2(ctz), pow2(ctz), n, ctz, ...] + // pow2(ctz) is equal to all zeros with only one on the `ctz`'th trailing position + + Pad, Incr, Neg, Add, // [pow2(ctz) - 1, pow2(ctz), n, ctz, ...] + + Swap, U32split, Drop, // [pow2(ctz), pow2(ctz) - 1, n, ctz, ...] + // We need to drop the high bits of `pow2(ctz)` because if `ctz` + // equals 32 `pow2(ctz)` will exceed the u32. Also in that case there + // is no need to check the dividing one, since it is absent (value is + // all 0's). + + Dup0, MovUp2, Add, // [bit_mask, pow2(ctz), n, ctz] + // 00..001111111111...11 <-- bitmask + // │└─ ctz ones ─┘ + // └─ additional one + + MovUp2, U32and, // [m, pow2(ctz), ctz] + // If calcualtion of `ctz` is correct, m should be equal to + // pow2(ctz) + + Eq, Assert(0), // [ctz, ...] + ]; + + span.add_ops(ops_group_2) +} + +/// Appends relevant operations to the span block for the correctness check of the `U32Cto` +/// injector. +/// The idea is to compare the actual value with a bitmask consisting of `cto` trailing ones to +/// check that every bit in `cto` trailing bits is one and `1` additional one to check that +/// `cto + 1`'th trailing bit is zero: +/// ```text +/// 10..01011111111111111 <-- actual value +/// └─ cto ones ─┘ +/// +/// 00..001111111111...11 <-- bitmask +/// │└─ cto ones ─┘ +/// └─ additional one +/// ``` +/// After applying a `u32and` bit operation on this values the result's trailing `cto` bits should +/// be ones, otherwise there were some zeros in initial value's `cto` trailing bits, and therefore +/// `cto` value is incorrect. `cto + 1`'th trailing bit of the result should be zero, otherwise +/// this bit in the initial value wasn't zero and `cto` value is incorrect: +/// ```text +/// 10...11|0|11...11 +/// & +/// 00...00|1|11...11 +/// = ↓ ↓↓ ↓↓ +/// 00...00|0|11...11 +/// ``` +/// +/// --- +/// The stack is expected to be arranged as follows (from the top): +/// - number of the trailing ones (`cto`), 1 element +/// - value for which we count the number of trailing zeros (`n`), 1 element +/// +/// After the operations are executed, the stack will be arranged as follows: +/// - number of the trailing zeros (`cto`), 1 element +/// +/// `[cto, n, ... ] -> [cto, ... ]` +/// +/// VM cycles: 32 +fn calculate_cto(span: &mut SpanBuilder) -> Result, AssemblyError> { + // [cto, n, ...] + #[rustfmt::skip] + let ops_group_1 = [ + Swap, Dup1, // [cto, n, cto, ...] + ]; + span.push_ops(ops_group_1); + + append_pow2_op(span); // [pow2(cto), n, cto, ...] + + #[rustfmt::skip] + let ops_group_2 = [ + Dup0, // [pow2(cto), pow2(cto), n, cto, ...] + // pow2(cto) is equal to all zeros with only one on the `cto`'th trailing position + + Pad, Incr, Neg, Add, // [pow2(cto) - 1, pow2(cto), n, cto, ...] + + Swap, U32split, Drop, // [pow2(cto), pow2(cto) - 1, n, cto, ...] + // We need to drop the high bits of `pow2(cto)` because if `cto` + // equals 32 `pow2(cto)` will exceed the u32. Also in that case there + // is no need to check the dividing zero, since it is absent (value + // is all 1's). + + Dup1, Add, // [bit_mask, pow2(cto) - 1, n, cto] + // 00..001111111111...11 <-- bitmask + // │└─ cto ones ─┘ + // └─ additional one + + MovUp2, U32and, // [m, pow2(cto) - 1, cto] + // If calcualtion of `cto` is correct, m should be equal to + // pow2(cto) - 1 + + Eq, Assert(0), // [cto, ...] + ]; + + span.add_ops(ops_group_2) +} + // COMPARISON OPERATIONS // ================================================================================================ diff --git a/assembly/src/ast/nodes/advice.rs b/assembly/src/ast/nodes/advice.rs index 5a1b4e58a3..2b0fdc8b86 100644 --- a/assembly/src/ast/nodes/advice.rs +++ b/assembly/src/ast/nodes/advice.rs @@ -19,7 +19,7 @@ use vm_core::{AdviceInjector, Felt, SignatureKind, ZERO}; /// - Insert new data into the advice map. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum AdviceInjectorNode { - PushU64div, + PushU64Div, PushExt2intt, PushSmtGet, PushSmtSet, @@ -40,7 +40,7 @@ impl From<&AdviceInjectorNode> for AdviceInjector { fn from(value: &AdviceInjectorNode) -> Self { use AdviceInjectorNode::*; match value { - PushU64div => Self::DivU64, + PushU64Div => Self::U64Div, PushExt2intt => Self::Ext2Intt, PushSmtGet => Self::SmtGet, PushSmtSet => Self::SmtSet, @@ -77,7 +77,7 @@ impl fmt::Display for AdviceInjectorNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use AdviceInjectorNode::*; match self { - PushU64div => write!(f, "push_u64div"), + PushU64Div => write!(f, "push_u64div"), PushExt2intt => write!(f, "push_ext2intt"), PushSmtGet => write!(f, "push_smtget"), PushSmtSet => write!(f, "push_smtset"), @@ -119,7 +119,7 @@ impl Serializable for AdviceInjectorNode { fn write_into(&self, target: &mut W) { use AdviceInjectorNode::*; match self { - PushU64div => target.write_u8(PUSH_U64DIV), + PushU64Div => target.write_u8(PUSH_U64DIV), PushExt2intt => target.write_u8(PUSH_EXT2INTT), PushSmtGet => target.write_u8(PUSH_SMTGET), PushSmtSet => target.write_u8(PUSH_SMTSET), @@ -153,7 +153,7 @@ impl Serializable for AdviceInjectorNode { impl Deserializable for AdviceInjectorNode { fn read_from(source: &mut R) -> Result { match source.read_u8()? { - PUSH_U64DIV => Ok(AdviceInjectorNode::PushU64div), + PUSH_U64DIV => Ok(AdviceInjectorNode::PushU64Div), PUSH_EXT2INTT => Ok(AdviceInjectorNode::PushExt2intt), PUSH_SMTGET => Ok(AdviceInjectorNode::PushSmtGet), PUSH_SMTSET => Ok(AdviceInjectorNode::PushSmtSet), diff --git a/assembly/src/ast/nodes/mod.rs b/assembly/src/ast/nodes/mod.rs index 1a9dd143bc..f10feb087c 100644 --- a/assembly/src/ast/nodes/mod.rs +++ b/assembly/src/ast/nodes/mod.rs @@ -63,6 +63,7 @@ pub enum Instruction { Exp, ExpImm(Felt), ExpBitLength(u8), + ILog2, Not, And, Or, @@ -132,6 +133,10 @@ pub enum Instruction { U32Rotl, U32RotlImm(u8), U32Popcnt, + U32Clz, + U32Ctz, + U32Clo, + U32Cto, U32Lt, U32Lte, U32Gt, @@ -323,6 +328,7 @@ impl fmt::Display for Instruction { Self::Exp => write!(f, "exp"), Self::ExpImm(value) => write!(f, "exp.{value}"), Self::ExpBitLength(value) => write!(f, "exp.u{value}"), + Self::ILog2 => write!(f, "ilog2"), Self::Not => write!(f, "not"), Self::And => write!(f, "and"), Self::Or => write!(f, "or"), @@ -392,6 +398,10 @@ impl fmt::Display for Instruction { Self::U32Rotl => write!(f, "u32rotl"), Self::U32RotlImm(value) => write!(f, "u32rotl.{value}"), Self::U32Popcnt => write!(f, "u32popcnt"), + Self::U32Clz => write!(f, "u32clz"), + Self::U32Ctz => write!(f, "u32ctz"), + Self::U32Clo => write!(f, "u32clo"), + Self::U32Cto => write!(f, "u32cto"), Self::U32Lt => write!(f, "u32lt"), Self::U32Lte => write!(f, "u32lte"), Self::U32Gt => write!(f, "u32gt"), diff --git a/assembly/src/ast/nodes/serde/deserialization.rs b/assembly/src/ast/nodes/serde/deserialization.rs index 52858ebee0..7a86a5caf6 100644 --- a/assembly/src/ast/nodes/serde/deserialization.rs +++ b/assembly/src/ast/nodes/serde/deserialization.rs @@ -82,6 +82,7 @@ impl Deserializable for Instruction { OpCode::Exp => Ok(Instruction::Exp), OpCode::ExpImm => Ok(Instruction::ExpImm(Felt::read_from(source)?)), OpCode::ExpBitLength => Ok(Instruction::ExpBitLength(source.read_u8()?)), + OpCode::ILog2 => Ok(Instruction::ILog2), OpCode::Not => Ok(Instruction::Not), OpCode::And => Ok(Instruction::And), OpCode::Or => Ok(Instruction::Or), @@ -157,6 +158,10 @@ impl Deserializable for Instruction { OpCode::U32Rotl => Ok(Instruction::U32Rotl), OpCode::U32RotlImm => Ok(Instruction::U32RotlImm(source.read_u8()?)), OpCode::U32Popcnt => Ok(Instruction::U32Popcnt), + OpCode::U32Clz => Ok(Instruction::U32Clz), + OpCode::U32Ctz => Ok(Instruction::U32Ctz), + OpCode::U32Clo => Ok(Instruction::U32Clo), + OpCode::U32Cto => Ok(Instruction::U32Cto), OpCode::U32Lt => Ok(Instruction::U32Lt), OpCode::U32Lte => Ok(Instruction::U32Lte), OpCode::U32Gt => Ok(Instruction::U32Gt), diff --git a/assembly/src/ast/nodes/serde/mod.rs b/assembly/src/ast/nodes/serde/mod.rs index dd35f2610e..a81ddaaa7a 100644 --- a/assembly/src/ast/nodes/serde/mod.rs +++ b/assembly/src/ast/nodes/serde/mod.rs @@ -38,230 +38,235 @@ pub enum OpCode { Exp = 20, ExpImm = 21, ExpBitLength = 22, - Not = 23, - And = 24, - Or = 25, - Xor = 26, - Eq = 27, - EqImm = 28, - Neq = 29, - NeqImm = 30, - Eqw = 31, - Lt = 32, - Lte = 33, - Gt = 34, - Gte = 35, - IsOdd = 36, + ILog2 = 23, + Not = 24, + And = 25, + Or = 26, + Xor = 27, + Eq = 28, + EqImm = 29, + Neq = 30, + NeqImm = 31, + Eqw = 32, + Lt = 33, + Lte = 34, + Gt = 35, + Gte = 36, + IsOdd = 37, // ----- ext2 operations ---------------------------------------------------------------------- - Ext2Add = 37, - Ext2Sub = 38, - Ext2Mul = 39, - Ext2Div = 40, - Ext2Neg = 41, - Ext2Inv = 42, + Ext2Add = 38, + Ext2Sub = 39, + Ext2Mul = 40, + Ext2Div = 41, + Ext2Neg = 42, + Ext2Inv = 43, // ----- u32 manipulation --------------------------------------------------------------------- - U32Test = 43, - U32TestW = 44, - U32Assert = 45, - U32AssertWithError = 46, - U32Assert2 = 47, - U32Assert2WithError = 48, - U32AssertW = 49, - U32AssertWWithError = 50, - U32Split = 51, - U32Cast = 52, - U32WrappingAdd = 53, - U32WrappingAddImm = 54, - U32OverflowingAdd = 55, - U32OverflowingAddImm = 56, - U32OverflowingAdd3 = 57, - U32WrappingAdd3 = 58, - U32WrappingSub = 59, - U32WrappingSubImm = 60, - U32OverflowingSub = 61, - U32OverflowingSubImm = 62, - U32WrappingMul = 63, - U32WrappingMulImm = 64, - U32OverflowingMul = 65, - U32OverflowingMulImm = 66, - U32OverflowingMadd = 67, - U32WrappingMadd = 68, - U32Div = 69, - U32DivImm = 70, - U32Mod = 71, - U32ModImm = 72, - U32DivMod = 73, - U32DivModImm = 74, - U32And = 75, - U32Or = 76, - U32Xor = 77, - U32Not = 78, - U32Shr = 79, - U32ShrImm = 80, - U32Shl = 81, - U32ShlImm = 82, - U32Rotr = 83, - U32RotrImm = 84, - U32Rotl = 85, - U32RotlImm = 86, - U32Popcnt = 87, - U32Lt = 88, - U32Lte = 89, - U32Gt = 90, - U32Gte = 91, - U32Min = 92, - U32Max = 93, + U32Test = 44, + U32TestW = 45, + U32Assert = 46, + U32AssertWithError = 47, + U32Assert2 = 48, + U32Assert2WithError = 49, + U32AssertW = 50, + U32AssertWWithError = 51, + U32Split = 52, + U32Cast = 53, + U32WrappingAdd = 54, + U32WrappingAddImm = 55, + U32OverflowingAdd = 56, + U32OverflowingAddImm = 57, + U32OverflowingAdd3 = 58, + U32WrappingAdd3 = 59, + U32WrappingSub = 60, + U32WrappingSubImm = 61, + U32OverflowingSub = 62, + U32OverflowingSubImm = 63, + U32WrappingMul = 64, + U32WrappingMulImm = 65, + U32OverflowingMul = 66, + U32OverflowingMulImm = 67, + U32OverflowingMadd = 68, + U32WrappingMadd = 69, + U32Div = 70, + U32DivImm = 71, + U32Mod = 72, + U32ModImm = 73, + U32DivMod = 74, + U32DivModImm = 75, + U32And = 76, + U32Or = 77, + U32Xor = 78, + U32Not = 79, + U32Shr = 80, + U32ShrImm = 81, + U32Shl = 82, + U32ShlImm = 83, + U32Rotr = 84, + U32RotrImm = 85, + U32Rotl = 86, + U32RotlImm = 87, + U32Popcnt = 88, + U32Clz = 89, + U32Ctz = 90, + U32Clo = 91, + U32Cto = 92, + U32Lt = 93, + U32Lte = 94, + U32Gt = 95, + U32Gte = 96, + U32Min = 97, + U32Max = 98, // ----- stack manipulation ------------------------------------------------------------------- - Drop = 94, - DropW = 95, - PadW = 96, - Dup0 = 97, - Dup1 = 98, - Dup2 = 99, - Dup3 = 100, - Dup4 = 101, - Dup5 = 102, - Dup6 = 103, - Dup7 = 104, - Dup8 = 105, - Dup9 = 106, - Dup10 = 107, - Dup11 = 108, - Dup12 = 109, - Dup13 = 110, - Dup14 = 111, - Dup15 = 112, - DupW0 = 113, - DupW1 = 114, - DupW2 = 115, - DupW3 = 116, - Swap1 = 117, - Swap2 = 118, - Swap3 = 119, - Swap4 = 120, - Swap5 = 121, - Swap6 = 122, - Swap7 = 123, - Swap8 = 124, - Swap9 = 125, - Swap10 = 126, - Swap11 = 127, - Swap12 = 128, - Swap13 = 129, - Swap14 = 130, - Swap15 = 131, - SwapW1 = 132, - SwapW2 = 133, - SwapW3 = 134, - SwapDW = 135, - MovUp2 = 136, - MovUp3 = 137, - MovUp4 = 138, - MovUp5 = 139, - MovUp6 = 140, - MovUp7 = 141, - MovUp8 = 142, - MovUp9 = 143, - MovUp10 = 144, - MovUp11 = 145, - MovUp12 = 146, - MovUp13 = 147, - MovUp14 = 148, - MovUp15 = 149, - MovUpW2 = 150, - MovUpW3 = 151, - MovDn2 = 152, - MovDn3 = 153, - MovDn4 = 154, - MovDn5 = 155, - MovDn6 = 156, - MovDn7 = 157, - MovDn8 = 158, - MovDn9 = 159, - MovDn10 = 160, - MovDn11 = 161, - MovDn12 = 162, - MovDn13 = 163, - MovDn14 = 164, - MovDn15 = 165, - MovDnW2 = 166, - MovDnW3 = 167, - CSwap = 168, - CSwapW = 169, - CDrop = 170, - CDropW = 171, + Drop = 99, + DropW = 100, + PadW = 101, + Dup0 = 102, + Dup1 = 103, + Dup2 = 104, + Dup3 = 105, + Dup4 = 106, + Dup5 = 107, + Dup6 = 108, + Dup7 = 109, + Dup8 = 110, + Dup9 = 111, + Dup10 = 112, + Dup11 = 113, + Dup12 = 114, + Dup13 = 115, + Dup14 = 116, + Dup15 = 117, + DupW0 = 118, + DupW1 = 119, + DupW2 = 120, + DupW3 = 121, + Swap1 = 122, + Swap2 = 123, + Swap3 = 124, + Swap4 = 125, + Swap5 = 126, + Swap6 = 127, + Swap7 = 128, + Swap8 = 129, + Swap9 = 130, + Swap10 = 131, + Swap11 = 132, + Swap12 = 133, + Swap13 = 134, + Swap14 = 135, + Swap15 = 136, + SwapW1 = 137, + SwapW2 = 138, + SwapW3 = 139, + SwapDW = 140, + MovUp2 = 141, + MovUp3 = 142, + MovUp4 = 143, + MovUp5 = 144, + MovUp6 = 145, + MovUp7 = 146, + MovUp8 = 147, + MovUp9 = 148, + MovUp10 = 149, + MovUp11 = 150, + MovUp12 = 151, + MovUp13 = 152, + MovUp14 = 153, + MovUp15 = 154, + MovUpW2 = 155, + MovUpW3 = 156, + MovDn2 = 157, + MovDn3 = 158, + MovDn4 = 159, + MovDn5 = 160, + MovDn6 = 161, + MovDn7 = 162, + MovDn8 = 163, + MovDn9 = 164, + MovDn10 = 165, + MovDn11 = 166, + MovDn12 = 167, + MovDn13 = 168, + MovDn14 = 169, + MovDn15 = 170, + MovDnW2 = 171, + MovDnW3 = 172, + CSwap = 173, + CSwapW = 174, + CDrop = 175, + CDropW = 176, // ----- input / output operations ------------------------------------------------------------ - PushU8 = 172, - PushU16 = 173, - PushU32 = 174, - PushFelt = 175, - PushWord = 176, - PushU8List = 177, - PushU16List = 178, - PushU32List = 179, - PushFeltList = 180, + PushU8 = 177, + PushU16 = 178, + PushU32 = 179, + PushFelt = 180, + PushWord = 181, + PushU8List = 182, + PushU16List = 183, + PushU32List = 184, + PushFeltList = 185, - Locaddr = 181, - Sdepth = 182, - Caller = 183, - Clk = 184, + Locaddr = 186, + Sdepth = 187, + Caller = 188, + Clk = 189, - MemLoad = 185, - MemLoadImm = 186, - MemLoadW = 187, - MemLoadWImm = 188, - LocLoad = 189, - LocLoadW = 190, - MemStore = 191, - MemStoreImm = 192, - LocStore = 193, - MemStoreW = 194, - MemStoreWImm = 195, - LocStoreW = 196, + MemLoad = 190, + MemLoadImm = 191, + MemLoadW = 192, + MemLoadWImm = 193, + LocLoad = 194, + LocLoadW = 195, + MemStore = 196, + MemStoreImm = 197, + LocStore = 198, + MemStoreW = 199, + MemStoreWImm = 200, + LocStoreW = 201, - MemStream = 197, - AdvPipe = 198, + MemStream = 202, + AdvPipe = 203, - AdvPush = 199, - AdvLoadW = 200, + AdvPush = 204, + AdvLoadW = 205, - AdvInject = 201, + AdvInject = 206, // ----- cryptographic operations ------------------------------------------------------------- - Hash = 202, - HMerge = 203, - HPerm = 204, - MTreeGet = 205, - MTreeSet = 206, - MTreeMerge = 207, - MTreeVerify = 208, + Hash = 207, + HMerge = 208, + HPerm = 209, + MTreeGet = 210, + MTreeSet = 211, + MTreeMerge = 212, + MTreeVerify = 213, // ----- STARK proof verification ------------------------------------------------------------- - FriExt2Fold4 = 209, - RCombBase = 210, + FriExt2Fold4 = 214, + RCombBase = 215, // ----- exec / call -------------------------------------------------------------------------- - ExecLocal = 211, - ExecImported = 212, - CallLocal = 213, - CallMastRoot = 214, - CallImported = 215, - SysCall = 216, - DynExec = 217, - DynCall = 218, - ProcRefLocal = 219, - ProcRefImported = 220, + ExecLocal = 216, + ExecImported = 217, + CallLocal = 218, + CallMastRoot = 219, + CallImported = 220, + SysCall = 221, + DynExec = 222, + DynCall = 223, + ProcRefLocal = 224, + ProcRefImported = 225, // ----- debugging ---------------------------------------------------------------------------- - Debug = 221, + Debug = 226, // ----- event decorators --------------------------------------------------------------------- - Emit = 222, - Trace = 223, + Emit = 227, + Trace = 228, // ----- control flow ------------------------------------------------------------------------- IfElse = 253, diff --git a/assembly/src/ast/nodes/serde/serialization.rs b/assembly/src/ast/nodes/serde/serialization.rs index 53e3f45ec9..71a0edeb05 100644 --- a/assembly/src/ast/nodes/serde/serialization.rs +++ b/assembly/src/ast/nodes/serde/serialization.rs @@ -105,6 +105,7 @@ impl Serializable for Instruction { OpCode::ExpBitLength.write_into(target); target.write_u8(*v); } + Self::ILog2 => OpCode::ILog2.write_into(target), Self::Not => OpCode::Not.write_into(target), Self::And => OpCode::And.write_into(target), Self::Or => OpCode::Or.write_into(target), @@ -228,6 +229,10 @@ impl Serializable for Instruction { target.write_u8(*v); } Self::U32Popcnt => OpCode::U32Popcnt.write_into(target), + Self::U32Clz => OpCode::U32Clz.write_into(target), + Self::U32Ctz => OpCode::U32Ctz.write_into(target), + Self::U32Clo => OpCode::U32Clo.write_into(target), + Self::U32Cto => OpCode::U32Cto.write_into(target), Self::U32Lt => OpCode::U32Lt.write_into(target), Self::U32Lte => OpCode::U32Lte.write_into(target), Self::U32Gt => OpCode::U32Gt.write_into(target), diff --git a/assembly/src/ast/parsers/adv_ops.rs b/assembly/src/ast/parsers/adv_ops.rs index 4d758bf971..2ba6802145 100644 --- a/assembly/src/ast/parsers/adv_ops.rs +++ b/assembly/src/ast/parsers/adv_ops.rs @@ -23,7 +23,7 @@ pub fn parse_adv_inject(op: &Token) -> Result { let injector = match op.parts()[1] { "push_u64div" => match op.num_parts() { - 2 => AdvInject(PushU64div), + 2 => AdvInject(PushU64Div), _ => return Err(ParsingError::extra_param(op)), }, "push_ext2intt" => match op.num_parts() { diff --git a/assembly/src/ast/parsers/context.rs b/assembly/src/ast/parsers/context.rs index ba5ace4639..41c3dadb9f 100644 --- a/assembly/src/ast/parsers/context.rs +++ b/assembly/src/ast/parsers/context.rs @@ -477,6 +477,7 @@ impl ParserContext<'_> { "pow2" => simple_instruction(op, Pow2), "exp" => field_ops::parse_exp(op), + "ilog2" => simple_instruction(op, ILog2), "not" => simple_instruction(op, Not), "and" => simple_instruction(op, And), @@ -542,6 +543,10 @@ impl ParserContext<'_> { "u32rotl" => u32_ops::parse_u32_rotl(op), "u32popcnt" => simple_instruction(op, U32Popcnt), + "u32clz" => simple_instruction(op, U32Clz), + "u32ctz" => simple_instruction(op, U32Ctz), + "u32clo" => simple_instruction(op, U32Clo), + "u32cto" => simple_instruction(op, U32Cto), "u32lt" => simple_instruction(op, U32Lt), "u32lte" => simple_instruction(op, U32Lte), diff --git a/assembly/src/ast/tests.rs b/assembly/src/ast/tests.rs index 6611e6f950..f465ffa1e4 100644 --- a/assembly/src/ast/tests.rs +++ b/assembly/src/ast/tests.rs @@ -226,7 +226,7 @@ fn test_ast_parsing_adv_injection() { let source = "begin adv.push_u64div adv.push_mapval adv.push_smtget adv.insert_mem end"; let nodes: Vec = vec![ - Node::Instruction(AdvInject(PushU64div)), + Node::Instruction(AdvInject(PushU64Div)), Node::Instruction(AdvInject(PushMapVal)), Node::Instruction(AdvInject(PushSmtGet)), Node::Instruction(AdvInject(InsertMem)), @@ -235,6 +235,28 @@ fn test_ast_parsing_adv_injection() { assert_program_output(source, BTreeMap::new(), nodes); } +#[test] +fn test_ast_parsing_bitwise_counters() { + let source = "begin u32clz u32ctz u32clo u32cto end"; + let nodes: Vec = vec![ + Node::Instruction(Instruction::U32Clz), + Node::Instruction(Instruction::U32Ctz), + Node::Instruction(Instruction::U32Clo), + Node::Instruction(Instruction::U32Cto), + ]; + + assert_program_output(source, BTreeMap::new(), nodes); +} + +#[test] +fn test_ast_parsing_ilog2() { + let source = "begin push.8 ilog2 end"; + let nodes: Vec = + vec![Node::Instruction(Instruction::PushU8(8)), Node::Instruction(Instruction::ILog2)]; + + assert_program_output(source, BTreeMap::new(), nodes); +} + #[test] fn test_ast_parsing_use() { let source = "\ diff --git a/core/src/operations/decorators/advice.rs b/core/src/operations/decorators/advice.rs index e84c5ae7f8..9f2aef6807 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/operations/decorators/advice.rs @@ -103,7 +103,7 @@ pub enum AdviceInjector { /// respectively (with a0 representing the 32 lest significant bits and a1 representing the /// 32 most significant bits). Similarly, (q0, q1) and (r0, r1) represent the quotient and /// the remainder respectively. - DivU64, + U64Div, /// Given an element in a quadratic extension field on the top of the stack (i.e., a0, b1), /// computes its multiplicative inverse and push the result onto the advice stack. @@ -165,6 +165,60 @@ pub enum AdviceInjector { /// Advice stack: [VALUE, ...] SmtPeek, + /// Pushes the number of the leading zeros of the top stack element onto the advice stack. + /// + /// Inputs: + /// Operand stack: [n, ...] + /// Advice stack: [...] + /// + /// Outputs: + /// Operand stack: [n, ...] + /// Advice stack: [leading_zeros, ...] + U32Clz, + + /// Pushes the number of the trailing zeros of the top stack element onto the advice stack. + /// + /// Inputs: + /// Operand stack: [n, ...] + /// Advice stack: [...] + /// + /// Outputs: + /// Operand stack: [n, ...] + /// Advice stack: [trailing_zeros, ...] + U32Ctz, + + /// Pushes the number of the leading ones of the top stack element onto the advice stack. + /// + /// Inputs: + /// Operand stack: [n, ...] + /// Advice stack: [...] + /// + /// Outputs: + /// Operand stack: [n, ...] + /// Advice stack: [leading_ones, ...] + U32Clo, + + /// Pushes the number of the trailing ones of the top stack element onto the advice stack. + /// + /// Inputs: + /// Operand stack: [n, ...] + /// Advice stack: [...] + /// + /// Outputs: + /// Operand stack: [n, ...] + /// Advice stack: [trailing_ones, ...] + U32Cto, + + /// Pushes the base 2 logarithm of the top stack element, rounded down. + /// Inputs: + /// Operand stack: [n, ...] + /// Advice stack: [...] + /// + /// Outputs: + /// Operand stack: [n, ...] + /// Advice stack: [ilog2(n), ...] + ILog2, + // ADVICE MAP INJECTORS // -------------------------------------------------------------------------------------------- /// Reads words from memory at the specified range and inserts them into the advice map under @@ -245,12 +299,17 @@ impl fmt::Display for AdviceInjector { write!(f, "map_value_to_stack.{key_offset}") } } - Self::DivU64 => write!(f, "div_u64"), + Self::U64Div => write!(f, "div_u64"), Self::Ext2Inv => write!(f, "ext2_inv"), Self::Ext2Intt => write!(f, "ext2_intt"), Self::SmtGet => write!(f, "smt_get"), Self::SmtSet => write!(f, "smt_set"), Self::SmtPeek => write!(f, "smt_peek"), + Self::U32Clz => write!(f, "u32clz"), + Self::U32Ctz => write!(f, "u32ctz"), + Self::U32Clo => write!(f, "u32clo"), + Self::U32Cto => write!(f, "u32cto"), + Self::ILog2 => write!(f, "ilog2"), Self::MemToMap => write!(f, "mem_to_map"), Self::HdwordToMap { domain } => write!(f, "hdword_to_map.{domain}"), Self::HpermToMap => write!(f, "hperm_to_map"), diff --git a/docs/src/user_docs/assembly/field_operations.md b/docs/src/user_docs/assembly/field_operations.md index 10327ff6bd..d3425c17a3 100644 --- a/docs/src/user_docs/assembly/field_operations.md +++ b/docs/src/user_docs/assembly/field_operations.md @@ -35,6 +35,7 @@ The arithmetic operations below are performed in a 64-bit [prime filed](https:// | inv
- *(1 cycle)* | [a, ...] | [b, ...] | $b \leftarrow a^{-1} \mod p$
Fails if $a = 0$ | | pow2
- *(16 cycles)* | [a, ...] | [b, ...] | $b \leftarrow 2^a$
Fails if $a > 63$ | | exp.*uxx*
- *(9 + xx cycles)*
exp.*b*
- *(9 + log2(b) cycles)* | [b, a, ...] | [c, ...] | $c \leftarrow a^b$
Fails if xx is outside [0, 63)
exp is equivalent to exp.u64 and needs 73 cycles | +| ilog2
- *(44 cycles)* | [a, ...] | [b, ...] | $b \leftarrow \lfloor{log_2{a}}\rfloor$
Fails if $a = 0 $ | | not
- *(1 cycle)* | [a, ...] | [b, ...] | $b \leftarrow 1 - a$
Fails if $a > 1$ | | and
- *(1 cycle)* | [b, a, ...] | [c, ...] | $c \leftarrow a \cdot b$
Fails if $max(a, b) > 1$ | | or
- *(1 cycle)* | [b, a, ...] | [c, ...] | $c \leftarrow a + b - a \cdot b$
Fails if $max(a, b) > 1$ | diff --git a/docs/src/user_docs/assembly/u32_operations.md b/docs/src/user_docs/assembly/u32_operations.md index 325fdd1c1c..10bb7ae61a 100644 --- a/docs/src/user_docs/assembly/u32_operations.md +++ b/docs/src/user_docs/assembly/u32_operations.md @@ -55,6 +55,11 @@ If the error code is omitted, the default value of $0$ is assumed. | u32rotl
- *(18 cycles)*
u32rotl.*b*
- *(3 cycles)* | [b, a, ...] | [c, ...] | Computes $c$ by rotating a 32-bit representation of $a$ to the left by $b$ bits.
Undefined if $a \ge 2^{32}$ or $b > 31$ | | u32rotr
- *(22 cycles)*
u32rotr.*b*
- *(3 cycles)* | [b, a, ...] | [c, ...] | Computes $c$ by rotating a 32-bit representation of $a$ to the right by $b$ bits.
Undefined if $a \ge 2^{32}$ or $b > 31$ | | u32popcnt
- *(33 cycles)* | [a, ...] | [b, ...] | Computes $b$ by counting the number of set bits in $a$ (hamming weight of $a$).
Undefined if $a \ge 2^{32}$ | +| u32clz
- *(37 cycles)* | [a, ...] | [b, ...] | Computes $b$ as a number of leading zeros of $a$.
Undefined if $a \ge 2^{32}$ | +| u32ctz
- *(34 cycles)* | [a, ...] | [b, ...] | Computes $b$ as a number of trailing zeros of $a$.
Undefined if $a \ge 2^{32}$ | +| u32clo
- *(36 cycles)* | [a, ...] | [b, ...] | Computes $b$ as a number of leading ones of $a$.
Undefined if $a \ge 2^{32}$ | +| u32cto
- *(33 cycles)* | [a, ...] | [b, ...] | Computes $b$ as a number of trailing ones of $a$.
Undefined if $a \ge 2^{32}$ | + ### Comparison operations diff --git a/miden/tests/integration/operations/field_ops.rs b/miden/tests/integration/operations/field_ops.rs index 278030537a..fb0d7e9247 100644 --- a/miden/tests/integration/operations/field_ops.rs +++ b/miden/tests/integration/operations/field_ops.rs @@ -352,6 +352,23 @@ fn exp_small_pow() { test.expect_stack(&[expected.as_int()]); } +#[test] +fn ilog2() { + let asm_op = "ilog2"; + build_op_test!(asm_op, &[1]).expect_stack(&[0]); + build_op_test!(asm_op, &[8]).expect_stack(&[3]); + build_op_test!(asm_op, &[15]).expect_stack(&[3]); + build_op_test!(asm_op, &[Felt::MODULUS - 1]).expect_stack(&[63]); +} + +#[test] +fn ilog2_fail() { + let asm_op = "ilog2"; + + build_op_test!(asm_op, &[0]) + .expect_error(TestError::ExecutionError(ExecutionError::LogArgumentZero(1))); +} + // FIELD OPS BOOLEAN - MANUAL TESTS // ================================================================================================ @@ -652,32 +669,38 @@ proptest! { let asm_op = "pow2"; let expected = 2_u64.wrapping_pow(b); - build_op_test!(asm_op, &[b as u64]).prop_expect_stack(&[expected])?; + let test = build_op_test!(asm_op, &[b as u64]); + test.prop_expect_stack(&[expected])?; } #[test] fn exp_proptest(a in any::(), b in any::()) { - - //---------------------- exp with no parameter ------------------------------------- - + // --- exp with no parameter -------------------------------------------------------------- let asm_op = "exp"; let base = a; let pow = b; let expected = Felt::new(base).exp(pow); let test = build_op_test!(asm_op, &[base, pow]); - test.expect_stack(&[expected.as_int()]); - - //----------------------- exp with parameter containing pow ---------------- + test.prop_expect_stack(&[expected.as_int()])?; + // --- exp with parameter containing pow -------------------------------------------------- let build_asm_op = |param: u64| format!("exp.{param}"); let base = a; let pow = b; let expected = Felt::new(base).exp(pow); let test = build_op_test!(build_asm_op(pow), &[base]); - test.expect_stack(&[expected.as_int()]); + test.prop_expect_stack(&[expected.as_int()])?; + } + #[test] + fn ilog2_proptest(a in 1..Felt::MODULUS) { + let asm_op = "ilog2"; + let expected = a.ilog2(); + + let test = build_op_test!(asm_op, &[a]); + test.prop_expect_stack(&[expected as u64])?; } } diff --git a/miden/tests/integration/operations/u32_ops/bitwise_ops.rs b/miden/tests/integration/operations/u32_ops/bitwise_ops.rs index 19b0ca13cf..7b29904d85 100644 --- a/miden/tests/integration/operations/u32_ops/bitwise_ops.rs +++ b/miden/tests/integration/operations/u32_ops/bitwise_ops.rs @@ -442,6 +442,46 @@ fn u32popcnt() { build_op_test!(asm_op, &[4294967295]).expect_stack(&[32]); } +#[test] +fn u32clz() { + let asm_op = "u32clz"; + build_op_test!(asm_op, &[0]).expect_stack(&[32]); + build_op_test!(asm_op, &[1]).expect_stack(&[31]); + // bit representation of the 67123567 is 00000100000000000011100101101111 + build_op_test!(asm_op, &[67123567]).expect_stack(&[5]); + build_op_test!(asm_op, &[4294967295]).expect_stack(&[0]); +} + +#[test] +fn u32ctz() { + let asm_op = "u32ctz"; + build_op_test!(asm_op, &[0]).expect_stack(&[32]); + build_op_test!(asm_op, &[1]).expect_stack(&[0]); + // bit representaion of the 14688 is 00000000000000000011100101100000 + build_op_test!(asm_op, &[14688]).expect_stack(&[5]); + build_op_test!(asm_op, &[4294967295]).expect_stack(&[0]); +} + +#[test] +fn u32clo() { + let asm_op = "u32clo"; + build_op_test!(asm_op, &[0]).expect_stack(&[0]); + build_op_test!(asm_op, &[1]).expect_stack(&[0]); + // bit representation of the 4185032762 is 11111001011100101000100000111010 + build_op_test!(asm_op, &[4185032762]).expect_stack(&[5]); + build_op_test!(asm_op, &[4294967295]).expect_stack(&[32]); +} + +#[test] +fn u32cto() { + let asm_op = "u32cto"; + build_op_test!(asm_op, &[0]).expect_stack(&[0]); + build_op_test!(asm_op, &[1]).expect_stack(&[1]); + // bit representation of the 4185032735 is 11111001011100101000100000011111 + build_op_test!(asm_op, &[4185032735]).expect_stack(&[5]); + build_op_test!(asm_op, &[4294967295]).expect_stack(&[32]); +} + // U32 OPERATIONS TESTS - RANDOMIZED - BITWISE OPERATIONS // ================================================================================================ @@ -553,4 +593,36 @@ proptest! { let test = build_op_test!(asm_opcode, &[a as u64]); test.prop_expect_stack(&[expected as u64])?; } + + #[test] + fn u32clz_proptest(a in any::()) { + let asm_opcode = "u32clz"; + let expected = a.leading_zeros(); + let test = build_op_test!(asm_opcode, &[a as u64]); + test.prop_expect_stack(&[expected as u64])?; + } + + #[test] + fn u32ctz_proptest(a in any::()) { + let asm_opcode = "u32ctz"; + let expected = a.trailing_zeros(); + let test = build_op_test!(asm_opcode, &[a as u64]); + test.prop_expect_stack(&[expected as u64])?; + } + + #[test] + fn u32clo_proptest(a in any::()) { + let asm_opcode = "u32clo"; + let expected = a.leading_ones(); + let test = build_op_test!(asm_opcode, &[a as u64]); + test.prop_expect_stack(&[expected as u64])?; + } + + #[test] + fn u32cto_proptest(a in any::()) { + let asm_opcode = "u32cto"; + let expected = a.trailing_ones(); + let test = build_op_test!(asm_opcode, &[a as u64]); + test.prop_expect_stack(&[expected as u64])?; + } } diff --git a/processor/src/errors.rs b/processor/src/errors.rs index 71f29e7622..46587b2424 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -20,9 +20,9 @@ pub enum ExecutionError { AdviceStackReadFailed(u32), CallerNotInSyscall, CodeBlockNotFound(Digest), - DynamicCodeBlockNotFound(Digest), CycleLimitExceeded(u32), DivideByZero(u32), + DynamicCodeBlockNotFound(Digest), EventError(String), Ext2InttError(Ext2InttError), FailedAssertion { @@ -30,6 +30,7 @@ pub enum ExecutionError { err_code: u32, err_msg: Option, }, + FailedSignatureGeneration(&'static str), InvalidFmpValue(Felt, Felt), InvalidFriDomainSegment(u64), InvalidFriLayerFolding(QuadFelt, QuadFelt), @@ -46,14 +47,16 @@ pub enum ExecutionError { depth: Felt, value: Felt, }, + LogArgumentZero(u32), + MalformedSignatureKey(&'static str), MemoryAddressOutOfBounds(u64), MerklePathVerificationFailed { value: Word, index: Felt, root: Digest, }, - MerkleStoreMergeFailed(MerkleError), MerkleStoreLookupFailed(MerkleError), + MerkleStoreMergeFailed(MerkleError), MerkleStoreUpdateFailed(MerkleError), NotBinaryValue(Felt), NotU32Value(Felt, Felt), @@ -62,8 +65,6 @@ pub enum ExecutionError { SmtNodePreImageNotValid(Word, usize), SyscallTargetNotInKernel(Digest), UnexecutableCodeBlock(CodeBlock), - MalformedSignatureKey(&'static str), - FailedSignatureGeneration(&'static str), } impl Display for ExecutionError { @@ -86,6 +87,10 @@ impl Display for ExecutionError { "Failed to execute code block with root {hex}; the block could not be found" ) } + CycleLimitExceeded(max_cycles) => { + write!(f, "Exceeded the allowed number of cycles (max cycles = {max_cycles})") + } + DivideByZero(clk) => write!(f, "Division by zero at clock cycle {clk}"), DynamicCodeBlockNotFound(digest) => { let hex = to_hex(&digest.as_bytes())?; write!( @@ -93,10 +98,6 @@ impl Display for ExecutionError { "Failed to execute the dynamic code block provided by the stack with root {hex}; the block could not be found" ) } - CycleLimitExceeded(max_cycles) => { - write!(f, "Exceeded the allowed number of cycles (max cycles = {max_cycles})") - } - DivideByZero(clk) => write!(f, "Division by zero at clock cycle {clk}"), EventError(error) => write!(f, "Failed to process event - {error}"), Ext2InttError(err) => write!(f, "Failed to execute Ext2Intt operation: {err}"), FailedAssertion { @@ -113,6 +114,9 @@ impl Display for ExecutionError { write!(f, "Assertion failed at clock cycle {clk} with error code {err_code}") } } + FailedSignatureGeneration(signature) => { + write!(f, "Failed to generate signature: {signature}") + } InvalidFmpValue(old, new) => { write!(f, "Updating FMP register from {old} to {new} failed because {new} is outside of {FMP_MIN}..{FMP_MAX}") } @@ -140,6 +144,13 @@ impl Display for ExecutionError { InvalidTreeNodeIndex { depth, value } => { write!(f, "The provided index {value} is out of bounds for a node at depth {depth}") } + LogArgumentZero(clk) => { + write!( + f, + "Calculating of the integer logarithm with zero argument at clock cycle {clk}" + ) + } + MalformedSignatureKey(signature) => write!(f, "Malformed signature key: {signature}"), MemoryAddressOutOfBounds(addr) => { write!(f, "Memory address cannot exceed 2^32 but was {addr}") } @@ -182,10 +193,6 @@ impl Display for ExecutionError { UnexecutableCodeBlock(block) => { write!(f, "Execution reached unexecutable code block {block:?}") } - MalformedSignatureKey(signature) => write!(f, "Malformed signature key: {signature}"), - FailedSignatureGeneration(signature) => { - write!(f, "Failed to generate signature: {signature}") - } } } } diff --git a/processor/src/host/advice/injectors/adv_stack_injectors.rs b/processor/src/host/advice/injectors/adv_stack_injectors.rs index 5046149d82..028901600f 100644 --- a/processor/src/host/advice/injectors/adv_stack_injectors.rs +++ b/processor/src/host/advice/injectors/adv_stack_injectors.rs @@ -1,5 +1,7 @@ use super::super::{AdviceSource, ExecutionError, Felt, HostResponse}; -use crate::{utils::collections::*, AdviceProvider, Ext2InttError, FieldElement, ProcessState}; +use crate::{ + utils::collections::*, AdviceProvider, Ext2InttError, FieldElement, ProcessState, ZERO, +}; use vm_core::{QuadExtension, SignatureKind}; use winter_prover::math::fft; @@ -296,11 +298,124 @@ pub(crate) fn push_signature( Ok(HostResponse::None) } +/// Pushes the number of the leading zeros of the top stack element onto the advice stack. +/// +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [leading_zeros, ...] +pub(crate) fn push_leading_zeros( + advice_provider: &mut A, + process: &S, +) -> Result { + push_transformed_stack_top(advice_provider, process, |stack_top| { + Felt::from(stack_top.leading_zeros()) + }) +} + +/// Pushes the number of the trailing zeros of the top stack element onto the advice stack. +/// +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [trailing_zeros, ...] +pub(crate) fn push_trailing_zeros( + advice_provider: &mut A, + process: &S, +) -> Result { + push_transformed_stack_top(advice_provider, process, |stack_top| { + Felt::from(stack_top.trailing_zeros()) + }) +} + +/// Pushes the number of the leading ones of the top stack element onto the advice stack. +/// +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [leading_ones, ...] +pub(crate) fn push_leading_ones( + advice_provider: &mut A, + process: &S, +) -> Result { + push_transformed_stack_top(advice_provider, process, |stack_top| { + Felt::from(stack_top.leading_ones()) + }) +} + +/// Pushes the number of the trailing ones of the top stack element onto the advice stack. +/// +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [trailing_ones, ...] +pub(crate) fn push_trailing_ones( + advice_provider: &mut A, + process: &S, +) -> Result { + push_transformed_stack_top(advice_provider, process, |stack_top| { + Felt::from(stack_top.trailing_ones()) + }) +} + +/// Pushes the base 2 logarithm of the top stack element, rounded down. +/// Inputs: +/// Operand stack: [n, ...] +/// Advice stack: [...] +/// +/// Outputs: +/// Operand stack: [n, ...] +/// Advice stack: [ilog2(n), ...] +/// +/// # Errors +/// Returns an error if the logarithm argument (top stack element) equals ZERO. +pub(crate) fn push_ilog2( + advice_provider: &mut A, + process: &S, +) -> Result { + let n = process.get_stack_item(0).as_int(); + if n == 0 { + return Err(ExecutionError::LogArgumentZero(process.clk())); + } + let ilog2 = Felt::from(n.ilog2()); + advice_provider.push_stack(AdviceSource::Value(ilog2))?; + Ok(HostResponse::None) +} + // HELPER FUNCTIONS // ================================================================================================ fn u64_to_u32_elements(value: u64) -> (Felt, Felt) { - let hi = Felt::new(value >> 32); - let lo = Felt::new((value as u32) as u64); + let hi = Felt::from((value >> 32) as u32); + let lo = Felt::from(value as u32); (hi, lo) } + +/// Gets the top stack element, applies a provided function to it and pushes it to the advice +/// provider. +fn push_transformed_stack_top( + advice_provider: &mut A, + process: &S, + f: impl FnOnce(u32) -> Felt, +) -> Result { + let stack_top = process.get_stack_item(0); + let stack_top: u32 = stack_top + .as_int() + .try_into() + .map_err(|_| ExecutionError::NotU32Value(stack_top, ZERO))?; + let transformed_stack_top = f(stack_top); + advice_provider.push_stack(AdviceSource::Value(transformed_stack_top))?; + Ok(HostResponse::None) +} diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index 94c391b84a..c28c53c00b 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -63,12 +63,18 @@ pub trait AdviceProvider: Sized { key_offset, } => self.copy_map_value_to_adv_stack(process, *include_len, *key_offset), AdviceInjector::UpdateMerkleNode => self.update_operand_stack_merkle_node(process), - AdviceInjector::DivU64 => self.push_u64_div_result(process), + AdviceInjector::U64Div => self.push_u64_div_result(process), AdviceInjector::Ext2Inv => self.push_ext2_inv_result(process), AdviceInjector::Ext2Intt => self.push_ext2_intt_result(process), AdviceInjector::SmtGet => self.push_smtget_inputs(process), AdviceInjector::SmtSet => self.push_smtset_inputs(process), AdviceInjector::SmtPeek => self.push_smtpeek_result(process), + AdviceInjector::U32Clz => self.push_leading_zeros(process), + AdviceInjector::U32Ctz => self.push_trailing_zeros(process), + AdviceInjector::U32Clo => self.push_leading_ones(process), + AdviceInjector::U32Cto => self.push_trailing_ones(process), + AdviceInjector::ILog2 => self.push_ilog2(process), + AdviceInjector::MemToMap => self.insert_mem_values_into_adv_map(process), AdviceInjector::HdwordToMap { domain } => { self.insert_hdword_into_adv_map(process, *domain) @@ -362,6 +368,85 @@ pub trait AdviceProvider: Sized { injectors::adv_stack_injectors::push_signature(self, process, kind) } + /// Pushes the number of the leading zeros of the top stack element onto the advice stack. + /// + /// Inputs: + /// Operand stack: [n, ...] + /// Advice stack: [...] + /// + /// Outputs: + /// Operand stack: [n, ...] + /// Advice stack: [leading_zeros, ...] + fn push_leading_zeros( + &mut self, + process: &S, + ) -> Result { + injectors::adv_stack_injectors::push_leading_zeros(self, process) + } + + /// Pushes the number of the trailing zeros of the top stack element onto the advice stack. + /// + /// Inputs: + /// Operand stack: [n, ...] + /// Advice stack: [...] + /// + /// Outputs: + /// Operand stack: [n, ...] + /// Advice stack: [trailing_zeros, ...] + fn push_trailing_zeros( + &mut self, + process: &S, + ) -> Result { + injectors::adv_stack_injectors::push_trailing_zeros(self, process) + } + + /// Pushes the number of the leading ones of the top stack element onto the advice stack. + /// + /// Inputs: + /// Operand stack: [n, ...] + /// Advice stack: [...] + /// + /// Outputs: + /// Operand stack: [n, ...] + /// Advice stack: [leading_ones, ...] + fn push_leading_ones( + &mut self, + process: &S, + ) -> Result { + injectors::adv_stack_injectors::push_leading_ones(self, process) + } + + /// Pushes the number of the trailing ones of the top stack element onto the advice stack. + /// + /// Inputs: + /// Operand stack: [n, ...] + /// Advice stack: [...] + /// + /// Outputs: + /// Operand stack: [n, ...] + /// Advice stack: [trailing_ones, ...] + fn push_trailing_ones( + &mut self, + process: &S, + ) -> Result { + injectors::adv_stack_injectors::push_trailing_ones(self, process) + } + + /// Pushes the base 2 logarithm of the top stack element, rounded down. + /// Inputs: + /// Operand stack: [n, ...] + /// Advice stack: [...] + /// + /// Outputs: + /// Operand stack: [n, ...] + /// Advice stack: [ilog2(n), ...] + /// + /// # Errors + /// Returns an error if the logarithm argument (top stack element) equals ZERO. + fn push_ilog2(&mut self, process: &S) -> Result { + injectors::adv_stack_injectors::push_ilog2(self, process) + } + // DEFAULT MERKLE STORE INJECTORS // --------------------------------------------------------------------------------------------