From 6d80dc6ddcf89a28206978c5ac5abef663206684 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Fri, 8 Dec 2023 21:13:00 +0100 Subject: [PATCH] feat: implement u32clz, u32ctz, u32clo, u32cto and ilog2 instrs --- CHANGELOG.md | 1 + .../src/assembler/instruction/field_ops.rs | 44 ++ assembly/src/assembler/instruction/mod.rs | 5 + assembly/src/assembler/instruction/u32_ops.rs | 174 ++++++-- assembly/src/ast/nodes/mod.rs | 10 + .../src/ast/nodes/serde/deserialization.rs | 5 + assembly/src/ast/nodes/serde/mod.rs | 403 +++++++++--------- assembly/src/ast/nodes/serde/serialization.rs | 5 + assembly/src/ast/parsers/context.rs | 6 + assembly/src/ast/tests.rs | 22 + core/src/operations/decorators/advice.rs | 59 +++ .../user_docs/assembly/field_operations.md | 1 + docs/src/user_docs/assembly/u32_operations.md | 5 + .../tests/integration/operations/field_ops.rs | 32 +- .../operations/u32_ops/bitwise_ops.rs | 72 ++++ processor/src/errors.rs | 31 +- .../advice/injectors/adv_stack_injectors.rs | 101 +++++ processor/src/host/advice/mod.rs | 85 ++++ 18 files changed, 811 insertions(+), 250 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc28ae1a2a..e6c768a00d 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). #### Stdlib - Introduced `std::utils` module with `is_empty_word` procedure. Refactored `std::collections::smt` diff --git a/assembly/src/assembler/instruction/field_ops.rs b/assembly/src/assembler/instruction/field_ops.rs index 0619c1596d..4d70101afb 100644 --- a/assembly/src/assembler/instruction/field_ops.rs +++ b/assembly/src/assembler/instruction/field_ops.rs @@ -3,6 +3,7 @@ use super::{ StarkField, ONE, 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(ZERO), + // => [ilog2, ...] + ]; + + span.add_ops(ops) +} + // COMPARISON OPERATIONS // ================================================================================================ diff --git a/assembly/src/assembler/instruction/mod.rs b/assembly/src/assembler/instruction/mod.rs index 7fa4f2412c..84732e4804 100644 --- a/assembly/src/assembler/instruction/mod.rs +++ b/assembly/src/assembler/instruction/mod.rs @@ -68,6 +68,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), @@ -158,6 +159,10 @@ impl Assembler { Instruction::U32Gte => u32_ops::u32gte(span), Instruction::U32Min => u32_ops::u32min(span), Instruction::U32Max => u32_ops::u32max(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), // ----- stack manipulation ----------------------------------------------------------- Instruction::Drop => span.add_op(Drop), diff --git a/assembly/src/assembler/instruction/u32_ops.rs b/assembly/src/assembler/instruction/u32_ops.rs index 2b8762764f..ef81004aa0 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::{Clo, Clz, Cto, Ctz}; // 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,54 @@ 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 27 VM cycles. +pub fn u32clz(span: &mut SpanBuilder) -> Result, AssemblyError> { + span.push_advice_injector(Clz); + 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 27 VM cycles. +pub fn u32ctz(span: &mut SpanBuilder) -> Result, AssemblyError> { + span.push_advice_injector(Ctz); + 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 32 VM cycles. +pub fn u32clo(span: &mut SpanBuilder) -> Result, AssemblyError> { + span.push_advice_injector(Clo); + span.push_op(AdvPop); // [clo, n, ...] + let ops = [Swap, Neg, Push(Felt::new(u32::MAX as u64)), Add, Swap]; + span.push_ops(ops); + calculate_clz(span) +} - span.add_op(U32div) +/// 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 32 VM cycles. +pub fn u32cto(span: &mut SpanBuilder) -> Result, AssemblyError> { + span.push_advice_injector(Cto); + span.push_op(AdvPop); // [cto, n, ...] + let ops = [Swap, Neg, Push(Felt::new(u32::MAX as u64)), Add, Swap]; + span.push_ops(ops); + calculate_ctz(span) } // BITWISE OPERATIONS - HELPERS @@ -379,6 +433,54 @@ fn prepare_bitwise( Ok(()) } +/// Appends relevant operations to the span block for the correctness check of the Clz instruction. +/// +/// VM cycles: 26 +fn calculate_clz(span: &mut SpanBuilder) -> Result, AssemblyError> { + // [clz, n, ...] + #[rustfmt::skip] + let ops_group_1 = [ + Swap, Dup1, // [clz, n, clz, ...] + ]; + span.push_ops(ops_group_1); + + append_pow2_op(span); // [pow2(clz), n, clz, ...] + + #[rustfmt::skip] + let ops_group_2 = [ + Push(Felt::new(u32::MAX.into())), // [2^32 - 1, pow2(clz), n, clz, ...] + Swap, U32div, Drop, // [bit_mask, n, clz, ...] + Dup1, U32and, // [m, n, clz, ...] if calculation of clz is correct, m should be equal to n + Eq, Assert(ZERO) // [clz, ...] + ]; + + span.add_ops(ops_group_2) +} + +/// Appends relevant operations to the span block for the correctness check of the Ctz instruction. +/// +/// VM cycles: 26 +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 = [ + Push(Felt::new(u32::MAX as u64 + 1)), // [2^32, pow2(ctz), n, ctz, ...] + Swap, Neg, Add, // [bit_mask, n, ctz] + Dup1, U32and, // [m, n, ctz, ...] if calculation of ctz is correct, m should be equal to n + Eq, Assert(ZERO) // [ctz, ...] + ]; + + span.add_ops(ops_group_2) +} + // COMPARISON OPERATIONS // ================================================================================================ diff --git a/assembly/src/ast/nodes/mod.rs b/assembly/src/ast/nodes/mod.rs index a772ba03a5..433a55f944 100644 --- a/assembly/src/ast/nodes/mod.rs +++ b/assembly/src/ast/nodes/mod.rs @@ -64,6 +64,7 @@ pub enum Instruction { Exp, ExpImm(Felt), ExpBitLength(u8), + ILog2, Not, And, Or, @@ -139,6 +140,10 @@ pub enum Instruction { U32Gte, U32Min, U32Max, + U32Clz, + U32Ctz, + U32Clo, + U32Cto, // ----- stack manipulation ------------------------------------------------------------------- Drop, @@ -322,6 +327,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"), @@ -397,6 +403,10 @@ impl fmt::Display for Instruction { Self::U32Gte => write!(f, "u32gte"), Self::U32Min => write!(f, "u32min"), Self::U32Max => write!(f, "u32max"), + Self::U32Clz => write!(f, "u32clz"), + Self::U32Ctz => write!(f, "u32ctz"), + Self::U32Clo => write!(f, "u32clo"), + Self::U32Cto => write!(f, "u32cto"), // ----- stack manipulation --------------------------------------------------------------- Self::Drop => write!(f, "drop"), diff --git a/assembly/src/ast/nodes/serde/deserialization.rs b/assembly/src/ast/nodes/serde/deserialization.rs index 1195e4c299..10fac6dad0 100644 --- a/assembly/src/ast/nodes/serde/deserialization.rs +++ b/assembly/src/ast/nodes/serde/deserialization.rs @@ -81,6 +81,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), @@ -162,6 +163,10 @@ impl Deserializable for Instruction { OpCode::U32Gte => Ok(Instruction::U32Gte), OpCode::U32Min => Ok(Instruction::U32Min), OpCode::U32Max => Ok(Instruction::U32Max), + OpCode::U32Clz => Ok(Instruction::U32Clz), + OpCode::U32Ctz => Ok(Instruction::U32Ctz), + OpCode::U32Clo => Ok(Instruction::U32Clo), + OpCode::U32Cto => Ok(Instruction::U32Cto), // ----- stack manipulation ----------------------------------------------------------- OpCode::Drop => Ok(Instruction::Drop), diff --git a/assembly/src/ast/nodes/serde/mod.rs b/assembly/src/ast/nodes/serde/mod.rs index dfab410523..94713263dd 100644 --- a/assembly/src/ast/nodes/serde/mod.rs +++ b/assembly/src/ast/nodes/serde/mod.rs @@ -37,228 +37,233 @@ 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, + U32Lt = 89, + U32Lte = 90, + U32Gt = 91, + U32Gte = 92, + U32Min = 93, + U32Max = 94, + U32Clz = 95, + U32Ctz = 96, + U32Clo = 97, + U32Cto = 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, + FriExt2Fold4 = 214, // ----- exec / call -------------------------------------------------------------------------- - ExecLocal = 210, - ExecImported = 211, - CallLocal = 212, - CallMastRoot = 213, - CallImported = 214, - SysCall = 215, - DynExec = 216, - DynCall = 217, - ProcRefLocal = 218, - ProcRefImported = 219, + ExecLocal = 215, + ExecImported = 216, + CallLocal = 217, + CallMastRoot = 218, + CallImported = 219, + SysCall = 220, + DynExec = 221, + DynCall = 222, + ProcRefLocal = 223, + ProcRefImported = 224, // ----- debugging ---------------------------------------------------------------------------- - Debug = 220, + Debug = 225, // ----- emit -------------------------------------------------------------------------------- - Emit = 221, + Emit = 226, // ----- control flow ------------------------------------------------------------------------- IfElse = 253, diff --git a/assembly/src/ast/nodes/serde/serialization.rs b/assembly/src/ast/nodes/serde/serialization.rs index 5f75686ec2..5e2ef4351a 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), @@ -234,6 +235,10 @@ impl Serializable for Instruction { Self::U32Gte => OpCode::U32Gte.write_into(target), Self::U32Min => OpCode::U32Min.write_into(target), Self::U32Max => OpCode::U32Max.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), // ----- stack manipulation --------------------------------------------------------------- Self::Drop => OpCode::Drop.write_into(target), diff --git a/assembly/src/ast/parsers/context.rs b/assembly/src/ast/parsers/context.rs index 8437e84200..4f8e202116 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), @@ -552,6 +553,11 @@ impl ParserContext<'_> { "u32min" => simple_instruction(op, U32Min), "u32max" => simple_instruction(op, U32Max), + "u32clz" => simple_instruction(op, U32Clz), + "u32ctz" => simple_instruction(op, U32Ctz), + "u32clo" => simple_instruction(op, U32Clo), + "u32cto" => simple_instruction(op, U32Cto), + // ----- stack manipulation ----------------------------------------------------------- "drop" => simple_instruction(op, Drop), "dropw" => simple_instruction(op, DropW), diff --git a/assembly/src/ast/tests.rs b/assembly/src/ast/tests.rs index c1efc19bf6..a163f8b8e7 100644 --- a/assembly/src/ast/tests.rs +++ b/assembly/src/ast/tests.rs @@ -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 0a8011d4e5..de841cc6b1 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/operations/decorators/advice.rs @@ -218,6 +218,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, ...] + Clz, + + /// 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, ...] + Ctz, + + /// 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, ...] + Clo, + + /// 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, ...] + Cto, + + /// 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 @@ -304,6 +358,11 @@ impl fmt::Display for AdviceInjector { Self::SmtGet => write!(f, "smt_get"), Self::SmtSet => write!(f, "smt_set"), Self::SmtPeek => write!(f, "smt_peek"), + Self::Clz => write!(f, "u32clz"), + Self::Ctz => write!(f, "u32ctz"), + Self::Clo => write!(f, "u32clo"), + Self::Cto => 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 a0ca6c7b00..02b32df52a 100644 --- a/docs/src/user_docs/assembly/field_operations.md +++ b/docs/src/user_docs/assembly/field_operations.md @@ -33,6 +33,7 @@ If the error code is omitted, the default value of $0$ is assumed. | 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 45657e29e6..19ed8cb2b1 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
- *(27 cycles)* | [a, ...] | [b, ...] | Computes $b$ as a number of leading zeros of $a$.
Undefined if $a \ge 2^{32}$ | +| u32ctz
- *(27 cycles)* | [a, ...] | [b, ...] | Computes $b$ as a number of trailing zeros of $a$.
Undefined if $a \ge 2^{32}$ | +| u32clo
- *(32 cycles)* | [a, ...] | [b, ...] | Computes $b$ as a number of leading ones of $a$.
Undefined if $a \ge 2^{32}$ | +| u32cto
- *(2 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 ed4e913fc5..6916124fa2 100644 --- a/miden/tests/integration/operations/field_ops.rs +++ b/miden/tests/integration/operations/field_ops.rs @@ -333,6 +333,22 @@ 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("LogArgumentZero")); +} + // FIELD OPS BOOLEAN - MANUAL TESTS // ================================================================================================ @@ -639,7 +655,8 @@ 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] @@ -653,7 +670,7 @@ proptest! { let expected = Felt::new(base).exp(pow); let test = build_op_test!(asm_op, &[base, pow]); - test.expect_stack(&[expected.as_int()]); + test.prop_expect_stack(&[expected.as_int()])?; //----------------------- exp with parameter containing pow ---------------- @@ -663,8 +680,17 @@ proptest! { 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 e75be1047b..2e0843b23b 100644 --- a/miden/tests/integration/operations/u32_ops/bitwise_ops.rs +++ b/miden/tests/integration/operations/u32_ops/bitwise_ops.rs @@ -420,6 +420,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 14703 is 00000000000000000011100101101111 + build_op_test!(asm_op, &[14703]).expect_stack(&[18]); + 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 4185032763 is 11111001011100101000100000111011 + build_op_test!(asm_op, &[4185032763]).expect_stack(&[2]); + build_op_test!(asm_op, &[4294967295]).expect_stack(&[32]); +} + // U32 OPERATIONS TESTS - RANDOMIZED - BITWISE OPERATIONS // ================================================================================================ @@ -531,4 +571,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 5c817ba0bd..d6f2712beb 100644 --- a/processor/src/errors.rs +++ b/processor/src/errors.rs @@ -23,12 +23,13 @@ pub enum ExecutionError { AdviceStackReadFailed(u32), CallerNotInSyscall, CodeBlockNotFound(Digest), - DynamicCodeBlockNotFound(Digest), CycleLimitExceeded(u32), DivideByZero(u32), + DynamicCodeBlockNotFound(Digest), EventError(String), Ext2InttError(Ext2InttError), FailedAssertion(u32, Felt), + FailedSignatureGeneration(&'static str), InvalidFmpValue(Felt, Felt), InvalidFriDomainSegment(u64), InvalidFriLayerFolding(QuadFelt, QuadFelt), @@ -37,17 +38,17 @@ pub enum ExecutionError { InvalidStackWordOffset(usize), InvalidTreeDepth { depth: Felt }, InvalidTreeNodeIndex { depth: Felt, value: Felt }, + LogArgumentZero(u32), + MalformedSignatureKey(&'static str), MemoryAddressOutOfBounds(u64), - MerkleStoreMergeFailed(MerkleError), MerkleStoreLookupFailed(MerkleError), + MerkleStoreMergeFailed(MerkleError), MerkleStoreUpdateFailed(MerkleError), NotBinaryValue(Felt), NotU32Value(Felt, Felt), ProverError(ProverError), SyscallTargetNotInKernel(Digest), UnexecutableCodeBlock(CodeBlock), - MalformedSignatureKey(&'static str), - FailedSignatureGeneration(&'static str), } impl Display for ExecutionError { @@ -77,6 +78,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!( @@ -84,15 +89,14 @@ 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(clk, err_code) => { 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}") } @@ -120,6 +124,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}") } @@ -149,10 +160,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 6804a71595..95542df5e4 100644 --- a/processor/src/host/advice/injectors/adv_stack_injectors.rs +++ b/processor/src/host/advice/injectors/adv_stack_injectors.rs @@ -296,6 +296,107 @@ 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 { + let n = process.get_stack_item(0).as_int() as u32; + let leading_zeros = Felt::new(n.leading_zeros().into()); + advice_provider.push_stack(AdviceSource::Value(leading_zeros))?; + Ok(HostResponse::None) +} + +/// 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 { + let n = process.get_stack_item(0).as_int() as u32; + let trailing_zeros = Felt::new(n.trailing_zeros().into()); + advice_provider.push_stack(AdviceSource::Value(trailing_zeros))?; + Ok(HostResponse::None) +} + +/// 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 { + let n = process.get_stack_item(0).as_int() as u32; + let leading_ones = Felt::new(n.leading_ones().into()); + advice_provider.push_stack(AdviceSource::Value(leading_ones))?; + Ok(HostResponse::None) +} + +/// 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 { + let n = process.get_stack_item(0).as_int() as u32; + let trailing_ones = Felt::new(n.trailing_ones().into()); + advice_provider.push_stack(AdviceSource::Value(trailing_ones))?; + Ok(HostResponse::None) +} + +/// 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 log_val = n.ilog2(); + let ilog2 = Felt::new(log_val.into()); + advice_provider.push_stack(AdviceSource::Value(ilog2))?; + Ok(HostResponse::None) +} + // HELPER FUNCTIONS // ================================================================================================ diff --git a/processor/src/host/advice/mod.rs b/processor/src/host/advice/mod.rs index f7bb0e2579..f8fd415ec1 100644 --- a/processor/src/host/advice/mod.rs +++ b/processor/src/host/advice/mod.rs @@ -69,6 +69,12 @@ pub trait AdviceProvider: Sized { AdviceInjector::SmtGet => self.push_smtget_inputs(process), AdviceInjector::SmtSet => self.push_smtset_inputs(process), AdviceInjector::SmtPeek => self.push_smtpeek_result(process), + AdviceInjector::Clz => self.push_leading_zeros(process), + AdviceInjector::Ctz => self.push_trailing_zeros(process), + AdviceInjector::Clo => self.push_leading_ones(process), + AdviceInjector::Cto => 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 // --------------------------------------------------------------------------------------------