use crate::kernel::{Planner, WisdomEntry};
use crate::prelude::*;
const WISDOM_MARKER: &str = "oxifft-wisdom";
const WISDOM_LEGACY_HEADER: &str = "oxifft-wisdom-1.0";
pub const WISDOM_FORMAT_VERSION: u32 = 2;
pub const MAX_WISDOM_TEXT_BYTES: usize = 64 * 1024 * 1024;
pub const MAX_WISDOM_TEXT_ENTRIES: usize = 1_000_000;
pub const MAX_WISDOM_BINARY_BYTES: usize = 64 * 1024 * 1024;
pub const MAX_WISDOM_BINARY_ENTRIES: usize = 1_000_000;
#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use]
pub struct WisdomImportResult {
pub imported: usize,
pub skipped_invalid: usize,
pub format_version: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use]
pub struct WisdomMergeResult {
pub added: usize,
pub replaced: usize,
pub kept_existing: usize,
pub skipped_invalid: usize,
pub format_version: u32,
}
static GLOBAL_WISDOM: RwLock<Option<WisdomCache>> = RwLock::new(None);
#[derive(Debug, Clone, Default)]
pub struct WisdomCache {
entries: HashMap<u64, WisdomEntry>,
}
impl WisdomCache {
#[must_use]
pub fn new() -> Self {
Self {
entries: HashMap::new(),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn lookup(&self, hash: u64) -> Option<&WisdomEntry> {
self.entries.get(&hash)
}
pub fn store(&mut self, entry: WisdomEntry) {
self.entries.insert(entry.problem_hash, entry);
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn import_from_planner<T: crate::kernel::Float>(&mut self, planner: &Planner<T>) {
let exported = planner.wisdom_export();
let _ = self.import_string(&exported);
}
pub fn export_to_planner<T: crate::kernel::Float>(&self, planner: &mut Planner<T>) {
let exported = self.export_string();
let _ = planner.wisdom_import(&exported);
}
#[must_use]
pub fn export_string(&self) -> String {
use core::fmt::Write;
let mut result = format!("({WISDOM_MARKER}\n (format_version {WISDOM_FORMAT_VERSION})\n");
for entry in self.entries.values() {
let _ = writeln!(
result,
" ({} \"{}\" {})",
entry.problem_hash, entry.solver_name, entry.cost
);
}
result.push(')');
result
}
pub fn import_string(&mut self, s: &str) -> Result<WisdomImportResult, WisdomError> {
let s = s.trim();
if s.len() > MAX_WISDOM_TEXT_BYTES {
return Err(WisdomError::TooLarge {
kind: "bytes",
limit: MAX_WISDOM_TEXT_BYTES,
actual: s.len(),
});
}
let format_version = detect_format_version(s)?;
if format_version > WISDOM_FORMAT_VERSION {
return Err(WisdomError::IncompatibleVersion {
found: format_version,
expected: WISDOM_FORMAT_VERSION,
});
}
let mut imported = 0usize;
let mut skipped_invalid = 0usize;
let mut entry_lines_seen = 0usize;
for line in s.lines().skip(1) {
let line = line.trim();
if !is_entry_line(line) {
continue;
}
entry_lines_seen += 1;
if entry_lines_seen > MAX_WISDOM_TEXT_ENTRIES {
return Err(WisdomError::TooLarge {
kind: "entries",
limit: MAX_WISDOM_TEXT_ENTRIES,
actual: entry_lines_seen,
});
}
match parse_entry_line(line) {
Some(entry) if is_valid_entry(&entry) => {
self.entries.insert(entry.problem_hash, entry);
imported += 1;
}
Some(_) => {
skipped_invalid += 1;
}
None => {
skipped_invalid += 1;
}
}
}
Ok(WisdomImportResult {
imported,
skipped_invalid,
format_version,
})
}
pub fn merge_string(&mut self, s: &str) -> Result<WisdomMergeResult, WisdomError> {
let s = s.trim();
if s.len() > MAX_WISDOM_TEXT_BYTES {
return Err(WisdomError::TooLarge {
kind: "bytes",
limit: MAX_WISDOM_TEXT_BYTES,
actual: s.len(),
});
}
let format_version = detect_format_version(s)?;
if format_version > WISDOM_FORMAT_VERSION {
return Err(WisdomError::IncompatibleVersion {
found: format_version,
expected: WISDOM_FORMAT_VERSION,
});
}
let mut added = 0usize;
let mut replaced = 0usize;
let mut kept_existing = 0usize;
let mut skipped_invalid = 0usize;
let mut entry_lines_seen = 0usize;
for line in s.lines().skip(1) {
let line = line.trim();
if !is_entry_line(line) {
continue;
}
entry_lines_seen += 1;
if entry_lines_seen > MAX_WISDOM_TEXT_ENTRIES {
return Err(WisdomError::TooLarge {
kind: "entries",
limit: MAX_WISDOM_TEXT_ENTRIES,
actual: entry_lines_seen,
});
}
match parse_entry_line(line) {
Some(entry) if is_valid_entry(&entry) => {
match self.entries.get(&entry.problem_hash) {
None => {
self.entries.insert(entry.problem_hash, entry);
added += 1;
}
Some(existing) if entry.cost < existing.cost => {
self.entries.insert(entry.problem_hash, entry);
replaced += 1;
}
Some(_) => {
kept_existing += 1;
}
}
}
_ => {
skipped_invalid += 1;
}
}
}
Ok(WisdomMergeResult {
added,
replaced,
kept_existing,
skipped_invalid,
format_version,
})
}
}
fn detect_format_version(s: &str) -> Result<u32, WisdomError> {
let first_line = s.lines().next().unwrap_or("").trim();
if first_line.starts_with(&format!("({WISDOM_LEGACY_HEADER}")) {
return Ok(0);
}
if first_line.starts_with(&format!("({WISDOM_MARKER}")) {
for line in s.lines().skip(1).take(5) {
let line = line.trim();
if let Some(ver) = parse_format_version_line(line) {
return Ok(ver);
}
}
return Ok(1);
}
Err(WisdomError::ParseError(
"missing oxifft-wisdom header".to_string(),
))
}
fn parse_format_version_line(line: &str) -> Option<u32> {
let line = line.trim();
if !line.starts_with("(format_version ") || !line.ends_with(')') {
return None;
}
let inner = &line["(format_version ".len()..line.len() - 1];
inner.trim().parse::<u32>().ok()
}
fn is_entry_line(line: &str) -> bool {
line.starts_with('(')
&& line.ends_with(')')
&& !line.starts_with(&format!("({WISDOM_MARKER}"))
&& !line.starts_with(&format!("({WISDOM_LEGACY_HEADER}"))
&& !line.starts_with("(format_version ")
}
fn parse_entry_line(line: &str) -> Option<WisdomEntry> {
let inner = line.get(1..line.len().checked_sub(1)?)?;
let parts: Vec<&str> = inner.split_whitespace().collect();
if parts.len() < 3 {
return None;
}
let hash = parts[0].parse::<u64>().ok()?;
let solver_name = parts[1].trim_matches('"').to_string();
let cost = parts[2].parse::<f64>().ok()?;
Some(WisdomEntry {
problem_hash: hash,
solver_name,
cost,
})
}
fn is_valid_entry(entry: &WisdomEntry) -> bool {
if entry.problem_hash == 0
|| entry.solver_name.is_empty()
|| !entry.cost.is_finite()
|| entry.cost < 0.0
{
return false;
}
match parse_mixed_radix_name(&entry.solver_name) {
Some(factors) => is_valid_mixed_radix_factors(&factors, entry.problem_hash),
None => true,
}
}
const MIXED_RADIX_VALID_RADICES: [u16; 7] = [2, 3, 4, 5, 7, 8, 16];
fn parse_mixed_radix_name(name: &str) -> Option<Vec<u16>> {
let suffix = name.strip_prefix("mixed-radix-")?;
suffix.split('-').map(|s| s.parse::<u16>().ok()).collect()
}
#[allow(clippy::redundant_pub_crate)]
pub(crate) fn is_valid_mixed_radix_factors(factors: &[u16], size: u64) -> bool {
if factors.is_empty() {
return false;
}
let mut product: u128 = 1;
for &f in factors {
if !MIXED_RADIX_VALID_RADICES.contains(&f) {
return false;
}
product = product.saturating_mul(u128::from(f));
if product > u128::from(u64::MAX) {
return false;
}
}
product == u128::from(size)
}
fn ensure_global_wisdom() {
#[cfg(feature = "std")]
{
let needs_init = GLOBAL_WISDOM
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_none();
if needs_init {
let mut write_guard = GLOBAL_WISDOM
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
write_guard.get_or_insert_with(WisdomCache::new);
}
}
#[cfg(not(feature = "std"))]
{
let needs_init = GLOBAL_WISDOM.read().is_none();
if needs_init {
let mut write_guard = GLOBAL_WISDOM.write();
write_guard.get_or_insert_with(WisdomCache::new);
}
}
}
fn with_wisdom<F, R>(f: F) -> R
where
F: FnOnce(&WisdomCache) -> R,
{
ensure_global_wisdom();
#[cfg(feature = "std")]
{
let guard = GLOBAL_WISDOM
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match guard.as_ref() {
Some(cache) => f(cache),
None => f(&WisdomCache::new()),
}
}
#[cfg(not(feature = "std"))]
{
let guard = GLOBAL_WISDOM.read();
match guard.as_ref() {
Some(cache) => f(cache),
None => f(&WisdomCache::new()),
}
}
}
fn with_wisdom_mut<F, R>(f: F) -> R
where
F: FnOnce(&mut WisdomCache) -> R,
{
ensure_global_wisdom();
#[cfg(feature = "std")]
{
let mut guard = GLOBAL_WISDOM
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
f(guard.get_or_insert_with(WisdomCache::new))
}
#[cfg(not(feature = "std"))]
{
let mut guard = GLOBAL_WISDOM.write();
f(guard.get_or_insert_with(WisdomCache::new))
}
}
#[must_use]
pub fn export_to_string() -> String {
with_wisdom(WisdomCache::export_string)
}
pub fn import_from_string(s: &str) -> Result<WisdomImportResult, WisdomError> {
with_wisdom_mut(|cache| cache.import_string(s))
}
pub fn merge_from_string(s: &str) -> Result<WisdomMergeResult, WisdomError> {
with_wisdom_mut(|cache| cache.merge_string(s))
}
#[cfg(feature = "std")]
pub fn export_to_file(path: &std::path::Path) -> std::io::Result<()> {
let wisdom = export_to_string();
std::fs::write(path, wisdom)
}
#[cfg(feature = "std")]
fn check_wisdom_file_size(path: &std::path::Path) -> Result<(), WisdomError> {
let len = std::fs::metadata(path)?.len();
if len > MAX_WISDOM_TEXT_BYTES as u64 {
return Err(WisdomError::TooLarge {
kind: "bytes",
limit: MAX_WISDOM_TEXT_BYTES,
actual: usize::try_from(len).unwrap_or(usize::MAX),
});
}
Ok(())
}
#[cfg(feature = "std")]
pub fn import_from_file(path: &std::path::Path) -> Result<WisdomImportResult, WisdomError> {
check_wisdom_file_size(path)?;
let contents = std::fs::read_to_string(path)?;
import_from_string(&contents)
}
#[cfg(feature = "std")]
pub fn merge_from_file(path: &std::path::Path) -> Result<WisdomMergeResult, WisdomError> {
check_wisdom_file_size(path)?;
let contents = std::fs::read_to_string(path)?;
merge_from_string(&contents)
}
#[cfg(feature = "std")]
fn import_from_first_existing(
paths: &[std::path::PathBuf],
) -> Result<WisdomImportResult, WisdomError> {
let mut last_error: Option<WisdomError> = None;
for path in paths {
if !path.exists() {
continue;
}
match import_from_file(path) {
Ok(result) => return Ok(result),
Err(e) => {
last_error = Some(e);
}
}
}
Err(last_error.unwrap_or_else(|| {
WisdomError::IoError(std::io::Error::new(
std::io::ErrorKind::NotFound,
"No system wisdom found",
))
}))
}
#[cfg(feature = "std")]
pub fn import_system_wisdom() -> Result<WisdomImportResult, WisdomError> {
import_from_first_existing(&get_system_wisdom_paths())
}
#[cfg(feature = "std")]
#[must_use]
pub fn get_user_wisdom_path() -> Option<std::path::PathBuf> {
#[cfg(target_os = "linux")]
{
if let Some(config_dir) = std::env::var_os("XDG_CONFIG_HOME") {
let mut path = std::path::PathBuf::from(config_dir);
path.push("oxifft");
path.push("wisdom");
return Some(path);
}
if let Some(home) = std::env::var_os("HOME") {
let mut path = std::path::PathBuf::from(home);
path.push(".config");
path.push("oxifft");
path.push("wisdom");
return Some(path);
}
}
#[cfg(target_os = "macos")]
{
if let Some(home) = std::env::var_os("HOME") {
let mut path = std::path::PathBuf::from(home);
path.push("Library");
path.push("Application Support");
path.push("oxifft");
path.push("wisdom");
return Some(path);
}
}
#[cfg(target_os = "windows")]
{
if let Some(appdata) = std::env::var_os("APPDATA") {
let mut path = std::path::PathBuf::from(appdata);
path.push("oxifft");
path.push("wisdom");
return Some(path);
}
}
None
}
#[cfg(feature = "std")]
fn get_system_wisdom_paths() -> Vec<std::path::PathBuf> {
let mut paths = Vec::new();
if let Some(user_path) = get_user_wisdom_path() {
paths.push(user_path);
}
#[cfg(target_os = "linux")]
{
paths.push(std::path::PathBuf::from("/etc/oxifft/wisdom"));
paths.push(std::path::PathBuf::from("/usr/share/oxifft/wisdom"));
}
#[cfg(target_os = "macos")]
{
paths.push(std::path::PathBuf::from(
"/Library/Application Support/oxifft/wisdom",
));
}
paths
}
pub fn forget() {
with_wisdom_mut(WisdomCache::clear);
}
#[must_use]
pub fn wisdom_count() -> usize {
with_wisdom(WisdomCache::len)
}
pub fn store_wisdom(entry: WisdomEntry) {
with_wisdom_mut(|cache| cache.store(entry));
}
#[must_use]
pub fn lookup_wisdom(hash: u64) -> Option<WisdomEntry> {
with_wisdom(|cache| cache.lookup(hash).cloned())
}
#[derive(Debug)]
#[non_exhaustive]
pub enum WisdomError {
ParseError(String),
IncompatibleVersion {
found: u32,
expected: u32,
},
#[cfg(feature = "std")]
IoError(std::io::Error),
TooLarge {
kind: &'static str,
limit: usize,
actual: usize,
},
}
impl core::fmt::Display for WisdomError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ParseError(msg) => write!(f, "Wisdom parse error: {msg}"),
Self::IncompatibleVersion { found, expected } => write!(
f,
"Wisdom format version {found} is not supported \
(this build understands up to version {expected})"
),
#[cfg(feature = "std")]
Self::IoError(e) => write!(f, "I/O error: {e}"),
Self::TooLarge {
kind,
limit,
actual,
} => write!(
f,
"Wisdom input exceeds the maximum allowed {kind} ({actual} > {limit}); \
rejected before parsing to avoid unbounded resource use"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for WisdomError {}
#[cfg(feature = "std")]
impl From<std::io::Error> for WisdomError {
fn from(e: std::io::Error) -> Self {
Self::IoError(e)
}
}
const BINARY_MAGIC: &[u8; 8] = b"OXIWISDM";
const BINARY_FORMAT_VERSION_V1: u16 = 1;
const BINARY_FORMAT_VERSION_V2: u16 = 2;
const BINARY_FORMAT_VERSION: u16 = BINARY_FORMAT_VERSION_V2;
const BINARY_ENTRY_SIZE_V1: usize = 30;
pub const MAX_BINARY_FACTORS: usize = u8::MAX as usize;
const ALGO_TAG_COOLEY_TUKEY: u8 = 0;
const ALGO_TAG_SPLIT_RADIX: u8 = 1;
const ALGO_TAG_STOCKHAM: u8 = 2;
const ALGO_TAG_BLUESTEIN: u8 = 3;
const ALGO_TAG_RADER: u8 = 4;
const ALGO_TAG_MIXED_RADIX: u8 = 5;
const ALGO_TAG_WINOGRAD: u8 = 6;
const ALGO_TAG_DIRECT: u8 = 7;
const ALGO_TAG_GENERIC: u8 = 8;
const ALGO_TAG_COMPOSITE: u8 = 9;
const ALGO_TAG_NOP: u8 = 10;
const ALGO_TAG_UNKNOWN: u8 = 255;
fn algo_tag_from_solver_name(name: &str) -> (u8, Vec<u16>) {
if let Some(factors) = parse_mixed_radix_name(name) {
return (ALGO_TAG_MIXED_RADIX, factors);
}
let tag = match name {
"ct-dit" | "ct-dif" | "ct-radix4" | "ct-radix8" => ALGO_TAG_COOLEY_TUKEY,
"ct-splitradix" => ALGO_TAG_SPLIT_RADIX,
"stockham" => ALGO_TAG_STOCKHAM,
"bluestein" => ALGO_TAG_BLUESTEIN,
"rader" => ALGO_TAG_RADER,
"winograd" | "winograd-pfa" => ALGO_TAG_WINOGRAD,
"direct" => ALGO_TAG_DIRECT,
"generic" | "cache-oblivious" => ALGO_TAG_GENERIC,
"composite" => ALGO_TAG_COMPOSITE,
"nop" => ALGO_TAG_NOP,
n if n.starts_with("CooleyTukey") => ALGO_TAG_COOLEY_TUKEY,
n if n.starts_with("Winograd") => ALGO_TAG_WINOGRAD,
_ => ALGO_TAG_UNKNOWN,
};
(tag, Vec::new())
}
fn solver_name_from_algo_tag(tag: u8, factors: &[u16]) -> String {
match tag {
ALGO_TAG_COOLEY_TUKEY => "ct-dit".to_string(),
ALGO_TAG_SPLIT_RADIX => "ct-splitradix".to_string(),
ALGO_TAG_STOCKHAM => "stockham".to_string(),
ALGO_TAG_BLUESTEIN => "bluestein".to_string(),
ALGO_TAG_RADER => "rader".to_string(),
ALGO_TAG_MIXED_RADIX => {
let parts: Vec<String> = factors.iter().map(|r| r.to_string()).collect();
format!("mixed-radix-{}", parts.join("-"))
}
ALGO_TAG_WINOGRAD => "winograd".to_string(),
ALGO_TAG_DIRECT => "direct".to_string(),
ALGO_TAG_GENERIC => "generic".to_string(),
ALGO_TAG_COMPOSITE => "composite".to_string(),
ALGO_TAG_NOP => "nop".to_string(),
_ => "unknown".to_string(),
}
}
fn is_valid_binary_entry(size_key: u64, algo_tag: u8, factors: &[u16]) -> bool {
if size_key == 0 || algo_tag == ALGO_TAG_UNKNOWN {
return false;
}
if algo_tag == ALGO_TAG_MIXED_RADIX {
return is_valid_mixed_radix_factors(factors, size_key);
}
true
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BinaryOverflowPolicy {
Cap,
Reject,
}
impl WisdomCache {
fn to_binary_with_policy(&self, policy: BinaryOverflowPolicy) -> Result<Vec<u8>, WisdomError> {
let total_entries = self.entries.len();
if total_entries > u32::MAX as usize && policy == BinaryOverflowPolicy::Reject {
return Err(WisdomError::TooLarge {
kind: "entries",
limit: u32::MAX as usize,
actual: total_entries,
});
}
let entry_count = total_entries.min(u32::MAX as usize);
let mut buf = Vec::with_capacity(16 + entry_count * 18);
buf.extend_from_slice(BINARY_MAGIC);
buf.extend_from_slice(&BINARY_FORMAT_VERSION_V2.to_le_bytes());
buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&(entry_count as u32).to_le_bytes());
for entry in self.entries.values().take(entry_count) {
let (tag, mut factors) = algo_tag_from_solver_name(&entry.solver_name);
if factors.len() > MAX_BINARY_FACTORS {
match policy {
BinaryOverflowPolicy::Reject => {
return Err(WisdomError::TooLarge {
kind: "factors",
limit: MAX_BINARY_FACTORS,
actual: factors.len(),
});
}
BinaryOverflowPolicy::Cap => factors.truncate(MAX_BINARY_FACTORS),
}
}
let elapsed_ns = entry.cost as u64;
buf.extend_from_slice(&entry.problem_hash.to_le_bytes());
buf.push(tag);
buf.push(factors.len() as u8);
for f in &factors {
buf.extend_from_slice(&f.to_le_bytes());
}
buf.extend_from_slice(&elapsed_ns.to_le_bytes());
}
Ok(buf)
}
#[must_use]
pub fn to_binary(&self) -> Vec<u8> {
self.to_binary_with_policy(BinaryOverflowPolicy::Cap)
.unwrap_or_default()
}
pub fn to_binary_checked(&self) -> Result<Vec<u8>, WisdomError> {
self.to_binary_with_policy(BinaryOverflowPolicy::Reject)
}
fn decode_binary(data: &[u8]) -> Result<(Self, usize, u16), WisdomError> {
if data.len() > MAX_WISDOM_BINARY_BYTES {
return Err(WisdomError::TooLarge {
kind: "bytes",
limit: MAX_WISDOM_BINARY_BYTES,
actual: data.len(),
});
}
if data.len() < 16 {
return Err(WisdomError::ParseError(format!(
"binary wisdom data truncated: header requires 16 bytes, got {}",
data.len()
)));
}
if &data[..8] != BINARY_MAGIC {
return Err(WisdomError::ParseError(
"binary wisdom data: bad magic bytes (not an OxiFFT wisdom blob)".to_string(),
));
}
let version = u16::from_le_bytes([data[8], data[9]]);
match version {
BINARY_FORMAT_VERSION_V1 => {
Self::from_binary_v1(data).map(|(cache, skipped)| (cache, skipped, version))
}
BINARY_FORMAT_VERSION_V2 => {
Self::from_binary_v2(data).map(|(cache, skipped)| (cache, skipped, version))
}
v if u32::from(v) > u32::from(BINARY_FORMAT_VERSION) => {
Err(WisdomError::IncompatibleVersion {
found: u32::from(v),
expected: u32::from(BINARY_FORMAT_VERSION),
})
}
v => Err(WisdomError::ParseError(format!(
"binary wisdom data: unrecognised format version {v}"
))),
}
}
fn from_binary_v1(data: &[u8]) -> Result<(Self, usize), WisdomError> {
let entry_count = u16::from_le_bytes([data[10], data[11]]) as usize;
if entry_count > MAX_WISDOM_BINARY_ENTRIES {
return Err(WisdomError::TooLarge {
kind: "entries",
limit: MAX_WISDOM_BINARY_ENTRIES,
actual: entry_count,
});
}
let expected_len = 16 + entry_count * BINARY_ENTRY_SIZE_V1;
if data.len() < expected_len {
return Err(WisdomError::ParseError(format!(
"binary wisdom data truncated: header declares {entry_count} v1 entries \
({expected_len} bytes total) but only {} bytes are present",
data.len()
)));
}
let mut cache = WisdomCache::new();
let mut skipped_invalid = 0usize;
for i in 0..entry_count {
let offset = 16 + i * BINARY_ENTRY_SIZE_V1;
let entry_bytes = &data[offset..offset + BINARY_ENTRY_SIZE_V1];
let size_key = u64::from_le_bytes([
entry_bytes[0],
entry_bytes[1],
entry_bytes[2],
entry_bytes[3],
entry_bytes[4],
entry_bytes[5],
entry_bytes[6],
entry_bytes[7],
]);
let algo_tag = entry_bytes[8];
let factors_len = (entry_bytes[9] as usize).min(6);
let factors: [u16; 6] = [
u16::from_le_bytes([entry_bytes[10], entry_bytes[11]]),
u16::from_le_bytes([entry_bytes[12], entry_bytes[13]]),
u16::from_le_bytes([entry_bytes[14], entry_bytes[15]]),
u16::from_le_bytes([entry_bytes[16], entry_bytes[17]]),
u16::from_le_bytes([entry_bytes[18], entry_bytes[19]]),
u16::from_le_bytes([entry_bytes[20], entry_bytes[21]]),
];
let elapsed_ns = u64::from_le_bytes([
entry_bytes[22],
entry_bytes[23],
entry_bytes[24],
entry_bytes[25],
entry_bytes[26],
entry_bytes[27],
entry_bytes[28],
entry_bytes[29],
]);
let factor_slice = &factors[..factors_len];
if !is_valid_binary_entry(size_key, algo_tag, factor_slice) {
skipped_invalid += 1;
continue;
}
let solver_name = solver_name_from_algo_tag(algo_tag, factor_slice);
cache.store(WisdomEntry {
problem_hash: size_key,
solver_name,
cost: elapsed_ns as f64,
});
}
Ok((cache, skipped_invalid))
}
fn from_binary_v2(data: &[u8]) -> Result<(Self, usize), WisdomError> {
let entry_count = u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
if entry_count > MAX_WISDOM_BINARY_ENTRIES {
return Err(WisdomError::TooLarge {
kind: "entries",
limit: MAX_WISDOM_BINARY_ENTRIES,
actual: entry_count,
});
}
let mut cache = WisdomCache::new();
let mut skipped_invalid = 0usize;
let mut offset = 16usize;
for _ in 0..entry_count {
if data.len() < offset + 10 {
return Err(WisdomError::ParseError(format!(
"binary wisdom data truncated: expected an entry header at byte {offset}, \
only {} bytes are present",
data.len()
)));
}
let size_key = u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
let algo_tag = data[offset + 8];
let factors_len = data[offset + 9] as usize;
offset += 10;
let factors_bytes_len = factors_len * 2;
if data.len() < offset + factors_bytes_len + 8 {
return Err(WisdomError::ParseError(format!(
"binary wisdom data truncated: entry declares {factors_len} factors at byte \
{offset} but the blob ends before its data"
)));
}
let mut factors: Vec<u16> = Vec::with_capacity(factors_len);
for i in 0..factors_len {
let fo = offset + i * 2;
factors.push(u16::from_le_bytes([data[fo], data[fo + 1]]));
}
offset += factors_bytes_len;
let elapsed_ns = u64::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]);
offset += 8;
if !is_valid_binary_entry(size_key, algo_tag, &factors) {
skipped_invalid += 1;
continue;
}
let solver_name = solver_name_from_algo_tag(algo_tag, &factors);
cache.store(WisdomEntry {
problem_hash: size_key,
solver_name,
cost: elapsed_ns as f64,
});
}
Ok((cache, skipped_invalid))
}
pub fn from_binary(data: &[u8]) -> Result<Self, WisdomError> {
Self::decode_binary(data).map(|(cache, _skipped, _version)| cache)
}
pub fn import_binary(&mut self, data: &[u8]) -> Result<WisdomImportResult, WisdomError> {
let (decoded, skipped_invalid, version) = Self::decode_binary(data)?;
let imported = decoded.entries.len();
self.entries.extend(decoded.entries);
Ok(WisdomImportResult {
imported,
skipped_invalid,
format_version: u32::from(version),
})
}
#[must_use]
pub fn entry_count(&self) -> usize {
self.entries.len()
}
}
#[cfg(test)]
#[path = "wisdom_tests.rs"]
mod tests;