tasm_lib/arithmetic/u64/
log_2_floor.rs1use std::collections::HashMap;
2
3use triton_vm::prelude::*;
4
5use crate::prelude::*;
6use crate::traits::basic_snippet::Reviewer;
7use crate::traits::basic_snippet::SignOffFingerprint;
8
9#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
28pub struct Log2Floor;
29
30impl BasicSnippet for Log2Floor {
31 fn inputs(&self) -> Vec<(DataType, String)> {
32 vec![(DataType::U64, "x".to_string())]
33 }
34
35 fn outputs(&self) -> Vec<(DataType, String)> {
36 vec![(DataType::U32, "log_2_floor(x)".to_string())]
37 }
38
39 fn entrypoint(&self) -> String {
40 "tasmlib_arithmetic_u64_log_2_floor".to_string()
41 }
42
43 fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> {
44 let entrypoint = self.entrypoint();
45
46 let hi_neq_zero = format!("{entrypoint}_hi_neq_zero");
47 let hi_eq_zero = format!("{entrypoint}_hi_eq_zero");
48 triton_asm!(
49 {entrypoint}:
52 push 1
53 dup 2
54 skiz call {hi_neq_zero}
57 skiz call {hi_eq_zero}
58 return
61
62 {hi_neq_zero}:
63 pop 1
67 pop_count
71 pop 1
72 log_2_floor
75 addi 32
76 push 0
79 return
82
83 {hi_eq_zero}:
84 pick 1
87 pop 1
88 log_2_floor
89 return
92 )
93 }
94
95 fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> {
96 let mut sign_offs = HashMap::new();
97 sign_offs.insert(Reviewer("ferdinand"), 0x549a2ff4d3b45eda.into());
98 sign_offs
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::test_helpers::negative_test;
106 use crate::test_prelude::*;
107
108 impl Closure for Log2Floor {
109 type Args = u64;
110
111 fn rust_shadow(&self, stack: &mut Vec<BFieldElement>) {
112 let x = pop_encodable::<Self::Args>(stack);
113 push_encodable(stack, &x.ilog2());
114 }
115
116 fn pseudorandom_args(
117 &self,
118 seed: [u8; 32],
119 bench_case: Option<BenchmarkCase>,
120 ) -> Self::Args {
121 match bench_case {
122 Some(BenchmarkCase::CommonCase) => u64::from(u32::MAX),
123 Some(BenchmarkCase::WorstCase) => u64::MAX,
124 None => StdRng::from_seed(seed).random(),
125 }
126 }
127
128 fn corner_case_args(&self) -> Vec<Self::Args> {
129 (0..63)
130 .map(|pow| 1_u64 << pow)
131 .flat_map(|x| [x.checked_sub(1), Some(x), x.checked_add(1)])
132 .flatten()
133 .filter(|&x| x != 0)
134 .collect()
135 }
136 }
137
138 #[test]
139 fn rust_shadow_test() {
140 ShadowedClosure::new(Log2Floor).test();
141 }
142
143 #[proptest]
144 fn hi_is_u32_but_lo_is_not(
145 #[strategy(0_u32..)]
146 #[map(BFieldElement::from)]
147 x_hi: BFieldElement,
148 #[strategy(1_u64 << 32..)]
149 #[map(BFieldElement::new)]
150 x_lo: BFieldElement,
151 ) {
152 let mut init_stack = Log2Floor.init_stack_for_isolated_run();
153 init_stack.push(x_hi);
154 init_stack.push(x_lo);
155
156 let expected_err = InstructionError::OpStackError(OpStackError::FailedU32Conversion(x_lo));
157 negative_test(
158 &ShadowedClosure::new(Log2Floor),
159 InitVmState::with_stack(init_stack),
160 &[expected_err],
161 );
162 }
163
164 #[proptest]
165 fn hi_is_not_u32_but_lo_is(
166 #[strategy(1_u64 << 32..)]
167 #[map(BFieldElement::new)]
168 x_hi: BFieldElement,
169 #[strategy(0_u32..)]
170 #[map(BFieldElement::from)]
171 x_lo: BFieldElement,
172 ) {
173 let mut init_stack = Log2Floor.init_stack_for_isolated_run();
174 init_stack.push(x_hi);
175 init_stack.push(x_lo);
176
177 let expected_err = InstructionError::OpStackError(OpStackError::FailedU32Conversion(x_hi));
178 negative_test(
179 &ShadowedClosure::new(Log2Floor),
180 InitVmState::with_stack(init_stack),
181 &[expected_err],
182 );
183 }
184
185 #[test]
186 fn crash_on_zero() {
187 negative_test(
188 &ShadowedClosure::new(Log2Floor),
189 InitVmState::with_stack(Log2Floor.set_up_test_stack(0)),
190 &[InstructionError::LogarithmOfZero],
191 );
192 }
193
194 #[test]
195 fn unit_test() {
196 fn assert_terminal_stack_is_as_expected(x: u64, expected: u32) {
197 let mut expected_stack = Log2Floor.init_stack_for_isolated_run();
198 push_encodable(&mut expected_stack, &expected);
199
200 test_rust_equivalence_given_complete_state(
201 &ShadowedClosure::new(Log2Floor),
202 &Log2Floor.set_up_test_stack(x),
203 &[],
204 &NonDeterminism::default(),
205 &None,
206 Some(&expected_stack),
207 );
208 }
209
210 assert_terminal_stack_is_as_expected(1, 0);
213 assert_terminal_stack_is_as_expected(2, 1);
214 assert_terminal_stack_is_as_expected(3, 1);
215 assert_terminal_stack_is_as_expected(4, 2);
216 assert_terminal_stack_is_as_expected(5, 2);
217 assert_terminal_stack_is_as_expected(6, 2);
218 assert_terminal_stack_is_as_expected(7, 2);
219 assert_terminal_stack_is_as_expected(8, 3);
220 assert_terminal_stack_is_as_expected((1 << 32) - 20_000, 31);
221 assert_terminal_stack_is_as_expected((1 << 32) + 800, 32);
222 }
223}
224
225#[cfg(test)]
226mod benches {
227 use super::*;
228 use crate::test_prelude::*;
229
230 #[test]
231 fn benchmark() {
232 ShadowedClosure::new(Log2Floor).bench();
233 }
234}