|
| 1 | +#![allow(dead_code)] |
| 2 | + |
| 3 | +use bytemuck::{NoUninit, Pod}; |
| 4 | +use spacetimedb_sats::{i256, u256}; |
| 5 | +use v8::{BigInt, Boolean, HandleScope, Integer, Local, Number, Value}; |
| 6 | + |
| 7 | +/// Types that can be converted to a v8-stack-allocated [`Value`]. |
| 8 | +/// The conversion can be done without the possibility for error. |
| 9 | +pub(super) trait ToValue { |
| 10 | + /// Convert `self` within `scope` (a sort of stack management in V8) to a [`Value`]. |
| 11 | + fn to_value<'s>(&self, scope: &mut HandleScope<'s>) -> Local<'s, Value>; |
| 12 | +} |
| 13 | + |
| 14 | +/// Provides a [`ToValue`] implementation. |
| 15 | +macro_rules! impl_to_value { |
| 16 | + ($ty:ty, ($val:ident, $scope:ident) => $logic:expr) => { |
| 17 | + impl ToValue for $ty { |
| 18 | + fn to_value<'s>(&self, $scope: &mut HandleScope<'s>) -> Local<'s, Value> { |
| 19 | + let $val = *self; |
| 20 | + $logic.into() |
| 21 | + } |
| 22 | + } |
| 23 | + }; |
| 24 | +} |
| 25 | + |
| 26 | +// Floats are the most direct conversion. |
| 27 | +impl_to_value!(f32, (val, scope) => (val as f64).to_value(scope)); |
| 28 | +impl_to_value!(f64, (val, scope) => Number::new(scope, val)); |
| 29 | + |
| 30 | +// Booleans have dedicated conversions. |
| 31 | +impl_to_value!(bool, (val, scope) => Boolean::new(scope, val)); |
| 32 | + |
| 33 | +// Sub-32-bit integers get widened to 32-bit first. |
| 34 | +impl_to_value!(i8, (val, scope) => (val as i32).to_value(scope)); |
| 35 | +impl_to_value!(u8, (val, scope) => (val as u32).to_value(scope)); |
| 36 | +impl_to_value!(i16, (val, scope) => (val as i32).to_value(scope)); |
| 37 | +impl_to_value!(u16, (val, scope) => (val as u32).to_value(scope)); |
| 38 | + |
| 39 | +// 32-bit integers have dedicated conversions. |
| 40 | +impl_to_value!(i32, (val, scope) => Integer::new(scope, val)); |
| 41 | +impl_to_value!(u32, (val, scope) => Integer::new_from_unsigned(scope, val)); |
| 42 | + |
| 43 | +// 64-bit integers have dedicated conversions. |
| 44 | +impl_to_value!(i64, (val, scope) => BigInt::new_from_i64(scope, val)); |
| 45 | +impl_to_value!(u64, (val, scope) => BigInt::new_from_u64(scope, val)); |
| 46 | + |
| 47 | +/// Converts the little-endian bytes of a number to a V8 [`BigInt`]. |
| 48 | +/// |
| 49 | +/// The `sign` is passed along to the `BigInt`. |
| 50 | +fn le_bytes_to_bigint<'s, const N: usize, const W: usize>( |
| 51 | + scope: &mut HandleScope<'s>, |
| 52 | + sign: bool, |
| 53 | + le_bytes: [u8; N], |
| 54 | +) -> Local<'s, BigInt> |
| 55 | +where |
| 56 | + [u8; N]: NoUninit, |
| 57 | + [u64; W]: Pod, |
| 58 | +{ |
| 59 | + let words = bytemuck::must_cast::<_, [u64; W]>(le_bytes).map(u64::from_le); |
| 60 | + BigInt::new_from_words(scope, sign, &words).unwrap() |
| 61 | +} |
| 62 | + |
| 63 | +// Unsigned 128-bit and 256-bit integers have dedicated conversions. |
| 64 | +// They are convered to a list of words before becoming `BigInt`s. |
| 65 | +impl_to_value!(u128, (val, scope) => le_bytes_to_bigint::<16, 2>(scope, false, val.to_le_bytes())); |
| 66 | +impl_to_value!(u256, (val, scope) => le_bytes_to_bigint::<32, 4>(scope, false, val.to_le_bytes())); |
| 67 | + |
| 68 | +/// Returns `iN::MIN` for `N = 8 * WORDS` as a V8 [`BigInt`]. |
| 69 | +/// |
| 70 | +/// Examples: |
| 71 | +/// `i64::MIN` becomes `-1 * WORD_MIN * (2^64)^0 = -1 * WORD_MIN` |
| 72 | +/// `i128::MIN` becomes `-1 * (0 * (2^64)^0 + WORD_MIN * (2^64)^1) = -1 * WORD_MIN * 2^64` |
| 73 | +/// `i256::MIN` becomes `-1 * (0 * (2^64)^0 + 0 * (2^64)^1 + WORD_MIN * (2^64)^2) = -1 * WORD_MIN * (2^128)` |
| 74 | +fn signed_min_bigint<'s, const WORDS: usize>(scope: &mut HandleScope<'s>) -> Local<'s, BigInt> { |
| 75 | + const WORD_MIN: u64 = i64::MIN as u64; |
| 76 | + let words = &mut [0u64; WORDS]; |
| 77 | + if let [.., last] = words.as_mut_slice() { |
| 78 | + *last = WORD_MIN; |
| 79 | + } |
| 80 | + v8::BigInt::new_from_words(scope, true, words).unwrap() |
| 81 | +} |
| 82 | + |
| 83 | +// Signed 128-bit and 256-bit integers have dedicated conversions. |
| 84 | +// |
| 85 | +// For the negative number case, the magnitude is computed and the sign is passed along. |
| 86 | +// A special case is the minimum number. |
| 87 | +impl_to_value!(i128, (val, scope) => { |
| 88 | + let sign = val.is_negative(); |
| 89 | + let magnitude = if sign { val.checked_neg() } else { Some(val) }; |
| 90 | + match magnitude { |
| 91 | + Some(magnitude) => le_bytes_to_bigint::<16, 2>(scope, sign, magnitude.to_le_bytes()), |
| 92 | + None => signed_min_bigint::<2>(scope), |
| 93 | + } |
| 94 | +}); |
| 95 | +impl_to_value!(i256, (val, scope) => { |
| 96 | + let sign = val.is_negative(); |
| 97 | + let magnitude = if sign { val.checked_neg() } else { Some(val) }; |
| 98 | + match magnitude { |
| 99 | + Some(magnitude) => le_bytes_to_bigint::<32, 4>(scope, sign, magnitude.to_le_bytes()), |
| 100 | + None => signed_min_bigint::<4>(scope), |
| 101 | + } |
| 102 | +}); |
0 commit comments