tasm_lib/arithmetic/u64/
log_2_floor.rs

1use 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/// The base 2 logarithm of the input, rounded down. See also: [u64::ilog2].
10///
11/// ### Behavior
12///
13/// ```text
14/// BEFORE: _ [x: u64]
15/// AFTER:  _ [y: u32]
16/// ```
17///
18/// ### Preconditions
19///
20/// - the input `x` is properly [`BFieldCodec`] encoded
21/// - the input `x` is not 0
22///
23/// ### Postconditions
24///
25/// - `y` is the [base-2 integer logarithm](u64::ilog2) of `x`
26/// - `y` is properly [`BFieldCodec`] encoded
27#[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            // BEFORE: _ x_hi x_lo
50            // AFTER:  _ log2_floor(x)
51            {entrypoint}:
52                push 1
53                dup 2
54                // _ x_hi x_lo 1 x_hi
55
56                skiz call {hi_neq_zero}
57                skiz call {hi_eq_zero}
58                // _ log2_floor(x)
59
60                return
61
62            {hi_neq_zero}:
63                // x_hi != 0
64                // _ x_hi x_lo 1
65
66                pop 1
67                // _ x_hi x_lo
68
69                /* assert valid encoding */
70                pop_count
71                pop 1
72                // _ x_hi
73
74                log_2_floor
75                addi 32
76                // _ (log2_floor(x_hi) + 32)
77
78                push 0
79                // _ (log2_floor(x_hi) + 32) 0
80
81                return
82
83            {hi_eq_zero}:
84                // x_hi == 0
85                // _ 0 x_lo
86                pick 1
87                pop 1
88                log_2_floor
89                // _ log_2_floor(x_lo)
90
91                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        // many of the following are already covered by the edge cases declared in
211        // “corner_case_initial_states” but repeated here as a sanity check
212        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}