wiwi/serialiser/binary/
int.rs

1#![allow(
2	unused_variables,
3	reason = "wip"
4)]
5use crate::prelude::*;
6use crate::num::*;
7use super::{ Serialise, Serialiser, Deserialise, Input, Output };
8
9#[repr(transparent)]
10struct IntSerialiser<Num, const BYTES: usize>
11where
12	Num: BytesWithinBounds<BYTES>
13{
14	num: Num
15}
16
17impl<Num, const BYTES: usize> Serialiser<'_> for IntSerialiser<Num, BYTES>
18where
19	Num: BytesWithinBounds<BYTES>
20{
21	#[inline]
22	fn serialise<O>(&self, out: O)
23	where
24		O: Output
25	{
26		let bytes = self.num.clone().into_le_bytes();
27
28		let repr = if Num::SIGNED {
29			// SAFETY: trait bound `BytesWithinBounds` ensures safety invariant
30			unsafe { get_repr_info_signed_le(bytes) }
31		} else {
32			// SAFETY: same as above
33			unsafe { get_repr_info_unsigned_le(bytes) }
34		};
35
36		// SAFETY: same as above again lol
37		unsafe { serialise_repr_and_marker(out, bytes, repr) }
38	}
39}
40
41/// # Safety
42///
43/// See safety comment on [`hint_bytes_valid`]
44#[inline]
45unsafe fn serialise_repr_and_marker<O, const BYTES: usize>(out: O, bytes: [u8; BYTES], repr: IntReprInfo)
46where
47	O: Output
48{
49	// SAFETY: caller promises safety preconditions are met
50	unsafe { hint_bytes_valid::<BYTES>() }
51
52	match repr {
53		IntReprInfo::Inline => {}
54		IntReprInfo::I8 => {}
55		IntReprInfo::I16 if BYTES >= 2 => {}
56		IntReprInfo::I24 if BYTES >= 3 => {}
57		IntReprInfo::I32 if BYTES >= 4 => {}
58		IntReprInfo::I48 if BYTES >= 6 => {}
59		IntReprInfo::I64 if BYTES >= 8 => {}
60		IntReprInfo::I96 if BYTES >= 12 => {}
61		IntReprInfo::BigintUnsigned(_len) => {}
62		IntReprInfo::BigintSigned(_len) => {}
63		_ => {
64			// SAFETY: we're using if guards to filter out branches that aren't
65			// applicable (and won't happen) for our value of `BYTES`
66			unsafe { hint::unreachable_unchecked() }
67		}
68	}
69
70	todo!()
71}
72
73/// Helper marker trait for number types that both have the necessary traits
74/// implemented, and are appropriately sized for, usage with [`IntSerialiser`]
75///
76/// # Safety
77///
78/// `BYTES` must be greater than 0, and less than or equal to 256.
79unsafe trait BytesWithinBounds<const BYTES: usize>
80where
81	Self: ArrayConversions<BYTES> + Clone + IntSignedness
82{}
83
84// SAFETY: size is within bounds
85unsafe impl BytesWithinBounds<1> for u8 {}
86
87// SAFETY: size is within bounds
88unsafe impl BytesWithinBounds<2> for u16 {}
89
90// SAFETY: size is within bounds
91unsafe impl BytesWithinBounds<4> for u32 {}
92
93// SAFETY: size is within bounds
94unsafe impl BytesWithinBounds<8> for u64 {}
95
96// SAFETY: size is within bounds
97unsafe impl BytesWithinBounds<16> for u128 {}
98
99#[cfg(target_pointer_width = "16")]
100// SAFETY: size is within bounds
101unsafe impl BytesWithinBounds<2> for usize {}
102
103#[cfg(target_pointer_width = "32")]
104// SAFETY: size is within bounds
105unsafe impl BytesWithinBounds<4> for usize {}
106
107#[cfg(target_pointer_width = "64")]
108// SAFETY: size is within bounds
109unsafe impl BytesWithinBounds<8> for usize {}
110
111// SAFETY: size is within bounds
112unsafe impl BytesWithinBounds<1> for i8 {}
113
114// SAFETY: size is within bounds
115unsafe impl BytesWithinBounds<2> for i16 {}
116
117// SAFETY: size is within bounds
118unsafe impl BytesWithinBounds<4> for i32 {}
119
120// SAFETY: size is within bounds
121unsafe impl BytesWithinBounds<8> for i64 {}
122
123// SAFETY: size is within bounds
124unsafe impl BytesWithinBounds<16> for i128 {}
125
126#[cfg(target_pointer_width = "16")]
127// SAFETY: size is within bounds
128unsafe impl BytesWithinBounds<2> for isize {}
129
130#[cfg(target_pointer_width = "32")]
131// SAFETY: size is within bounds
132unsafe impl BytesWithinBounds<4> for isize {}
133
134#[cfg(target_pointer_width = "64")]
135// SAFETY: size is within bounds
136unsafe impl BytesWithinBounds<8> for isize {}
137
138enum IntReprInfo {
139	Inline,
140	I8,
141	I16,
142	I24,
143	I32,
144	I48,
145	I64,
146	I96,
147	BigintUnsigned(u8),
148	BigintSigned(u8)
149}
150
151struct UnsignedByteCount {
152	count: u8,
153	highest_bit_set: bool
154}
155
156/// # Safety
157///
158/// See safety comment on [`hint_bytes_valid`]
159#[inline]
160unsafe fn get_repr_info_unsigned_le<const BYTES: usize>(bytes: [u8; BYTES]) -> IntReprInfo {
161	// SAFETY: we have same safety invariant as `get_byte_count_unsigned_le`
162	let byte_count = unsafe { get_byte_count_unsigned_le(bytes) };
163
164	match (byte_count.count, byte_count.highest_bit_set) {
165		(1, false) => { IntReprInfo::I8 }
166		(2, false) | (1, true) => { IntReprInfo::I16 }
167		(3, false) | (2, true) => { IntReprInfo::I24 }
168		(4, false) | (3, true) => { IntReprInfo::I32 }
169		(5..=6, false) | (4..=5, true) => { IntReprInfo::I48 }
170		(7..=8, false) | (6..=7, true) => { IntReprInfo::I64 }
171		(9..=12, false) | (8..=11, true) => { IntReprInfo::I96 }
172		(byte_count, _) => { IntReprInfo::BigintUnsigned(byte_count) }
173	}
174}
175
176/// # Safety
177///
178/// See safety comment on [`hint_bytes_valid`]
179#[inline]
180unsafe fn get_repr_info_signed_le<const BYTES: usize>(bytes: [u8; BYTES]) -> IntReprInfo {
181	// SAFETY: we have same safety invariant as `get_byte_count_signed_le`
182	let byte_count = unsafe { get_byte_count_signed_le(bytes) };
183
184	match byte_count {
185		1 => { IntReprInfo::I8 }
186		2 => { IntReprInfo::I16 }
187		3 => { IntReprInfo::I24 }
188		4 => { IntReprInfo::I32 }
189		5..=6 => { IntReprInfo::I48 }
190		7..=8 => { IntReprInfo::I64 }
191		9..=12 => { IntReprInfo::I96 }
192		byte_count => { IntReprInfo::BigintSigned(byte_count) }
193	}
194}
195
196/// # Safety
197///
198/// See safety comment on [`hint_bytes_valid`]
199#[inline]
200unsafe fn get_byte_count_unsigned_le<const BYTES: usize>(bytes: [u8; BYTES]) -> UnsignedByteCount {
201	// SAFETY: caller promises safety preconditions are met
202	unsafe { hint_bytes_valid::<BYTES>() }
203
204	for (i, byte) in bytes.into_iter().enumerate().rev() {
205		// simple! just return the first byte (including the byte) where its not
206		// all 0s. If the byte has the highest bit set to 1, a regular int would
207		// need to go to the next "tier" of int, but a bigint would not need that,
208		// so return the information seperately.
209
210		if byte != 0 {
211			let highest_bit_set = byte >> 7 != 0;
212			return UnsignedByteCount {
213				// `i + 1` is because `i` is 0 based index in the array,
214				// but `count` is count of bytes
215				count: (i + 1).into_u8_lossy(),
216				highest_bit_set
217			}
218		}
219	}
220
221	// everything is empty so just return the minimum
222	UnsignedByteCount {
223		count: 1,
224		highest_bit_set: false
225	}
226}
227
228/// # Safety
229///
230/// See safety comment on [`hint_bytes_valid`]
231#[inline]
232unsafe fn get_byte_count_signed_le<const BYTES: usize>(bytes: [u8; BYTES]) -> u8 {
233	// SAFETY: caller promises safety preconditions are met
234	unsafe { hint_bytes_valid::<BYTES>() }
235
236	let sign_bit = {
237		// SAFETY: `bytes` is len `BYTES`, and we assert that `BYTES > 0`,
238		// so this will be in bounds
239		let byte = unsafe { *bytes.get_unchecked(BYTES - 1) };
240
241		byte >> 7
242	};
243
244	// byte that has empty data and can (probably) be safely discarded.
245	// if negative, all bits 1, if positive, all bits 0
246	let empty_byte = if sign_bit == 0 { 0 } else { u8::MAX };
247
248	for (i, byte) in bytes.into_iter().enumerate().rev() {
249		// the following could be shortened to a one liner...
250		// but this absolutely sucks for readability/maintainability, so nah
251		// if byte != empty_byte { return (i + 1) as u8 + (byte >> 7 != sign_bit) as u8 }
252
253		if byte == empty_byte {
254			// byte is full of 1s if negative, or full of 0s if positive
255			// this byte can (probably) be safely discarded; continue
256		} else if byte >> 7 == sign_bit {
257			// sign bit is the same, return up to / including this byte
258			// iter range is 0 to BYTES - 1 (inclusive), so this return range
259			// will be 1 to BYTES (inclusive), which is correct
260			return (i + 1).into_u8_lossy()
261		} else {
262			// sign bit is different, return this byte and one more after it.
263			// if the next byte would have the wrong sign, it would have returned
264			// already in the previous branch. This won't ever overflow because
265			// the first byte will not have a different sign (as... itself),
266			// so will never reach here
267			return (i + 2).into_u8_lossy()
268		}
269	}
270
271	// everything is "empty", just return the minimum
272	1
273}
274
275/// Helper to hint and assert in debug that `BYTES` is not 0 and less than 256
276///
277/// # Safety
278///
279/// `BYTES > 0` and `BYTES <= 256` must both be true. This function adds some
280/// compiler hints for it as well, which makes it UB if these invariants aren't
281/// met
282#[expect(clippy::inline_always, reason = "in release this fn is no-op")]
283#[inline(always)]
284unsafe fn hint_bytes_valid<const BYTES: usize>() {
285	// ints must be at least one byte
286	debug_assert!(BYTES > 0);
287	// largest bigint we support ~~for now~~ is 256 bytes
288	debug_assert!(BYTES <= 256);
289
290	// SAFETY: caller promises this precondition
291	unsafe { hint::assert_unchecked(BYTES > 0) }
292	// SAFETY: same as above
293	unsafe { hint::assert_unchecked(BYTES <= 256) }
294}