use super::resource_envelope::{Allocation, ResourceEnvelope};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SymbolDomain {
pub lower: u64,
pub upper: u64,
pub alignment: u64,
}
impl SymbolDomain {
pub const fn contains(&self, value: u64) -> bool {
self.alignment != 0
&& self.lower <= self.upper
&& value >= self.lower
&& value <= self.upper
&& (value - self.lower) % self.alignment == 0
}
pub const fn cardinality(&self) -> u64 {
if self.alignment == 0 || self.lower > self.upper {
0
} else {
((self.upper - self.lower) / self.alignment) + 1
}
}
pub const fn contains_domain(&self, other: &Self) -> bool {
if self.alignment == 0
|| other.alignment == 0
|| self.lower > self.upper
|| other.lower > other.upper
|| other.lower < self.lower
|| other.upper > self.upper
|| !self.contains(other.lower)
|| !self.contains(other.upper)
{
return false;
}
if other.lower == other.upper {
return true;
}
other.alignment % self.alignment == 0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AffineResourceExpr<const V: usize> {
pub base: u64,
pub coeffs: [u64; V],
}
impl<const V: usize> AffineResourceExpr<V> {
pub fn evaluate(&self, values: &[u64; V]) -> Option<u64> {
let mut total = self.base;
let mut index = 0usize;
while index < V {
let term = self.coeffs[index].checked_mul(values[index])?;
total = total.checked_add(term)?;
index += 1;
}
Some(total)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SymbolicFamily<const V: usize> {
pub subject_id: u64,
pub family_id: u64,
pub reachability_mask: u64,
pub domains: [SymbolDomain; V],
pub cpu: AffineResourceExpr<V>,
pub memory: AffineResourceExpr<V>,
pub io: AffineResourceExpr<V>,
}
impl<const V: usize> SymbolicFamily<V> {
pub fn concrete_space_saturating(&self) -> u64 {
let mut product = 1u64;
let mut index = 0usize;
while index < V {
product = product.saturating_mul(self.domains[index].cardinality());
index += 1;
}
product
}
pub fn concretize(&self, witness: &SymbolicWitness<V>) -> Result<Allocation, SymbolicRefusal> {
if witness.family_id != self.family_id {
return Err(SymbolicRefusal::FamilyIdMismatch {
expected: self.family_id,
actual: witness.family_id,
});
}
let mut index = 0usize;
while index < V {
let domain = self.domains[index];
if domain.cardinality() == 0 {
return Err(SymbolicRefusal::InvalidDomain { index });
}
if !domain.contains(witness.values[index]) {
return Err(SymbolicRefusal::ValueOutOfDomain {
index,
value: witness.values[index],
});
}
index += 1;
}
let cpu = self
.cpu
.evaluate(&witness.values)
.ok_or(SymbolicRefusal::ArithmeticOverflow)?;
let memory = self
.memory
.evaluate(&witness.values)
.ok_or(SymbolicRefusal::ArithmeticOverflow)?;
let io = self
.io
.evaluate(&witness.values)
.ok_or(SymbolicRefusal::ArithmeticOverflow)?;
Ok(Allocation { cpu, memory, io })
}
pub fn admit_in(
&self,
envelope: &ResourceEnvelope,
witness: &SymbolicWitness<V>,
) -> Result<Allocation, SymbolicRefusal> {
let allocation = self.concretize(witness)?;
if envelope.admits(&allocation) {
Ok(allocation)
} else {
Err(SymbolicRefusal::EnvelopeExceeded {
requested: allocation,
envelope: *envelope,
})
}
}
pub fn subsumes_without_option_loss(&self, other: &Self) -> bool {
if self.subject_id != other.subject_id
|| (self.reachability_mask | other.reachability_mask) != self.reachability_mask
|| self.cpu != other.cpu
|| self.memory != other.memory
|| self.io != other.io
{
return false;
}
let mut index = 0usize;
while index < V {
if !self.domains[index].contains_domain(&other.domains[index]) {
return false;
}
index += 1;
}
true
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SymbolicWitness<const V: usize> {
pub family_id: u64,
pub values: [u64; V],
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SymbolicRefusal {
FamilyIdMismatch {
expected: u64,
actual: u64,
},
InvalidDomain {
index: usize,
},
ValueOutOfDomain {
index: usize,
value: u64,
},
ArithmeticOverflow,
EnvelopeExceeded {
requested: Allocation,
envelope: ResourceEnvelope,
},
InvalidLifetime {
index: usize,
},
}
impl core::fmt::Display for SymbolicRefusal {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(self, f)
}
}
#[cfg(feature = "std")]
impl std::error::Error for SymbolicRefusal {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TimedAllocation {
pub start: u64,
pub end: u64,
pub allocation: Allocation,
}
fn checked_add_allocation(left: Allocation, right: Allocation) -> Option<Allocation> {
Some(Allocation {
cpu: left.cpu.checked_add(right.cpu)?,
memory: left.memory.checked_add(right.memory)?,
io: left.io.checked_add(right.io)?,
})
}
pub fn peak_reusable_usage<const M: usize>(
allocations: &[TimedAllocation; M],
) -> Result<Allocation, SymbolicRefusal> {
let mut index = 0usize;
while index < M {
if allocations[index].start >= allocations[index].end {
return Err(SymbolicRefusal::InvalidLifetime { index });
}
index += 1;
}
let mut peak = Allocation {
cpu: 0,
memory: 0,
io: 0,
};
let mut probe = 0usize;
while probe < M {
let time = allocations[probe].start;
let mut active = Allocation {
cpu: 0,
memory: 0,
io: 0,
};
let mut candidate = 0usize;
while candidate < M {
let item = allocations[candidate];
if item.start <= time && time < item.end {
active = checked_add_allocation(active, item.allocation)
.ok_or(SymbolicRefusal::ArithmeticOverflow)?;
}
candidate += 1;
}
peak.cpu = core::cmp::max(peak.cpu, active.cpu);
peak.memory = core::cmp::max(peak.memory, active.memory);
peak.io = core::cmp::max(peak.io, active.io);
probe += 1;
}
Ok(peak)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PipelineKnobs {
pub load_body_open: bool,
pub body_store_open: bool,
}
pub fn pipeline_latency(
load: u64,
body: u64,
store: u64,
knobs: PipelineKnobs,
) -> Result<u64, SymbolicRefusal> {
match (knobs.load_body_open, knobs.body_store_open) {
(false, false) => load
.checked_add(body)
.and_then(|value| value.checked_add(store))
.ok_or(SymbolicRefusal::ArithmeticOverflow),
(false, true) => {
let load_body = load
.checked_add(body)
.ok_or(SymbolicRefusal::ArithmeticOverflow)?;
Ok(core::cmp::max(load_body, store))
}
(true, false) => {
let body_store = body
.checked_add(store)
.ok_or(SymbolicRefusal::ArithmeticOverflow)?;
Ok(core::cmp::max(load, body_store))
}
(true, true) => Ok(core::cmp::max(load, core::cmp::max(body, store))),
}
}