Skip to content

Commit

Permalink
feat: implement u32clz, u32ctz, u32clo, u32cto and ilog2 instrs
Browse files Browse the repository at this point in the history
  • Loading branch information
Fumuran committed Dec 8, 2023
1 parent ca7de61 commit 6d80dc6
Show file tree
Hide file tree
Showing 18 changed files with 811 additions and 250 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Introduced the `procref.<proc_name>` 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`
Expand Down
44 changes: 44 additions & 0 deletions assembly/src/assembler/instruction/field_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<Option<CodeBlock>, 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
// ================================================================================================

Expand Down
5 changes: 5 additions & 0 deletions assembly/src/assembler/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
174 changes: 138 additions & 36 deletions assembly/src/assembler/instruction/u32_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ================================================================================================
Expand Down Expand Up @@ -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<u32>,
) -> Result<Option<CodeBlock>, 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<u32>,
) -> Result<Option<CodeBlock>, 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
// ================================================================================================

Expand Down Expand Up @@ -310,48 +358,54 @@ pub fn u32popcnt(span: &mut SpanBuilder) -> Result<Option<CodeBlock>, 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<u32>,
) -> Result<Option<CodeBlock>, AssemblyError> {
if let Some(imm) = imm {
push_u32_value(span, imm);
}
/// This operation takes 27 VM cycles.
pub fn u32clz(span: &mut SpanBuilder) -> Result<Option<CodeBlock>, 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<Option<CodeBlock>, 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<u32>,
) -> Result<Option<CodeBlock>, 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<Option<CodeBlock>, 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<Option<CodeBlock>, 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
Expand Down Expand Up @@ -379,6 +433,54 @@ fn prepare_bitwise<const MAX_VALUE: u8>(
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<Option<CodeBlock>, 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<Option<CodeBlock>, 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
// ================================================================================================

Expand Down
10 changes: 10 additions & 0 deletions assembly/src/ast/nodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub enum Instruction {
Exp,
ExpImm(Felt),
ExpBitLength(u8),
ILog2,
Not,
And,
Or,
Expand Down Expand Up @@ -139,6 +140,10 @@ pub enum Instruction {
U32Gte,
U32Min,
U32Max,
U32Clz,
U32Ctz,
U32Clo,
U32Cto,

// ----- stack manipulation -------------------------------------------------------------------
Drop,
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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"),
Expand Down
5 changes: 5 additions & 0 deletions assembly/src/ast/nodes/serde/deserialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
Loading

0 comments on commit 6d80dc6

Please sign in to comment.