pub mod dump;
mod measure;
mod report;
mod rule;
mod tier;
pub use crate::extract::dump::{Page as ExtractionPage, Record as DumpDecision};
pub use measure::{CACHE_BYTES, EFFECT_UNIT_BYTES, HOIST_BYTES, Metric, Metrics};
pub use report::{Decision, DecisionKind, Delta, EXTRACT_PASS, Extraction, Reason};
pub use rule::{Rejection, epsilon_pct, judge};
pub use tier::{OptTier, OptimizationBudget, TiePolicy};
use alloc::vec::Vec;
use vize_l0::pass::{Fusability, PassDesc, PassKind, Preserved};
use crate::op::Program;
use crate::placement::facts::Index;
use crate::placement::{Placement, PlacementRecord};
use measure::{Choice, Model};
pub const EXTRACT: PassDesc = PassDesc::new(
EXTRACT_PASS,
PassKind::Optional,
Fusability::Barrier,
Preserved::ALL,
);
pub fn extract(program: &mut Program<'_>, tier: OptTier) -> Extraction {
let (chosen, extraction) = plan(&Index::new(program), tier);
for (record, chosen) in program.placements.iter_mut().zip(chosen) {
record.chosen = chosen;
}
extraction
}
fn plan(index: &Index<'_, '_>, tier: OptTier) -> (Vec<Placement>, Extraction) {
let program = index.program;
let budget = tier.budget();
let model = Model::new(index);
let mut plan = alloc::vec![Choice::INLINE; program.ops.len()];
let before = model.measure(&plan);
let mut current = before;
let mut left = budget.candidate_budget;
let mut decisions = Vec::new();
let mut chosen = Vec::with_capacity(program.placements.len());
for record in program.placements.iter() {
let Some((position, op)) = index
.position(record.op)
.and_then(|position| Some((position, program.ops.get(position)?)))
else {
chosen.push(Placement::Inline);
continue;
};
let span = op.span;
let mut committed = Placement::Inline;
for placement in record.alternatives.iter() {
if placement == Placement::Inline || committed != Placement::Inline {
continue;
}
let choice = Choice {
placement,
leader: record.leader.and_then(|leader| index.position(leader)),
};
let (reason, after) =
if let Some(reason) = blocked(index, &plan, record, position, choice) {
(reason, current)
} else if left == 0 {
(Reason::BudgetExhausted, current)
} else {
left -= 1;
set(&mut plan, position, choice);
let after = model.measure(&plan);
match judge(current, after, &budget) {
Ok(()) => (Reason::Committed, after),
Err(Rejection::Regressed(metric)) => (Reason::Regressed(metric), after),
Err(Rejection::NoImprovement) => (Reason::NoImprovement, after),
}
};
let kind = if reason == Reason::Committed {
committed = placement;
DecisionKind::Applied
} else {
set(&mut plan, position, Choice::INLINE);
DecisionKind::Missed
};
decisions.push(Decision {
op: record.op,
span,
placement,
kind,
reason,
delta: Delta::between(current, after),
budget_left: left,
});
if kind == DecisionKind::Applied {
current = after;
}
}
chosen.push(committed);
}
let extraction = Extraction {
tier,
candidate_budget: budget.candidate_budget,
budget_left: left,
before,
after: current,
decisions,
};
(chosen, extraction)
}
fn set(plan: &mut [Choice], position: usize, choice: Choice) {
if let Some(slot) = plan.get_mut(position) {
*slot = choice;
}
}
fn blocked(
index: &Index<'_, '_>,
plan: &[Choice],
record: &PlacementRecord,
position: usize,
choice: Choice,
) -> Option<Reason> {
match choice.placement {
Placement::Hoist => {
let program = index.program;
let mut region = index.region(program.ops.get(position)?.region);
for _ in 0..=program.regions.len() {
let owner = index.position(region?.owner?)?;
if plan.get(owner)?.placement == Placement::Hoist {
return Some(Reason::Subsumed);
}
region = index.region(program.ops.get(owner)?.region);
}
None
}
Placement::Group => {
let leader = record.leader?;
let mut current = index.keyed_pred(record.op);
for _ in 0..index.program.ops.len() {
let Some(pred) = current else {
return Some(Reason::NotContiguous);
};
if pred == leader {
return None;
}
let member = index
.position(pred)
.and_then(|pred| plan.get(pred).copied());
if member.is_none_or(|member| {
member.placement != Placement::Group || member.leader != choice.leader
}) {
return Some(Reason::NotContiguous);
}
current = index.keyed_pred(pred);
}
Some(Reason::NotContiguous)
}
Placement::Inline | Placement::Cache => None,
}
}