|
1 | 1 | use std::collections::HashMap;
|
2 | 2 |
|
3 |
| -use rand::prelude::*; |
4 | 3 | use triton_vm::prelude::*;
|
5 | 4 |
|
6 |
| -use crate::empty_stack; |
| 5 | +use crate::arithmetic::u32::shift_right::ShiftRight; |
7 | 6 | use crate::prelude::*;
|
8 |
| -use crate::traits::deprecated_snippet::DeprecatedSnippet; |
9 |
| -use crate::InitVmState; |
10 |
| - |
11 |
| -#[derive(Clone, Debug)] |
| 7 | +use crate::traits::basic_snippet::Reviewer; |
| 8 | +use crate::traits::basic_snippet::SignOffFingerprint; |
| 9 | + |
| 10 | +/// [Shift left][shl] for unsigned 32-bit integers. |
| 11 | +/// |
| 12 | +/// # Behavior |
| 13 | +/// |
| 14 | +/// ```text |
| 15 | +/// BEFORE: _ [arg: u32] shift_amount |
| 16 | +/// AFTER: _ [result: u32] |
| 17 | +/// ``` |
| 18 | +/// |
| 19 | +/// # Preconditions |
| 20 | +/// |
| 21 | +/// - input argument `arg` is properly [`BFieldCodec`] encoded |
| 22 | +/// - input argument `shift_amount` is in `0..32` |
| 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, Default, Copy, Clone, Eq, PartialEq, Hash)] |
12 | 32 | pub struct ShiftLeft;
|
13 | 33 |
|
14 |
| -impl DeprecatedSnippet for ShiftLeft { |
15 |
| - fn entrypoint_name(&self) -> String { |
16 |
| - "tasmlib_arithmetic_u32_shift_left".to_string() |
17 |
| - } |
18 |
| - |
19 |
| - fn input_field_names(&self) -> Vec<String> { |
20 |
| - vec!["value".to_string(), "shift".to_string()] |
21 |
| - } |
22 |
| - |
23 |
| - fn input_types(&self) -> Vec<DataType> { |
24 |
| - vec![DataType::U32, DataType::U32] |
25 |
| - } |
| 34 | +impl ShiftLeft { |
| 35 | + pub const SHIFT_AMOUNT_TOO_BIG_ERROR_ID: i128 = 480; |
| 36 | +} |
26 | 37 |
|
27 |
| - fn output_field_names(&self) -> Vec<String> { |
28 |
| - vec!["value << shift".to_string()] |
| 38 | +impl BasicSnippet for ShiftLeft { |
| 39 | + fn inputs(&self) -> Vec<(DataType, String)> { |
| 40 | + ShiftRight.inputs() |
29 | 41 | }
|
30 | 42 |
|
31 |
| - fn output_types(&self) -> Vec<DataType> { |
32 |
| - vec![DataType::U32] |
| 43 | + fn outputs(&self) -> Vec<(DataType, String)> { |
| 44 | + ShiftRight.outputs() |
33 | 45 | }
|
34 | 46 |
|
35 |
| - fn stack_diff(&self) -> isize { |
36 |
| - -1 |
| 47 | + fn entrypoint(&self) -> String { |
| 48 | + "tasmlib_arithmetic_u32_shift_left".to_string() |
37 | 49 | }
|
38 | 50 |
|
39 |
| - fn function_code(&self, _library: &mut Library) -> String { |
40 |
| - let entrypoint = self.entrypoint_name(); |
41 |
| - |
42 |
| - // I'm unsure if we should do a bounds check to check if `shift < 32` |
43 |
| - format!( |
44 |
| - " |
| 51 | + fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> { |
| 52 | + triton_asm!( |
45 | 53 | // BEFORE: _ value shift
|
46 | 54 | // AFTER: _ (value << shift)
|
47 |
| - {entrypoint}: |
48 |
| - // Bounds check. May be superfluous but this mimics Rust's behavior. |
| 55 | + {self.entrypoint()}: |
| 56 | + /* bounds check mimics Rust's behavior */ |
49 | 57 | push 32
|
50 | 58 | dup 1
|
51 | 59 | lt
|
52 |
| - assert |
| 60 | + assert error_id {Self::SHIFT_AMOUNT_TOO_BIG_ERROR_ID} |
53 | 61 |
|
54 | 62 | push 2
|
55 | 63 | pow
|
56 | 64 | mul
|
57 | 65 | split
|
58 |
| - swap 1 |
| 66 | + pick 1 |
59 | 67 | pop 1
|
60 | 68 |
|
61 | 69 | return
|
62 |
| - " |
63 | 70 | )
|
64 | 71 | }
|
65 | 72 |
|
66 |
| - fn crash_conditions(&self) -> Vec<String> { |
67 |
| - vec![ |
68 |
| - "inputs are not valid u32s".to_string(), |
69 |
| - "attempting to left shift with a value greater than 31".to_string(), |
70 |
| - ] |
71 |
| - } |
72 |
| - |
73 |
| - fn gen_input_states(&self) -> Vec<InitVmState> { |
74 |
| - let mut rng = thread_rng(); |
75 |
| - let mut ret: Vec<InitVmState> = vec![]; |
76 |
| - for _ in 0..100 { |
77 |
| - let value = rng.next_u32(); |
78 |
| - let shift = rng.gen_range(0..32); |
79 |
| - ret.push(prepare_state(value, shift)); |
80 |
| - } |
81 |
| - |
82 |
| - ret |
83 |
| - } |
84 |
| - |
85 |
| - fn common_case_input_state(&self) -> InitVmState { |
86 |
| - prepare_state((1 << 16) - 1, 16) |
87 |
| - } |
88 |
| - |
89 |
| - fn worst_case_input_state(&self) -> InitVmState { |
90 |
| - prepare_state(u32::MAX, 31) |
91 |
| - } |
92 |
| - |
93 |
| - fn rust_shadowing( |
94 |
| - &self, |
95 |
| - stack: &mut Vec<BFieldElement>, |
96 |
| - _std_in: Vec<BFieldElement>, |
97 |
| - _secret_in: Vec<BFieldElement>, |
98 |
| - _memory: &mut HashMap<BFieldElement, BFieldElement>, |
99 |
| - ) { |
100 |
| - // Find shift amount |
101 |
| - let shift_amount: u32 = stack.pop().unwrap().try_into().unwrap(); |
102 |
| - |
103 |
| - // Original value |
104 |
| - let value: u32 = stack.pop().unwrap().try_into().unwrap(); |
105 |
| - |
106 |
| - let ret = value << shift_amount; |
107 |
| - stack.push((ret as u64).into()); |
| 73 | + fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> { |
| 74 | + let mut sign_offs = HashMap::new(); |
| 75 | + sign_offs.insert(Reviewer("ferdinand"), 0xec49012951c99eb1.into()); |
| 76 | + sign_offs |
108 | 77 | }
|
109 | 78 | }
|
110 | 79 |
|
111 |
| -fn prepare_state(value: u32, shift: u32) -> InitVmState { |
112 |
| - let mut stack = empty_stack(); |
113 |
| - let value = BFieldElement::new(value as u64); |
114 |
| - let shift = BFieldElement::new(shift as u64); |
115 |
| - stack.push(value); |
116 |
| - stack.push(shift); |
117 |
| - |
118 |
| - InitVmState::with_stack(stack) |
119 |
| -} |
120 |
| - |
121 | 80 | #[cfg(test)]
|
122 | 81 | mod tests {
|
123 | 82 | use super::*;
|
124 |
| - use crate::test_helpers::test_rust_equivalence_given_input_values_deprecated; |
125 |
| - use crate::test_helpers::test_rust_equivalence_multiple_deprecated; |
| 83 | + use crate::test_prelude::*; |
126 | 84 |
|
127 |
| - #[test] |
128 |
| - fn shift_left_test() { |
129 |
| - test_rust_equivalence_multiple_deprecated(&ShiftLeft, true); |
130 |
| - } |
| 85 | + impl Closure for ShiftLeft { |
| 86 | + type Args = (u32, u32); |
131 | 87 |
|
132 |
| - #[test] |
133 |
| - fn shift_left_max_value_test() { |
134 |
| - for i in 0..32 { |
135 |
| - prop_shift_left(u32::MAX, i); |
| 88 | + fn rust_shadow(&self, stack: &mut Vec<BFieldElement>) { |
| 89 | + let (arg, shift_amount) = pop_encodable::<Self::Args>(stack); |
| 90 | + assert!(shift_amount < 32); |
| 91 | + push_encodable(stack, &(arg << shift_amount)); |
| 92 | + } |
| 93 | + |
| 94 | + fn pseudorandom_args( |
| 95 | + &self, |
| 96 | + seed: [u8; 32], |
| 97 | + bench_case: Option<BenchmarkCase>, |
| 98 | + ) -> Self::Args { |
| 99 | + let mut rng = StdRng::from_seed(seed); |
| 100 | + match bench_case { |
| 101 | + Some(BenchmarkCase::CommonCase) => ((1 << 16) - 1, 16), |
| 102 | + Some(BenchmarkCase::WorstCase) => (u32::MAX, 31), |
| 103 | + None => (rng.gen(), rng.gen_range(0..32)), |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + fn corner_case_args(&self) -> Vec<Self::Args> { |
| 108 | + (0..32).map(|i| (u32::MAX, i)).collect() |
136 | 109 | }
|
137 | 110 | }
|
138 | 111 |
|
139 | 112 | #[test]
|
140 |
| - #[should_panic] |
141 |
| - fn shift_beyond_limit() { |
142 |
| - let mut init_stack = empty_stack(); |
143 |
| - init_stack.push(BFieldElement::new(u32::MAX as u64)); |
144 |
| - init_stack.push(32u64.into()); |
145 |
| - ShiftLeft.link_and_run_tasm_from_state_for_test(&mut InitVmState::with_stack(init_stack)); |
| 113 | + fn rust_shadow() { |
| 114 | + ShadowedClosure::new(ShiftLeft).test(); |
146 | 115 | }
|
147 | 116 |
|
148 |
| - fn prop_shift_left(value: u32, shift_amount: u32) { |
149 |
| - let mut init_stack = empty_stack(); |
150 |
| - init_stack.push(BFieldElement::new(value as u64)); |
151 |
| - init_stack.push(BFieldElement::new(shift_amount as u64)); |
152 |
| - |
153 |
| - let expected_u32 = value << shift_amount; |
154 |
| - |
155 |
| - let mut expected_stack = empty_stack(); |
156 |
| - expected_stack.push((expected_u32 as u64).into()); |
157 |
| - |
158 |
| - test_rust_equivalence_given_input_values_deprecated( |
159 |
| - &ShiftLeft, |
160 |
| - &init_stack, |
161 |
| - &[], |
162 |
| - HashMap::default(), |
163 |
| - Some(&expected_stack), |
164 |
| - ); |
| 117 | + #[proptest] |
| 118 | + fn too_big_shift_amount_crashes_vm(arg: u32, #[strategy(32_u32..)] shift_amount: u32) { |
| 119 | + test_assertion_failure( |
| 120 | + &ShadowedClosure::new(ShiftLeft), |
| 121 | + InitVmState::with_stack(ShiftLeft.set_up_test_stack((arg, shift_amount))), |
| 122 | + &[ShiftLeft::SHIFT_AMOUNT_TOO_BIG_ERROR_ID], |
| 123 | + ) |
165 | 124 | }
|
166 | 125 | }
|
167 | 126 |
|
168 | 127 | #[cfg(test)]
|
169 | 128 | mod benches {
|
170 | 129 | use super::*;
|
171 |
| - use crate::snippet_bencher::bench_and_write; |
| 130 | + use crate::test_prelude::*; |
172 | 131 |
|
173 | 132 | #[test]
|
174 |
| - fn shift_left_benchmark() { |
175 |
| - bench_and_write(ShiftLeft); |
| 133 | + fn benchmark() { |
| 134 | + ShadowedClosure::new(ShiftLeft).bench(); |
176 | 135 | }
|
177 | 136 | }
|
0 commit comments