use smallvec::SmallVec;
use crate::{
adt::{SmallDenseMap, SmallSet},
dataflow::analyses::{scheduling::*, LivenessAnalysis},
dialects::builtin::Function,
dominance::DominanceInfo,
effects::*,
pass::{Pass, PassExecutionState},
traits::Terminator,
Block, BlockRef, EntityMut, Op, Operation, ProgramPoint, Report, ValueRef,
};
pub struct InstructionScheduler;
impl Pass for InstructionScheduler {
type Target = Function;
fn name(&self) -> &'static str {
"scheduler"
}
fn argument(&self) -> &'static str {
"scheduler"
}
fn can_schedule_on(&self, _name: &crate::OperationName) -> bool {
true
}
fn run_on_operation(
&mut self,
op: EntityMut<'_, Self::Target>,
state: &mut PassExecutionState,
) -> Result<(), Report> {
log::debug!(target: "scheduler", "optimizing instruction schedule for {}", op.as_operation());
let mut operation = op.as_operation_ref();
let entry_block = op.entry_block();
drop(op);
let dominfo = state.analysis_manager().get_analysis::<DominanceInfo>()?;
let liveness = state.analysis_manager().get_analysis_for::<LivenessAnalysis, Function>()?;
let depgraph = build_dependency_graph(entry_block, &operation.borrow(), &liveness);
dbg!(&depgraph);
let treegraph = OrderedTreeGraph::new(&depgraph)
.expect("unable to topologically sort treegraph for block");
dbg!(&treegraph);
self.rewrite(&mut operation.borrow_mut(), &dominfo, &liveness)
}
}
impl InstructionScheduler {
fn rewrite(
&mut self,
op: &mut Operation,
dominfo: &DominanceInfo,
liveness: &LivenessAnalysis,
) -> Result<(), Report> {
todo!()
}
}
fn build_dependency_graph(
block_id: BlockRef,
op: &Operation,
liveness: &LivenessAnalysis,
) -> DependencyGraph {
let mut graph = DependencyGraph::default();
let mut materialized_args = SmallSet::<ValueRef, 4>::default();
let mut block_arg_uses = SmallDenseMap::<ValueRef, SmallSet<BlockRef, 2>>::default();
let block = block_id.borrow();
for inst in block.body().iter() {
materialized_args.clear();
block_arg_uses.clear();
let inst_index = inst.get_or_compute_order();
let inst_id = inst.as_operation_ref();
let node_id = graph.add_node(Node::Inst {
op: inst_id,
pos: inst_index as u16,
});
let pp = ProgramPoint::before(&*inst);
for arg in inst.operands().group(0).iter().copied() {
let value = arg.borrow().as_value_ref();
materialized_args.insert(value);
let arg_node = ArgumentNode::Direct(arg);
graph.add_data_dependency(node_id, arg_node, value, pp);
}
for (result_idx, result) in inst.results().iter().copied().enumerate() {
let result_node = Node::Result {
value: result,
index: result_idx as u8,
};
let result_node_id = graph.add_node(result_node);
graph.add_dependency(result_node_id, node_id);
}
match inst.num_successors() {
0 => {}
1 => {
for arg in inst.successor(0).arguments.into_iter().copied() {
let value = arg.borrow().as_value_ref();
let arg_node = ArgumentNode::Indirect(arg);
graph.add_data_dependency(node_id, arg_node, value, pp);
}
}
_ => {
for succ in inst.successor_iter() {
for arg in succ.arguments.iter() {
let arg = arg.borrow().as_value_ref();
block_arg_uses
.entry(arg)
.or_insert_with(Default::default)
.insert(succ.successor());
}
}
let materialization_threshold = inst.num_successors();
for succ in inst.successor_iter() {
for arg in succ.arguments.iter().copied() {
let value = arg.borrow().as_value_ref();
let is_conditionally_materialized =
block_arg_uses[&value].len() < materialization_threshold;
let must_materialize =
materialized_args.contains(&value) || !is_conditionally_materialized;
let arg_node = if must_materialize {
ArgumentNode::Indirect(arg)
} else {
ArgumentNode::Conditional(arg)
};
graph.add_data_dependency(node_id, arg_node, value, pp);
}
}
}
}
}
assign_effect_dependencies(&mut graph, &block, liveness);
graph
}
fn assign_effect_dependencies(
graph: &mut DependencyGraph,
block: &Block,
liveness: &LivenessAnalysis,
) {
let terminator = {
let op = block.terminator().unwrap();
let index = op.borrow().get_or_compute_order();
Node::Inst {
op,
pos: index as u16,
}
};
let mut reads: Vec<(Node, Option<ValueRef>)> = vec![];
let mut writes: Vec<(Node, Option<ValueRef>)> = vec![];
let mut handle_effects = |op: &Operation,
node: Node,
interface: &dyn EffectOpInterface<MemoryEffect>,
graph: &mut DependencyGraph| {
if interface.has_no_effect() {
return;
}
for effect in interface.effects() {
if matches!(effect.effect(), MemoryEffect::Read | MemoryEffect::Write) {
let value = effect.value();
if let Some(value) = value {
if let Some(last_store) = writes.iter().find_map(|(prev_store, written_to)| {
if written_to.is_none_or(|v| v == value) {
Some(*prev_store)
} else {
None
}
}) {
if !graph.is_reachable_from(node, last_store) {
graph.add_dependency(node, last_store);
}
}
} else if let Some(_symbol) = effect.symbol() {
if let Some((last_store, _)) = writes.last() {
if !graph.is_reachable_from(node, *last_store) {
graph.add_dependency(node, *last_store);
}
}
} else {
if let Some((last_store, _)) = writes.last() {
if !graph.is_reachable_from(node, *last_store) {
graph.add_dependency(node, *last_store);
}
}
}
if matches!(effect.effect(), MemoryEffect::Read) {
reads.push((node, value));
continue;
}
if let Some(value) = value {
if let Some(last_read) = reads.iter().find_map(|(prev_read, read_from)| {
if read_from.is_none_or(|v| v == value) {
Some(*prev_read)
} else {
None
}
}) {
if !graph.is_reachable_from(node, last_read) {
graph.add_dependency(node, last_read);
}
}
} else if let Some(_symbol) = effect.symbol() {
if let Some((last_read, _)) = reads.last() {
if !graph.is_reachable_from(node, *last_read) {
graph.add_dependency(node, *last_read);
}
}
} else {
if let Some((last_read, _)) = reads.last() {
if !graph.is_reachable_from(node, *last_read) {
graph.add_dependency(node, *last_read);
}
}
}
writes.push((node, value));
} else {
if let Some((last_write, _)) = writes.last() {
if !graph.is_reachable_from(node, *last_write) {
graph.add_dependency(node, *last_write);
}
}
if let Some((last_read, _)) = reads.last() {
if !graph.is_reachable_from(node, *last_read) {
graph.add_dependency(node, *last_read);
}
}
reads.push((node, None));
writes.push((node, None));
}
}
};
for op in block.body().iter() {
if op.implements::<dyn Terminator>() {
continue;
}
let inst_index = op.get_or_compute_order();
let node = Node::Inst {
op: op.as_operation_ref(),
pos: inst_index as u16,
};
let has_side_effects = !op.is_memory_effect_free();
if let Some(memory_effects) = op.as_trait::<dyn EffectOpInterface<MemoryEffect>>() {
handle_effects(&op, node, memory_effects, graph)
}
let has_dependents = graph.predecessors(node).any(|pred| {
if pred.dependent.is_result() {
graph.num_predecessors(pred.dependent) > 0
} else {
true
}
});
if has_dependents {
continue;
}
let mut live_results = SmallVec::<[Node; 2]>::default();
for pred in graph.predecessors(node) {
match pred.dependent {
Node::Result { value, .. } => {
let is_live_after =
liveness.is_live_at_end(value as ValueRef, block.as_block_ref());
if is_live_after {
live_results.push(pred.dependent);
}
}
_ => continue,
}
}
let has_live_results = !live_results.is_empty();
for result_node in live_results.into_iter() {
graph.add_dependency(terminator, result_node);
}
if has_side_effects && !has_live_results {
if !graph.is_reachable_from(terminator, node) {
graph.add_dependency(terminator, node);
}
continue;
}
}
}