Skip to main content

tasm_lib/arithmetic/u64/
shift_left.rs

1use std::collections::HashMap;
2
3use triton_vm::prelude::*;
4
5use crate::arithmetic::u64::shift_right::ShiftRight;
6use crate::prelude::*;
7use crate::traits::basic_snippet::Reviewer;
8use crate::traits::basic_snippet::SignOffFingerprint;
9
10/// [Shift left][shl] for unsigned 64-bit integers.
11///
12/// # Behavior
13///
14/// ```text
15/// BEFORE: _ [arg: u64] shift_amount
16/// AFTER:  _ [result: u64]
17/// ```
18///
19/// # Preconditions
20///
21/// - input argument `arg` is properly [`BFieldCodec`] encoded
22/// - input argument `shift_amount` is in `0..64`
23///
24/// # Postconditions
25///
26/// - the output is the input argument `arg` bit-shifted to the left by
27///   input argument `shift_amount`
28/// - the output is properly [`BFieldCodec`] encoded
29///
30/// [shl]: core::ops::Shl
31#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
32pub struct ShiftLeft;
33
34impl ShiftLeft {
35    pub const SHIFT_AMOUNT_TOO_BIG_ERROR_ID: i128 = 370;
36}
37
38impl BasicSnippet for ShiftLeft {
39    fn parameters(&self) -> Vec<(DataType, String)> {
40        ShiftRight.parameters()
41    }
42
43    fn return_values(&self) -> Vec<(DataType, String)> {
44        ShiftRight.return_values()
45    }
46
47    fn entrypoint(&self) -> String {
48        "tasmlib_arithmetic_u64_shift_left".to_string()
49    }
50
51    fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> {
52        let entrypoint = self.entrypoint();
53        let handle_hi_shift = format!("{entrypoint}_handle_hi_shift");
54
55        triton_asm!(
56            // BEFORE: _ arg_hi arg_lo shift
57            // AFTER:  _ (arg<< shift)_hi (arg<< shift)_lo
58            {entrypoint}:
59                /* bounds check */
60                push 64
61                dup 1
62                lt
63                assert error_id {Self::SHIFT_AMOUNT_TOO_BIG_ERROR_ID}
64                // _ arg_hi arg_lo shift
65
66                /* special case: shift amount is greater than 32 */
67                dup 0
68                push 32
69                lt
70                // _ arg_hi arg_lo shift (shift > 32)
71
72                skiz call {handle_hi_shift}
73                // _ arg_hi arg_lo shift
74                // where 32 <= shift < 64
75
76                push 2
77                pow
78                // _ arg_hi arg_lo (2^shift)
79
80                pick 2
81                dup 1
82                mul
83                // _ arg_lo (2^shift) (arg_hi << shift)_bfe
84
85                split
86                place 3
87                pop 1
88                // _ (arg_hi << shift) arg_lo (2^shift)
89
90                mul
91                split
92                // _ (arg_hi << shift) carry (arg_lo << shift)
93
94                place 2
95                add
96                pick 1
97                // _ (arg << shift)_hi (arg_lo << shift)
98
99                return
100
101            // BEFORE: _ arg_hi arg_lo shift
102            // AFTER:  _ (arg<< 32)_hi (arg<< 32)_lo (shift - 32)
103            {handle_hi_shift}:
104                addi -32
105                // _ arg_hi arg_lo (shift - 32)
106
107                pick 2
108                pop 1
109                push 0
110                place 1
111                // _ arg_lo         0            (shift - 32)
112                // _ (arg<< 32)_hi (arg<< 32)_lo (shift - 32)
113
114                return
115        )
116    }
117
118    fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> {
119        let mut sign_offs = HashMap::new();
120        sign_offs.insert(Reviewer("ferdinand"), 0x4a03ab75c8546370.into());
121        sign_offs
122    }
123}
124
125#[cfg(test)]
126pub(crate) mod tests {
127    use super::*;
128    use crate::test_prelude::*;
129
130    impl ShiftLeft {
131        pub fn assert_expected_behavior(&self, shift_amount: u32, arg: u64) {
132            let initial_stack = self.set_up_test_stack((arg, shift_amount));
133
134            let mut expected_stack = initial_stack.clone();
135            self.rust_shadow(&mut expected_stack).unwrap();
136
137            test_rust_equivalence_given_complete_state(
138                &ShadowedClosure::new(Self),
139                &initial_stack,
140                &[],
141                &NonDeterminism::default(),
142                &None,
143                Some(&expected_stack),
144            );
145        }
146    }
147
148    impl Closure for ShiftLeft {
149        type Args = (u64, u32);
150
151        fn rust_shadow(&self, stack: &mut Vec<BFieldElement>) -> Result<(), RustShadowError> {
152            let (arg, shift_amount) = pop_encodable::<Self::Args>(stack)?;
153            if shift_amount >= 64 {
154                return Err(RustShadowError::ArithmeticOverflow);
155            }
156            push_encodable(stack, &(arg << shift_amount));
157            Ok(())
158        }
159
160        fn pseudorandom_args(
161            &self,
162            seed: [u8; 32],
163            bench_case: Option<BenchmarkCase>,
164        ) -> Self::Args {
165            let mut rng = StdRng::from_seed(seed);
166
167            match bench_case {
168                Some(BenchmarkCase::CommonCase) => (0x642, 15),
169                Some(BenchmarkCase::WorstCase) => (0x123456789abcdef, 33),
170                None => (rng.random(), rng.random_range(0..64)),
171            }
172        }
173
174        fn corner_case_args(&self) -> Vec<Self::Args> {
175            (0..64).map(|i| (1, i)).collect()
176        }
177    }
178
179    #[macro_rules_attr::apply(test)]
180    fn rust_shadow() {
181        ShadowedClosure::new(ShiftLeft).test()
182    }
183
184    #[macro_rules_attr::apply(test)]
185    fn edge_cases() {
186        for i in 0..64 {
187            ShiftLeft.assert_expected_behavior(i, u64::MAX);
188        }
189    }
190
191    #[macro_rules_attr::apply(proptest)]
192    fn property_test(arg: u64, #[strategy(0_u32..64)] shift_amount: u32) {
193        ShiftLeft.assert_expected_behavior(shift_amount, arg);
194    }
195
196    #[macro_rules_attr::apply(proptest)]
197    fn negative_property_test(arg: u64, #[strategy(64_u32..)] shift_amount: u32) {
198        test_assertion_failure(
199            &ShadowedClosure::new(ShiftLeft),
200            InitVmState::with_stack(ShiftLeft.set_up_test_stack((arg, shift_amount))),
201            &[ShiftLeft::SHIFT_AMOUNT_TOO_BIG_ERROR_ID],
202        );
203    }
204}
205
206#[cfg(test)]
207mod benches {
208    use super::*;
209    use crate::test_prelude::*;
210
211    #[macro_rules_attr::apply(test)]
212    fn benchmark() {
213        ShadowedClosure::new(ShiftLeft).bench();
214    }
215}