use vj_codegen::systask::SdfCallSite;
use vj_exec::timing::TimingLive;
use vj_exec::timing::sdf::{
Change, SdfCall, SdfDelta, SdfKey, SdfOutcome, SdfPlan, SpecparamChange, hash_file,
};
use vj_runtime::sdf_annotate::SdfAnnotateCall;
use crate::interconnect::SharedTable;
use crate::sdf_apply::Annotator;
pub(crate) type SdfRefusal = (&'static str, String);
struct Cells {
paths: Vec<vj_exec::timing::specify::PathTimes>,
devices: Vec<vj_exec::timing::Delays>,
checks: Vec<Option<vj_exec::timing::check::CheckSpec>>,
specparams: Vec<(String, f64)>,
}
impl Cells {
fn read(live: &TimingLive, checks: usize) -> Cells {
Cells {
paths: live.paths.lock().unwrap().clone(),
devices: live
.prim
.device
.iter()
.map(|d| *d.lock().unwrap())
.collect(),
checks: (0..checks).map(|i| live.checks.declared_spec(i)).collect(),
specparams: vj_runtime::specparam::snapshot(),
}
}
}
fn call_of(site: &SdfCallSite, file: String) -> Option<SdfAnnotateCall> {
let opt = |k: usize| -> Option<Option<String>> {
site.options[k].clone().map(|o| o.filter(|s| !s.is_empty()))
};
Some(SdfAnnotateCall {
file,
scope: site.scope.clone(),
config_file: opt(0)?,
log_file: opt(1)?,
mtm_spec: opt(2)?,
scale_factors: opt(3)?,
scale_type: opt(4)?,
})
}
pub(crate) fn resolve(
sites: &[SdfCallSite],
live: &TimingLive,
wires: &SharedTable,
checks: usize,
mut annotator: Annotator,
) -> Result<SdfPlan, SdfRefusal> {
vj_runtime::specparam::clear();
let mut calls = Vec::new();
for site in sites {
let dynamic = || {
(
"sdf-annotate-dynamic-path",
format!(
"$sdf_annotate in \"{}\" has an argument that is not a string literal; \
build-aot reads the SDF file at build time",
site.scope
),
)
};
let file = site.file.clone().ok_or_else(dynamic)?;
let call = call_of(site, file).ok_or_else(dynamic)?;
wires.lock().unwrap().take_recorded();
let before = Cells::read(live, checks);
let outcome = match annotator.apply(&call) {
Err(msg) => SdfOutcome::Failed(msg),
Ok(warnings) => {
let after = Cells::read(live, checks);
SdfOutcome::Applied(SdfDelta {
warnings,
log: annotator.take_captured_log(),
paths: diff(&before.paths, &after.paths),
devices: diff(&before.devices, &after.devices),
checks: before
.checks
.iter()
.zip(&after.checks)
.enumerate()
.filter_map(|(i, (b, a))| match (b, a) {
(Some(b), Some(a)) if b != a => Some(Change {
index: i as u32,
before: *b,
after: *a,
}),
_ => None,
})
.collect(),
specparams: specparam_changes(&before.specparams, &after.specparams),
interconnect: wires.lock().unwrap().take_recorded(),
})
}
};
calls.push(SdfCall {
key: SdfKey {
file: call.file.clone(),
scope: call.scope.clone(),
config_file: call.config_file.clone(),
log_file: call.log_file.clone(),
mtm_spec: call.mtm_spec.clone(),
scale_factors: call.scale_factors.clone(),
scale_type: call.scale_type.clone(),
},
file_hash: hash_file(&call.file),
config_hash: call.config_file.as_deref().and_then(hash_file),
outcome,
});
}
vj_runtime::specparam::clear();
Ok(SdfPlan { calls })
}
fn diff<T: Copy + PartialEq>(before: &[T], after: &[T]) -> Vec<Change<T>> {
before
.iter()
.zip(after)
.enumerate()
.filter(|(_, (b, a))| b != a)
.map(|(i, (b, a))| Change {
index: i as u32,
before: *b,
after: *a,
})
.collect()
}
fn specparam_changes(before: &[(String, f64)], after: &[(String, f64)]) -> Vec<SpecparamChange> {
after
.iter()
.filter_map(|(path, value)| {
let old = before.iter().find(|(p, _)| p == path).map(|(_, v)| *v);
(old.map(f64::to_bits) != Some(value.to_bits())).then(|| SpecparamChange {
path: path.clone(),
before: old.map(f64::to_bits),
after: value.to_bits(),
})
})
.collect()
}