use smallvec::SmallVec;
use vyre_driver::arm_independence::ArmBindingSummary;
use vyre_driver::pipeline_fusion::{
decide_cross_pipeline_fusion, CrossPipelineConflict, CrossPipelineFusionDecision,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PipelineFusionSegment {
pub start: usize,
pub end: usize,
}
impl PipelineFusionSegment {
#[must_use]
pub fn len(&self) -> usize {
self.end.saturating_sub(self.start)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PipelineFusionBreak {
pub earlier: usize,
pub later: usize,
pub reason: CrossPipelineConflict,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CrossPipelineFusionPlan {
pub segments: SmallVec<[PipelineFusionSegment; 8]>,
pub breaks: SmallVec<[PipelineFusionBreak; 8]>,
}
impl CrossPipelineFusionPlan {
#[must_use]
pub fn contains_fused_segment(&self) -> bool {
self.segments.iter().any(|segment| segment.len() > 1)
}
}
#[must_use]
pub fn plan_cross_pipeline_fusion(summaries: &[ArmBindingSummary]) -> CrossPipelineFusionPlan {
if summaries.is_empty() {
return CrossPipelineFusionPlan {
segments: SmallVec::new(),
breaks: SmallVec::new(),
};
}
let mut segments = SmallVec::new();
let mut breaks = SmallVec::new();
let mut start = 0usize;
for idx in 1..summaries.len() {
match decide_cross_pipeline_fusion(&summaries[idx - 1], &summaries[idx]) {
CrossPipelineFusionDecision::Fuse => {}
CrossPipelineFusionDecision::KeepSeparate { reason } => {
segments.push(PipelineFusionSegment { start, end: idx });
breaks.push(PipelineFusionBreak {
earlier: idx - 1,
later: idx,
reason,
});
start = idx;
}
}
}
segments.push(PipelineFusionSegment {
start,
end: summaries.len(),
});
CrossPipelineFusionPlan { segments, breaks }
}
#[cfg(test)]
mod tests {
use super::*;
fn summary(reads: &[u32], writes: &[u32]) -> ArmBindingSummary {
ArmBindingSummary {
reads: reads.iter().copied().collect(),
writes: writes.iter().copied().collect(),
}
}
#[test]
fn empty_sequence_has_no_segments() {
let plan = plan_cross_pipeline_fusion(&[]);
assert!(plan.segments.is_empty());
assert!(plan.breaks.is_empty());
}
#[test]
fn disjoint_sequence_becomes_one_segment() {
let plan = plan_cross_pipeline_fusion(&[
summary(&[0], &[1]),
summary(&[2], &[3]),
summary(&[4], &[5]),
]);
assert_eq!(
plan.segments.as_slice(),
&[PipelineFusionSegment { start: 0, end: 3 }]
);
assert!(plan.breaks.is_empty());
assert!(plan.contains_fused_segment());
}
#[test]
fn conflict_splits_segments_with_reason() {
let plan = plan_cross_pipeline_fusion(&[
summary(&[0], &[1]),
summary(&[1], &[2]),
summary(&[3], &[4]),
]);
assert_eq!(
plan.segments.as_slice(),
&[
PipelineFusionSegment { start: 0, end: 1 },
PipelineFusionSegment { start: 1, end: 3 },
]
);
assert_eq!(
plan.breaks.as_slice(),
&[PipelineFusionBreak {
earlier: 0,
later: 1,
reason: CrossPipelineConflict::ReadAfterWrite,
}]
);
}
#[test]
fn read_only_overlap_stays_fused() {
let plan = plan_cross_pipeline_fusion(&[summary(&[0], &[1]), summary(&[0, 2], &[3])]);
assert_eq!(
plan.segments.as_slice(),
&[PipelineFusionSegment { start: 0, end: 2 }]
);
}
}