use std::collections::BTreeMap;
use crate::codegen::{internal_failure, wasm_u32};
use crate::compiler_error::CompilerFailure;
#[derive(Clone, Debug)]
pub struct BigIntLiteral {
pub digits: String,
pub sign: i8,
pub limbs_le: Vec<u64>,
}
#[derive(Clone, Copy, Debug)]
pub struct BigIntEntry {
pub data_idx: u32,
pub limb_count: u32,
pub sign: i8,
}
#[derive(Default, Clone, Debug)]
pub struct BigIntPool {
pub literals: Vec<BigIntLiteral>,
text_to_idx: BTreeMap<String, usize>,
}
impl BigIntPool {
pub(crate) fn intern_digits(&mut self, digits: &str) -> Result<Option<usize>, CompilerFailure> {
const SAFE_INTEGER_MAX: u64 = (1u64 << 53) - 1;
check_decimal_digits(digits)?;
if digits
.parse::<u64>()
.is_ok_and(|value| value <= SAFE_INTEGER_MAX)
{
return Ok(None);
}
if let Some(&existing) = self.text_to_idx.get(digits) {
return Ok(Some(existing));
}
let limbs_le = decimal_to_limbs(digits);
let idx = self.literals.len();
self.literals.push(BigIntLiteral {
digits: digits.to_string(),
sign: 1,
limbs_le,
});
self.text_to_idx.insert(digits.to_string(), idx);
Ok(Some(idx))
}
pub fn lookup(&self, digits: &str) -> Result<Option<BigIntEntry>, CompilerFailure> {
let Some(&idx) = self.text_to_idx.get(digits) else {
return Ok(None);
};
let lit = self.literal(idx)?;
Ok(Some(BigIntEntry {
data_idx: wasm_u32(idx)?,
limb_count: wasm_u32(lit.limbs_le.len())?,
sign: lit.sign,
}))
}
fn literal(&self, idx: usize) -> Result<&BigIntLiteral, CompilerFailure> {
self.literals
.get(idx)
.ok_or_else(|| internal_failure("a bigint literal pool index is out of range"))
}
pub fn is_empty(&self) -> bool {
self.literals.is_empty()
}
pub fn le_bytes(&self, idx: usize) -> Result<Vec<u8>, CompilerFailure> {
Ok(self
.literal(idx)?
.limbs_le
.iter()
.flat_map(|w| w.to_le_bytes())
.collect())
}
}
fn check_decimal_digits(digits: &str) -> Result<(), CompilerFailure> {
if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(internal_failure(
"a bigint literal is not a sign-free decimal digit string",
));
}
Ok(())
}
fn decimal_to_limbs(digits: &str) -> Vec<u64> {
use num_bigint::BigUint;
use num_traits::Zero;
let value: BigUint = digits
.parse()
.expect("validated decimal digits parse as BigUint");
if value.is_zero() {
return Vec::new();
}
value.to_u64_digits()
}
#[cfg(test)]
mod tests {
use super::{BigIntPool, decimal_to_limbs};
use crate::compiler_error::CompilerFailure;
#[test]
fn small_round_trip() {
let limbs = decimal_to_limbs("18446744073709551616");
assert_eq!(limbs, vec![0, 1]);
}
#[test]
fn one_bit_under_two_limbs() {
let limbs = decimal_to_limbs("18446744073709551615");
assert_eq!(limbs, vec![u64::MAX]);
}
#[test]
fn very_large() {
let limbs = decimal_to_limbs("1267650600228229401496703205376");
assert_eq!(limbs, vec![0, 1u64 << 36]);
}
#[test]
fn corrupt_literals_and_indices_are_internal_failures() {
let mut pool = BigIntPool::default();
for digits in ["-9223372036854775808", "", "12a", "+5"] {
assert!(
matches!(
pool.intern_digits(digits),
Err(CompilerFailure::Internal { .. })
),
"{digits:?}"
);
}
assert_eq!(pool.intern_digits("9007199254740991").unwrap(), None);
assert_eq!(pool.intern_digits("9007199254740992").unwrap(), Some(0));
assert!(pool.lookup("9007199254740992").unwrap().is_some());
assert!(pool.lookup("1").unwrap().is_none());
assert!(pool.le_bytes(1).is_err());
pool.literals.clear();
assert!(pool.lookup("9007199254740992").is_err());
}
}