From 42c0a7d3a13a400de3a630327230d2cf0d0db278 Mon Sep 17 00:00:00 2001 From: whichqua Date: Thu, 19 Dec 2024 16:49:10 +0300 Subject: [PATCH 1/3] feat: add the log2_ceil hint --- crates/starknet-os/src/hints/math.rs | 119 +++++++++++++++++++++++++++ crates/starknet-os/src/hints/mod.rs | 2 + 2 files changed, 121 insertions(+) create mode 100644 crates/starknet-os/src/hints/math.rs diff --git a/crates/starknet-os/src/hints/math.rs b/crates/starknet-os/src/hints/math.rs new file mode 100644 index 00000000..9108e7f4 --- /dev/null +++ b/crates/starknet-os/src/hints/math.rs @@ -0,0 +1,119 @@ +use std::collections::HashMap; + +use cairo_vm::hint_processor::builtin_hint_processor::hint_utils::{ + get_integer_from_var_name, insert_value_from_var_name, +}; +use cairo_vm::hint_processor::hint_processor_definition::HintReference; +use cairo_vm::serde::deserialize_program::ApTracking; +use cairo_vm::types::exec_scope::ExecutionScopes; +use cairo_vm::vm::errors::hint_errors::HintError; +use cairo_vm::vm::vm_core::VirtualMachine; +use cairo_vm::Felt252; +use indoc::indoc; +use num_bigint::BigUint; +use num_traits::{One, Zero}; + +use crate::hints::vars; + +pub const LOG2_CEIL: &str = indoc! {r#" + from starkware.python.math_utils import log2_ceil + ids.res = log2_ceil(ids.value)"# +}; +pub fn log2_ceil( + vm: &mut VirtualMachine, + _exec_scopes: &mut ExecutionScopes, + ids_data: &HashMap, + ap_tracking: &ApTracking, + _constants: &HashMap, +) -> Result<(), HintError> { + let value = get_integer_from_var_name(vars::ids::VALUE, vm, ids_data, ap_tracking)?; + let res = log2_ceil_internal(&value.to_biguint()); + insert_value_from_var_name(vars::ids::RES, Felt252::from(res), vm, ids_data, ap_tracking)?; + Ok(()) +} + +fn log2_ceil_internal(value: &BigUint) -> u64 { + assert!(!value.is_zero(), "log2_ceil is not defined for zero."); + + // bits() returns the number of bits required to represent `value`, which equals floor(log2(value)) + 1. + let bits = value.bits(); + + // Check if value is a power of two. + // A power of two in binary looks like: 1000...0. Subtracting one gives: 0111...1 + // The AND of these two should be zero if it's truly a power of two. + let is_power_of_two = { + if value == &BigUint::one() { + true // 1 is a power of two (2^0). + } else { + let val_minus_one = value - BigUint::one(); + (value & val_minus_one).is_zero() + } + }; + + if is_power_of_two { + // If it's a power of two, log2_ceil(value) = floor_log2(value) = bits - 1. + bits - 1 + } else { + // Otherwise, log2_ceil(value) = floor_log2(value) + 1 = bits + bits + } +} + +#[cfg(test)] +mod tests { + use cairo_vm::types::relocatable::Relocatable; + use num_bigint::BigUint; + use rstest::rstest; + + use super::*; + + #[rstest] + // Powers of two + #[case(1, 0)] // 1 = 2^0 + #[case(2, 1)] // 2 = 2^1 + #[case(4, 2)] // 4 = 2^2 + #[case(8, 3)] // 8 = 2^3 + #[case(1024, 10)] // 1024 = 2^10 + + // Non-powers of two + #[case(3, 2)] // between 2 and 4, floor(log2(3))=1 => log2_ceil=2 + #[case(5, 3)] // between 4 and 8, floor(log2(5))=2 => log2_ceil=3 + #[case(6, 3)] // between 4 and 8, floor(log2(6))=2 => log2_ceil=3 + #[case(9, 4)] // between 8 and 16, floor(log2(9))=3 => log2_ceil=4 + fn test_log2_ceil_parameterized(#[case] value: u64, #[case] expected: u64) { + let val = BigUint::from(value); + assert_eq!(log2_ceil_internal(&val), expected); + test_log2_ceil_hint(value, expected); + } + + #[test] + #[should_panic(expected = "not defined for zero")] + fn test_log2_ceil_zero() { + let zero = BigUint::from(0u64); + log2_ceil_internal(&zero); + } + + fn test_log2_ceil_hint(value: u64, expected: u64) { + let mut vm = VirtualMachine::new(false); + vm.add_memory_segment(); + vm.add_memory_segment(); + vm.set_fp(2); + + let ap_tracking = ApTracking::new(); + let constants = HashMap::new(); + let ids_data = HashMap::from([ + (vars::ids::VALUE.to_string(), HintReference::new_simple(-2)), + (vars::ids::RES.to_string(), HintReference::new_simple(-1)), + ]); + + vm.insert_value(Relocatable::from((1, 0)), Felt252::from(value)).unwrap(); + + let mut exec_scopes: ExecutionScopes = Default::default(); + + log2_ceil(&mut vm, &mut exec_scopes, &ids_data, &ap_tracking, &constants).unwrap(); + + let exp = get_integer_from_var_name(vars::ids::RES, &vm, &ids_data, &ap_tracking).unwrap(); + + assert_eq!(exp, Felt252::from(expected)) + } +} diff --git a/crates/starknet-os/src/hints/mod.rs b/crates/starknet-os/src/hints/mod.rs index d8ac0dcc..15d4a8ff 100644 --- a/crates/starknet-os/src/hints/mod.rs +++ b/crates/starknet-os/src/hints/mod.rs @@ -44,6 +44,7 @@ mod os; mod output; mod patricia; mod secp; +mod math; pub mod state; pub mod syscalls; #[cfg(test)] @@ -252,6 +253,7 @@ fn hints() -> HashMap where hints.insert(compiled_class::SET_AP_TO_SEGMENT_HASH.into(), compiled_class::set_ap_to_segment_hash); hints.insert(secp::READ_EC_POINT_ADDRESS.into(), secp::read_ec_point_from_address); hints.insert(execute_transactions::SHA2_FINALIZE.into(), execute_transactions::sha2_finalize); + hints.insert(math::LOG2_CEIL.into(), math::log2_ceil); hints } From 11862fd9ff5fd738fca96ea3928b5b01bcacd6b9 Mon Sep 17 00:00:00 2001 From: ftheirs Date: Fri, 27 Dec 2024 10:15:03 -0300 Subject: [PATCH 2/3] simplify log2 function --- crates/starknet-os/src/hints/math.rs | 36 ++++++---------------------- crates/starknet-os/src/hints/mod.rs | 4 ++-- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/crates/starknet-os/src/hints/math.rs b/crates/starknet-os/src/hints/math.rs index 9108e7f4..565a4fd5 100644 --- a/crates/starknet-os/src/hints/math.rs +++ b/crates/starknet-os/src/hints/math.rs @@ -19,7 +19,7 @@ pub const LOG2_CEIL: &str = indoc! {r#" from starkware.python.math_utils import log2_ceil ids.res = log2_ceil(ids.value)"# }; -pub fn log2_ceil( +pub fn log2_ceil_hint( vm: &mut VirtualMachine, _exec_scopes: &mut ExecutionScopes, ids_data: &HashMap, @@ -27,36 +27,14 @@ pub fn log2_ceil( _constants: &HashMap, ) -> Result<(), HintError> { let value = get_integer_from_var_name(vars::ids::VALUE, vm, ids_data, ap_tracking)?; - let res = log2_ceil_internal(&value.to_biguint()); + let res = log2_ceil(&value.to_biguint()); insert_value_from_var_name(vars::ids::RES, Felt252::from(res), vm, ids_data, ap_tracking)?; Ok(()) } -fn log2_ceil_internal(value: &BigUint) -> u64 { +pub fn log2_ceil(value: &BigUint) -> u64 { assert!(!value.is_zero(), "log2_ceil is not defined for zero."); - - // bits() returns the number of bits required to represent `value`, which equals floor(log2(value)) + 1. - let bits = value.bits(); - - // Check if value is a power of two. - // A power of two in binary looks like: 1000...0. Subtracting one gives: 0111...1 - // The AND of these two should be zero if it's truly a power of two. - let is_power_of_two = { - if value == &BigUint::one() { - true // 1 is a power of two (2^0). - } else { - let val_minus_one = value - BigUint::one(); - (value & val_minus_one).is_zero() - } - }; - - if is_power_of_two { - // If it's a power of two, log2_ceil(value) = floor_log2(value) = bits - 1. - bits - 1 - } else { - // Otherwise, log2_ceil(value) = floor_log2(value) + 1 = bits - bits - } + (value - &BigUint::one()).bits() } #[cfg(test)] @@ -82,7 +60,7 @@ mod tests { #[case(9, 4)] // between 8 and 16, floor(log2(9))=3 => log2_ceil=4 fn test_log2_ceil_parameterized(#[case] value: u64, #[case] expected: u64) { let val = BigUint::from(value); - assert_eq!(log2_ceil_internal(&val), expected); + assert_eq!(log2_ceil(&val), expected); test_log2_ceil_hint(value, expected); } @@ -90,7 +68,7 @@ mod tests { #[should_panic(expected = "not defined for zero")] fn test_log2_ceil_zero() { let zero = BigUint::from(0u64); - log2_ceil_internal(&zero); + log2_ceil(&zero); } fn test_log2_ceil_hint(value: u64, expected: u64) { @@ -110,7 +88,7 @@ mod tests { let mut exec_scopes: ExecutionScopes = Default::default(); - log2_ceil(&mut vm, &mut exec_scopes, &ids_data, &ap_tracking, &constants).unwrap(); + log2_ceil_hint(&mut vm, &mut exec_scopes, &ids_data, &ap_tracking, &constants).unwrap(); let exp = get_integer_from_var_name(vars::ids::RES, &vm, &ids_data, &ap_tracking).unwrap(); diff --git a/crates/starknet-os/src/hints/mod.rs b/crates/starknet-os/src/hints/mod.rs index 15d4a8ff..afd5c0e8 100644 --- a/crates/starknet-os/src/hints/mod.rs +++ b/crates/starknet-os/src/hints/mod.rs @@ -40,11 +40,11 @@ mod execute_transactions; pub mod execution; mod find_element; mod kzg; +mod math; mod os; mod output; mod patricia; mod secp; -mod math; pub mod state; pub mod syscalls; #[cfg(test)] @@ -253,7 +253,7 @@ fn hints() -> HashMap where hints.insert(compiled_class::SET_AP_TO_SEGMENT_HASH.into(), compiled_class::set_ap_to_segment_hash); hints.insert(secp::READ_EC_POINT_ADDRESS.into(), secp::read_ec_point_from_address); hints.insert(execute_transactions::SHA2_FINALIZE.into(), execute_transactions::sha2_finalize); - hints.insert(math::LOG2_CEIL.into(), math::log2_ceil); + hints.insert(math::LOG2_CEIL.into(), math::log2_ceil_hint); hints } From 751fd78344dd9232475bd83887aca35a06e69398 Mon Sep 17 00:00:00 2001 From: ftheirs Date: Mon, 6 Jan 2025 09:21:03 -0300 Subject: [PATCH 3/3] clean up unimplemented.rs --- crates/starknet-os/src/hints/unimplemented.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/starknet-os/src/hints/unimplemented.rs b/crates/starknet-os/src/hints/unimplemented.rs index a55861d5..55ddb5f5 100644 --- a/crates/starknet-os/src/hints/unimplemented.rs +++ b/crates/starknet-os/src/hints/unimplemented.rs @@ -1,4 +1 @@ -use indoc::indoc; - -#[allow(unused)] -pub const HINT_4: &str = indoc! {r#"exit_syscall(selector=ids.SHA256_PROCESS_BLOCK_SELECTOR)"#}; +// This file is intended to list all the unimplemented hints during an OS upgrade