core_models/abstractions/
bitvec.rs1use super::bit::{Bit, MachineInteger};
3use super::funarr::*;
4
5use std::fmt::Formatter;
6
7#[cfg(hax)]
9use hax_lib::{int, ToInt};
10
11#[hax_lib::fstar::before("noeq")]
24#[derive(Copy, Clone, Eq, PartialEq)]
25pub struct BitVec<const N: u64>(FunArray<N, Bit>);
26
27#[hax_lib::exclude]
29fn bit_slice_to_string(bits: &[Bit]) -> String {
30 bits.iter()
31 .map(|bit| match bit {
32 Bit::Zero => '0',
33 Bit::One => '1',
34 })
35 .collect::<Vec<_>>()
36 .chunks(8)
37 .map(|bits| bits.iter().collect::<String>())
38 .map(|s| format!("{s} "))
39 .collect::<String>()
40 .trim()
41 .into()
42}
43
44#[hax_lib::exclude]
45impl<const N: u64> core::fmt::Debug for BitVec<N> {
46 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
47 write!(f, "{}", bit_slice_to_string(&self.0.as_vec()))
48 }
49}
50
51#[hax_lib::attributes]
52impl<const N: u64> core::ops::Index<u64> for BitVec<N> {
53 type Output = Bit;
54 #[requires(index < N)]
55 fn index(&self, index: u64) -> &Self::Output {
56 self.0.get(index)
57 }
58}
59
60#[hax_lib::exclude]
62fn u128_int_from_bit_slice(bits: &[Bit]) -> u128 {
63 bits.iter()
64 .enumerate()
65 .map(|(i, bit)| u128::from(*bit) << i)
66 .sum::<u128>()
67}
68
69#[hax_lib::exclude]
71fn int_from_bit_slice<T: TryFrom<i128> + MachineInteger + Copy>(bits: &[Bit]) -> T {
72 debug_assert!(bits.len() <= T::bits() as usize);
73 let result = if T::SIGNED {
74 let is_negative = matches!(bits[T::bits() as usize - 1], Bit::One);
75 let s = u128_int_from_bit_slice(&bits[0..T::bits() as usize - 1]) as i128;
76 if is_negative {
77 s + (-2i128).pow(T::bits() - 1)
78 } else {
79 s
80 }
81 } else {
82 u128_int_from_bit_slice(bits) as i128
83 };
84 let Ok(n) = result.try_into() else {
85 unreachable!()
87 };
88 n
89}
90
91#[hax_lib::fstar::replace(
92 r#"
93let ${BitVec::<0>::from_fn::<fn(u64)->Bit>}
94 (v_N: u64)
95 (#_v_F: Type0)
96 (f: (i: u64 {v i < v v_N}) -> $:{Bit})
97 : t_BitVec v_N =
98 ${BitVec::<0>}(${FunArray::<0,()>::from_fn::<fn(u64)->()>} v_N #$:{Bit} #(u64 -> $:{Bit}) f)
99"#
100)]
101const _: () = ();
102
103macro_rules! impl_pointwise {
104 ($n:literal, $($i:literal)*) => {
105 impl BitVec<$n> {
106 pub fn pointwise(self) -> Self {
107 Self::from_fn(|i| match i {
108 $($i => self[$i],)*
109 _ => unreachable!(),
110 })
111 }
112 }
113 };
114}
115
116impl_pointwise!(128, 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127);
117impl_pointwise!(256, 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255);
118
119pub const REWRITE_RULE: () = {};
121
122#[hax_lib::exclude]
123impl<const N: u64> BitVec<N> {
124 pub fn from_fn<F: Fn(u64) -> Bit>(f: F) -> Self {
126 Self(FunArray::from_fn(f))
127 }
128 pub fn from_slice<T: Into<i128> + MachineInteger + Copy>(x: &[T], d: u64) -> Self {
130 Self::from_fn(|i| Bit::of_int::<T>(x[(i / d) as usize], (i % d) as u32))
131 }
132
133 pub fn from_int<T: Into<i128> + MachineInteger + Copy>(n: T) -> Self {
135 Self::from_slice::<T>(&[n], T::bits() as u64)
136 }
137
138 pub fn to_int<T: TryFrom<i128> + MachineInteger + Copy>(self) -> T {
140 int_from_bit_slice(&self.0.as_vec())
141 }
142
143 pub fn to_vec<T: TryFrom<i128> + MachineInteger + Copy>(&self) -> Vec<T> {
145 self.0
146 .as_vec()
147 .chunks(T::bits() as usize)
148 .map(int_from_bit_slice)
149 .collect()
150 }
151
152 pub fn rand() -> Self {
154 use rand::prelude::*;
155 let random_source: Vec<_> = {
156 let mut rng = rand::rng();
157 (0..N).map(|_| rng.random::<bool>()).collect()
158 };
159 Self::from_fn(|i| random_source[i as usize].into())
160 }
161}
162
163#[hax_lib::fstar::replace(
164 r#"
165open FStar.FunctionalExtensionality
166
167let extensionality' (#a: Type) (#b: Type) (f g: FStar.FunctionalExtensionality.(a ^-> b))
168 : Lemma (ensures (FStar.FunctionalExtensionality.feq f g <==> f == g))
169 = ()
170
171let mark_to_normalize #t (x: t): t = x
172
173open FStar.Tactics.V2
174#push-options "--z3rlimit 80 --admit_smt_queries true"
175let bitvec_rewrite_lemma_128 (x: $:{BitVec<128>})
176: Lemma (x == mark_to_normalize (${BitVec::<128>::pointwise} x)) =
177 let a = x._0._0 in
178 let b = (${BitVec::<128>::pointwise} x)._0._0 in
179 assert_norm (FStar.FunctionalExtensionality.feq a b);
180 extensionality' a b
181
182let bitvec_rewrite_lemma_256 (x: $:{BitVec<256>})
183: Lemma (x == mark_to_normalize (${BitVec::<256>::pointwise} x)) =
184 let a = x._0._0 in
185 let b = (${BitVec::<256>::pointwise} x)._0._0 in
186 assert_norm (FStar.FunctionalExtensionality.feq a b);
187 extensionality' a b
188#pop-options
189
190let bitvec_postprocess_norm_aux (): Tac unit = with_compat_pre_core 1 (fun () ->
191 let debug_mode = ext_enabled "debug_bv_postprocess_rewrite" in
192 let crate = match cur_module () with | crate::_ -> crate | _ -> fail "Empty module name" in
193 // Remove indirections
194 norm [primops; iota; delta_namespace [crate; "Libcrux_intrinsics"]; zeta_full];
195 // Rewrite call chains
196 let lemmas = FStar.List.Tot.map (fun f -> pack_ln (FStar.Stubs.Reflection.V2.Data.Tv_FVar f)) (lookup_attr (`${REWRITE_RULE}) (top_env ())) in
197 l_to_r lemmas;
198 /// Get rid of casts
199 norm [primops; iota; delta_namespace ["Rust_primitives"; "Prims.pow2"]; zeta_full];
200 if debug_mode then print ("[postprocess_rewrite_helper] lemmas = " ^ term_to_string (quote lemmas));
201
202 l_to_r [`bitvec_rewrite_lemma_128; `bitvec_rewrite_lemma_256];
203
204 let round _: Tac unit =
205 if debug_mode then dump "[postprocess_rewrite_helper] Rewrote goal";
206 // Normalize as much as possible
207 norm [primops; iota; delta_namespace ["Core"; crate; "Core_models"; "Libcrux_intrinsics"; "FStar.FunctionalExtensionality"; "Rust_primitives"]; zeta_full];
208 if debug_mode then print ("[postprocess_rewrite_helper] first norm done");
209 // Compute the last bits
210 // compute ();
211 // if debug_mode then dump ("[postprocess_rewrite_helper] compute done");
212 // Force full normalization
213 norm [primops; iota; delta; unascribe; zeta_full];
214 if debug_mode then dump "[postprocess_rewrite_helper] after full normalization";
215 // Solves the goal `<normalized body> == ?u`
216 trefl ()
217 in
218
219 ctrl_rewrite BottomUp (fun t ->
220 let f, args = collect_app t in
221 let matches = match inspect f with | Tv_UInst f _ | Tv_FVar f -> (inspect_fv f) = explode_qn (`%mark_to_normalize) | _ -> false in
222 let has_two_args = match args with | [_; _] -> true | _ -> false in
223 (matches && has_two_args, Continue)
224 ) round;
225
226 // Solves the goal `<normalized body> == ?u`
227 trefl ()
228)
229
230let ${bitvec_postprocess_norm} (): Tac unit =
231 if lax_on ()
232 then trefl () // don't bother rewritting the goal
233 else bitvec_postprocess_norm_aux ()
234"#
235)]
236pub fn bitvec_postprocess_norm() {}
242
243#[hax_lib::attributes]
244impl<const N: u64> BitVec<N> {
245 #[hax_lib::requires(CHUNK > 0 && CHUNK.to_int() * SHIFTS.to_int() == N.to_int())]
246 pub fn chunked_shift<const CHUNK: u64, const SHIFTS: u64>(
247 self,
248 shl: FunArray<SHIFTS, i128>,
249 ) -> BitVec<N> {
250 #[hax_lib::fstar::options("--z3rlimit 50 --split_queries always")]
252 #[hax_lib::requires(CHUNK > 0 && CHUNK.to_int() * SHIFTS.to_int() == N.to_int())]
253 fn chunked_shift<const N: u64, const CHUNK: u64, const SHIFTS: u64>(
254 bitvec: BitVec<N>,
255 shl: FunArray<SHIFTS, i128>,
256 ) -> BitVec<N> {
257 BitVec::from_fn(|i| {
258 let nth_bit = i % CHUNK;
259 let nth_chunk = i / CHUNK;
260 hax_lib::assert_prop!(nth_chunk.to_int() <= SHIFTS.to_int() - int!(1));
261 hax_lib::assert_prop!(
262 nth_chunk.to_int() * CHUNK.to_int()
263 <= (SHIFTS.to_int() - int!(1)) * CHUNK.to_int()
264 );
265 let shift: i128 = if nth_chunk < SHIFTS {
266 shl[nth_chunk]
267 } else {
268 0
269 };
270 let local_index = (nth_bit as i128).wrapping_sub(shift);
271 if local_index < CHUNK as i128 && local_index >= 0 {
272 let local_index = local_index as u64;
273 hax_lib::assert_prop!(
274 nth_chunk.to_int() * CHUNK.to_int() + local_index.to_int()
275 < SHIFTS.to_int() * CHUNK.to_int()
276 );
277 bitvec[nth_chunk * CHUNK + local_index]
278 } else {
279 Bit::Zero
280 }
281 })
282 }
283 chunked_shift::<N, CHUNK, SHIFTS>(self, shl)
284 }
285
286 pub fn fold<A>(&self, init: A, f: fn(A, Bit) -> A) -> A {
292 self.0.fold(init, f)
293 }
294}
295
296pub mod int_vec_interp {
297 use super::*;
299
300 #[allow(dead_code)]
302 #[hax_lib::fstar::before("irreducible")]
303 pub const SIMPLIFICATION_LEMMA: () = ();
304
305 macro_rules! interpretations {
308 ($n:literal; $($name:ident [$ty:ty; $m:literal]),*) => {
309 $(
310 #[doc = concat!(stringify!($ty), " vectors of size ", stringify!($m))]
311 #[allow(non_camel_case_types)]
312 pub type $name = FunArray<$m, $ty>;
313 pastey::paste! {
314 const _: () = {
315 #[hax_lib::opaque]
316 impl BitVec<$n> {
317 #[doc = concat!("Conversion from ", stringify!($ty), " vectors of size ", stringify!($m), "to bit vectors of size ", stringify!($n))]
318 pub fn [< from_ $name >](iv: $name) -> BitVec<$n> {
319 let vec: Vec<$ty> = iv.as_vec();
320 Self::from_slice(&vec[..], <$ty>::bits() as u64)
321 }
322 #[doc = concat!("Conversion from bit vectors of size ", stringify!($n), " to ", stringify!($ty), " vectors of size ", stringify!($m))]
323 pub fn [< to_ $name >](bv: BitVec<$n>) -> $name {
324 let vec: Vec<$ty> = bv.to_vec();
325 $name::from_fn(|i| vec[i as usize])
326 }
327 }
328
329 #[cfg(test)]
330 impl From<BitVec<$n>> for $name {
331 fn from(bv: BitVec<$n>) -> Self {
332 BitVec::[< to_ $name >](bv)
333 }
334 }
335 #[cfg(test)]
336 impl From<$name> for BitVec<$n> {
337 fn from(iv: $name) -> Self {
338 BitVec::[< from_ $name >](iv)
339 }
340 }
341 };
342 }
343 )*
344 };
345 }
346
347 interpretations!(256; i32x8 [i32; 8], i64x4 [i64; 4], i16x16 [i16; 16], i128x2 [i128; 2], i8x32 [i8; 32],
354 u32x8 [u32; 8], u64x4 [u64; 4], u16x16 [u16; 16]);
355 interpretations!(128; i32x4 [i32; 4], i64x2 [i64; 2], i16x8 [i16; 8], i128x1 [i128; 1], i8x16 [i8; 16],
356 u32x4 [u32; 4], u64x2 [u64; 2], u16x8 [u16; 8]);
357
358 impl i64x4 {
359 pub fn into_i32x8(self) -> i32x8 {
360 i32x8::from_fn(|i| {
361 let value = *self.get(i / 2);
362 (if i % 2 == 0 { value } else { value >> 32 }) as i32
363 })
364 }
365 }
366
367 impl i32x8 {
368 pub fn into_i64x4(self) -> i64x4 {
369 i64x4::from_fn(|i| {
370 let low = *self.get(2 * i) as u32 as u64;
371 let high = *self.get(2 * i + 1) as i32 as i64;
372 (high << 32) | low as i64
373 })
374 }
375 }
376
377 impl From<i64x4> for i32x8 {
378 fn from(vec: i64x4) -> Self {
379 vec.into_i32x8()
380 }
381 }
382
383 #[hax_lib::fstar::before("[@@ $SIMPLIFICATION_LEMMA ]")]
386 #[hax_lib::opaque]
387 #[hax_lib::lemma]
388 pub fn lemma_rewrite_i64x4_bv_i32x8(
389 bv: i64x4,
390 ) -> Proof<{ hax_lib::eq(BitVec::to_i32x8(BitVec::from_i64x4(bv)), bv.into_i32x8()) }> {
391 }
392
393 #[hax_lib::fstar::before("[@@ $SIMPLIFICATION_LEMMA ]")]
396 #[hax_lib::opaque]
397 #[hax_lib::lemma]
398 pub fn lemma_rewrite_i32x8_bv_i64x4(
399 bv: i32x8,
400 ) -> Proof<{ hax_lib::eq(BitVec::to_i64x4(BitVec::from_i32x8(bv)), bv.into_i64x4()) }> {
401 }
402
403 #[hax_lib::fstar::replace(
405 r#"
406 [@@ $SIMPLIFICATION_LEMMA ]
407 let lemma (t: Type) (i: Core.Convert.t_From t t) (x: t)
408 : Lemma (Core.Convert.f_from #t #t #i x == (norm [primops; iota; delta; zeta] i.f_from) x)
409 = ()
410 "#
411 )]
412 const _: () = ();
413
414 #[cfg(test)]
415 mod direct_convertions_tests {
416 use super::*;
417 use crate::helpers::test::HasRandom;
418
419 #[test]
420 fn into_i32x8() {
421 for _ in 0..10000 {
422 let x: i64x4 = i64x4::random();
423 let y = x.into_i32x8();
424 assert_eq!(BitVec::from_i64x4(x), BitVec::from_i32x8(y));
425 }
426 }
427 #[test]
428 fn into_i64x4() {
429 let x: i32x8 = i32x8::random();
430 let y = x.into_i64x4();
431 assert_eq!(BitVec::from_i32x8(x), BitVec::from_i64x4(y));
432 }
433 }
434}