use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};
use deq_decoder_abi::host::{DecoderLibrary, HostDecodeRequest, LoadedDecoder};
use deq_decoder_abi::interface::{
DEQ_DECODER_CAPABILITY_LOSS, DEQ_DECODER_CAPABILITY_REWEIGHTS, DEQ_DECODER_CAPABILITY_SEED,
};
use deq_decoder_abi::plugin::LossSiteView;
use serde::{Deserialize, Serialize};
#[cfg(feature = "cli")]
use structdoc::StructDoc;
use crate::decoder::blackbox_decoder::{DecodingHypergraph, ParityFactor};
use crate::decoder::thread_pooling::{
DecodeError, DecodeRequest, DecoderInstance, ThreadPoolingConfig, ThreadPoolingDecoder,
};
use crate::decoder::{DecoderFeatures, blackbox_util};
const _: () = {
assert!(DecoderFeatures::SEED.bits() as u64 == DEQ_DECODER_CAPABILITY_SEED);
assert!(DecoderFeatures::REWEIGHTS.bits() as u64 == DEQ_DECODER_CAPABILITY_REWEIGHTS);
assert!(DecoderFeatures::LOSS.bits() as u64 == DEQ_DECODER_CAPABILITY_LOSS);
};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "cli", derive(StructDoc))]
#[serde(deny_unknown_fields)]
pub struct DynLibDecoderConfig {
#[serde(flatten)]
pub thread_pooling_config: ThreadPoolingConfig,
pub library: PathBuf,
#[cfg_attr(feature = "cli", structdoc(skip))]
#[serde(default)]
pub decoder_config: serde_json::Value,
}
fn library_cache() -> &'static Mutex<HashMap<PathBuf, &'static DecoderLibrary>> {
static CACHE: OnceLock<Mutex<HashMap<PathBuf, &'static DecoderLibrary>>> = OnceLock::new();
CACHE.get_or_init(|| Mutex::new(HashMap::new()))
}
fn get_or_load_library(path: &Path) -> &'static DecoderLibrary {
let mut cache = library_cache().lock().unwrap();
if let Some(library) = cache.get(path) {
return library;
}
let library = unsafe { DecoderLibrary::load(path) }
.unwrap_or_else(|e| panic!("failed to load decoder plugin {}: {e}", path.display()));
cache.insert(path.to_path_buf(), library);
library
}
pub struct DynLibInstance {
loaded: LoadedDecoder,
active_edges: Option<Vec<u64>>,
}
fn library_features(library: &'static DecoderLibrary) -> DecoderFeatures {
let bits = library.capabilities();
u32::try_from(bits)
.ok()
.and_then(DecoderFeatures::from_bits)
.unwrap_or_else(|| panic!("decoder plugin advertises capability bits {bits:#x} that deq cannot represent"))
}
impl DecoderInstance for DynLibInstance {
fn supported_features(config: &serde_json::Value) -> DecoderFeatures {
let config: DynLibDecoderConfig =
serde_json::from_value(config.clone()).expect("invalid dynamic-library decoder configuration");
library_features(get_or_load_library(&config.library))
}
fn new(hypergraph: &DecodingHypergraph, config: &serde_json::Value) -> Self {
let config: DynLibDecoderConfig =
serde_json::from_value(config.clone()).expect("invalid dynamic-library decoder configuration");
let library = get_or_load_library(&config.library);
let features = library_features(library);
let complete_graph = features.intersects(DecoderFeatures::REWEIGHTS | DecoderFeatures::LOSS);
let selected: Vec<u64> = if complete_graph {
(0..hypergraph.hyperedges.len() as u64).collect()
} else {
blackbox_util::active_edge_indices(hypergraph)
};
let mut edge_probs = Vec::with_capacity(selected.len());
let mut edge_offsets = Vec::with_capacity(selected.len() + 1);
edge_offsets.push(0u64);
let mut edge_vertices = Vec::new();
for &edge in &selected {
let hyperedge = &hypergraph.hyperedges[edge as usize];
edge_probs.push(hyperedge.probability);
edge_vertices.extend_from_slice(&hyperedge.vertices);
edge_offsets.push(edge_vertices.len() as u64);
}
let decoder_config = serde_json::to_string(&config.decoder_config).expect("serialize decoder_config");
let loaded = LoadedDecoder::create(
library,
hypergraph.vertex_num,
&edge_probs,
&edge_offsets,
&edge_vertices,
&decoder_config,
)
.unwrap_or_else(|e| panic!("plugin {} failed to build decoder: {e}", config.library.display()));
let active_edges = (!complete_graph).then_some(selected);
Self { loaded, active_edges }
}
fn decode(&mut self, request: DecodeRequest<'_>) -> Result<ParityFactor, DecodeError> {
let sites: Vec<LossSiteView<'_>> = request
.loss
.map(|loss| {
loss.sites
.iter()
.map(|site| LossSiteView {
source_edges: &site.source_edges,
continuation_edges: &site.continuation_edges,
probability: site.probability,
children: &site.children,
heralds: &site.heralds,
})
.collect()
})
.unwrap_or_default();
let host_request = HostDecodeRequest {
syndrome_size: request.syndrome.size,
syndrome_data: &request.syndrome.data,
decoder_seed: request.decoder_seed,
reweights: request.reweights,
loss: request.loss.map(|_| sites.as_slice()),
};
let mut subgraph = Vec::new();
match self.loaded.decode_request(&host_request, &mut subgraph) {
Ok(()) => {
let Some(active_edges) = &self.active_edges else {
return Ok(ParityFactor { subgraph });
};
let subgraph = subgraph
.into_iter()
.map(|index| {
usize::try_from(index)
.ok()
.and_then(|index| active_edges.get(index))
.copied()
.ok_or_else(|| {
DecodeError::Backend(format!(
"decoder plugin returned edge {index}; the loaded graph has {} edges",
active_edges.len()
))
})
})
.collect::<Result<_, _>>()?;
Ok(ParityFactor { subgraph })
}
Err(error) => Err(DecodeError::Backend(error.to_string())),
}
}
fn reset(&mut self) {}
}
pub type DynLibDecoder = ThreadPoolingDecoder<DynLibInstance>;