use llvm_native_core::codegen::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand};
use std::collections::{HashMap, HashSet, VecDeque};
#[derive(Clone)]
pub struct MachinePipeliner {
pub pipelines_created: usize,
pub kernel_instructions: usize,
pub best_ii: u32,
}
impl MachinePipeliner {
pub fn new() -> Self {
Self {
pipelines_created: 0,
kernel_instructions: 0,
best_ii: u32::MAX,
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> usize {
self.pipelines_created = 0;
self.kernel_instructions = 0;
let loop_blocks_list = self.find_pipelineable_loops(mf);
for loop_blocks in &loop_blocks_list {
let ii = self.compute_initiation_interval(loop_blocks, mf);
if ii == 0 {
continue; }
let prologue = self.generate_prologue(loop_blocks, ii);
let kernel = self.generate_kernel(loop_blocks, ii);
let epilogue = self.generate_epilogue(loop_blocks, ii);
let kernel_instr_count = kernel.iter().map(|b| b.instructions.len()).sum::<usize>();
if Self::apply_pipeline(mf, loop_blocks, prologue, kernel, epilogue) {
self.pipelines_created += 1;
self.best_ii = self.best_ii.min(ii);
self.kernel_instructions += kernel_instr_count;
}
}
self.pipelines_created
}
fn find_pipelineable_loops(&self, mf: &MachineFunction) -> Vec<Vec<usize>> {
let mut loops = Vec::new();
for (i, bb) in mf.blocks.iter().enumerate() {
for &succ_idx in &bb.successors {
if succ_idx < mf.blocks.len() && succ_idx <= i {
let mut loop_blocks = Vec::new();
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(succ_idx);
visited.insert(succ_idx);
while let Some(current) = queue.pop_front() {
if current > i {
continue; }
loop_blocks.push(current);
for &si in &mf.blocks[current].successors {
if !visited.contains(&si) && si <= i {
visited.insert(si);
queue.push_back(si);
}
}
}
if loop_blocks.len() >= 2 {
loop_blocks.sort();
let existing = loops.iter().any(|l: &Vec<usize>| {
let set_a: HashSet<usize> = l.iter().copied().collect();
let set_b: HashSet<usize> = loop_blocks.iter().copied().collect();
set_a == set_b
});
if !existing {
loops.push(loop_blocks);
}
}
}
}
}
loops
}
fn compute_initiation_interval(&self, loop_blocks: &[usize], mf: &MachineFunction) -> u32 {
let instructions = Self::collect_loop_instructions(loop_blocks, mf);
if instructions.is_empty() {
return 0;
}
let resource_ii = ((instructions.len() as f64) / 2.0).ceil() as u32;
let dep_graph = Self::build_dependence_graph(&instructions);
let rec_ii = Self::compute_recurrence_mii(&dep_graph);
let ii = resource_ii.max(rec_ii);
if ii > 64 {
return 0; }
ii.max(1)
}
fn generate_prologue(&self, loop_blocks: &[usize], ii: u32) -> Vec<MachineBasicBlock> {
let mut prologue = Vec::new();
if ii <= 1 {
return prologue; }
let all_instructions =
Self::collect_loop_instructions(loop_blocks, &MachineFunction::new("_dummy"));
for stage in 0..(ii - 1) {
let mut block = MachineBasicBlock {
name: format!("pipeline.prologue.{}", stage),
instructions: Vec::new(),
successors: Vec::new(),
..Default::default()
};
let iter_count = stage + 1;
for _iter in 0..iter_count.min(all_instructions.len() as u32) {
for instr in &all_instructions {
block.instructions.push(instr.clone());
}
}
prologue.push(block);
}
let p_len = prologue.len();
for i in 0..p_len {
if i + 1 < p_len {
prologue[i].successors.push(i + 1);
} else {
prologue[i].successors.push(p_len); }
}
prologue
}
fn generate_kernel(&self, loop_blocks: &[usize], ii: u32) -> Vec<MachineBasicBlock> {
let mut kernel_blocks = Vec::new();
let all_instructions =
Self::collect_loop_instructions(loop_blocks, &MachineFunction::new("_dummy"));
let schedule = self.modulo_schedule(&all_instructions, ii);
for (stage, instr_indices) in schedule.iter().enumerate() {
let mut block = MachineBasicBlock {
name: format!("pipeline.kernel.s{}", stage),
instructions: Vec::new(),
successors: Vec::new(),
..Default::default()
};
for &idx in instr_indices {
if idx < all_instructions.len() {
block.instructions.push(all_instructions[idx].clone());
}
}
kernel_blocks.push(block);
}
let p_len = (ii.saturating_sub(1)) as usize; let k_len = kernel_blocks.len();
let epi_start = p_len + k_len;
for i in 0..k_len {
let next = (i + 1) % k_len;
if next == 0 {
kernel_blocks[i].successors.push(epi_start); } else {
kernel_blocks[i].successors.push(p_len + next);
}
}
kernel_blocks
}
fn generate_epilogue(&self, loop_blocks: &[usize], ii: u32) -> Vec<MachineBasicBlock> {
let mut epilogue = Vec::new();
if ii <= 1 {
return epilogue;
}
let all_instructions =
Self::collect_loop_instructions(loop_blocks, &MachineFunction::new("_dummy"));
for stage in 0..(ii - 1) {
let mut block = MachineBasicBlock {
name: format!("pipeline.epilogue.{}", stage),
instructions: Vec::new(),
successors: Vec::new(),
..Default::default()
};
let iter_count = (ii - 1) - stage;
for _iter in 0..iter_count.min(all_instructions.len() as u32) {
for instr in &all_instructions {
block.instructions.push(instr.clone());
}
}
epilogue.push(block);
}
let p_len = (ii.saturating_sub(1)) as usize; let k_len_guess = 1usize; let epi_base = p_len + k_len_guess;
for i in 0..epilogue.len() {
if i + 1 < epilogue.len() {
epilogue[i].successors.push(epi_base + i + 1);
}
}
epilogue
}
fn modulo_schedule(&self, instructions: &[MachineInstr], ii: u32) -> Vec<Vec<usize>> {
let ii = ii as usize;
if ii == 0 || instructions.is_empty() {
return vec![vec![]];
}
let dep_graph = Self::build_dependence_graph(instructions);
let asap = Self::compute_asap(&dep_graph, instructions.len());
let alap = Self::compute_alap(&dep_graph, instructions.len(), ii);
let mut priorities: Vec<(usize, i32)> = (0..instructions.len())
.map(|i| {
let slack = alap[i] as i32 - asap[i] as i32;
(i, slack)
})
.collect();
priorities.sort_by_key(|&(_, slack)| slack);
let mut schedule: Vec<Vec<usize>> = vec![Vec::new(); ii];
let mut resource_used: Vec<usize> = vec![0; ii]; let max_per_slot = 2;
for &(instr_idx, _) in &priorities {
let mut scheduled = false;
let start_cycle = asap[instr_idx];
for offset in 0..ii {
let slot = (start_cycle + offset) % ii;
if resource_used[slot] < max_per_slot {
let mut deps_satisfied = true;
for pred in &dep_graph[instr_idx] {
let pred_slot =
schedule.iter().position(|s| s.contains(pred)).unwrap_or(ii);
let pred_mod = pred_slot % ii;
if pred_mod == slot && instr_idx != *pred {
deps_satisfied = false;
break;
}
}
if deps_satisfied || dep_graph[instr_idx].is_empty() {
schedule[slot].push(instr_idx);
resource_used[slot] += 1;
scheduled = true;
break;
}
}
}
if !scheduled {
for slot in 0..ii {
if resource_used[slot] < max_per_slot {
schedule[slot].push(instr_idx);
resource_used[slot] += 1;
break;
}
}
}
}
schedule
}
fn build_dependence_graph(instructions: &[MachineInstr]) -> Vec<Vec<usize>> {
let n = instructions.len();
let mut graph: Vec<Vec<usize>> = vec![Vec::new(); n];
let mut def_map: HashMap<u32, usize> = HashMap::new();
for (i, instr) in instructions.iter().enumerate() {
for operand in &instr.operands {
if let MachineOperand::Reg(reg) = operand {
if let Some(&def_idx) = def_map.get(reg) {
if def_idx != i {
graph[def_idx].push(i);
}
}
}
}
if let Some(def_reg) = instr.def {
def_map.insert(def_reg, i);
}
}
for i in 0..n {
let mut to_remove = HashSet::new();
for &j in &graph[i] {
for &k in &graph[j] {
if graph[i].contains(&k) {
to_remove.insert(k);
}
}
}
graph[i].retain(|x| !to_remove.contains(x));
}
graph
}
fn compute_asap(dep_graph: &[Vec<usize>], n: usize) -> Vec<usize> {
let mut pred_graph: Vec<Vec<usize>> = vec![Vec::new(); n];
for i in 0..n {
for &j in &dep_graph[i] {
pred_graph[j].push(i);
}
}
let mut times = vec![0usize; n];
let mut changed = true;
while changed {
changed = false;
for i in 0..n {
let max_pred_time = pred_graph[i]
.iter()
.map(|&pred| times[pred] + 1)
.max()
.unwrap_or(0);
if max_pred_time > times[i] {
times[i] = max_pred_time;
changed = true;
}
}
}
times
}
fn compute_alap(dep_graph: &[Vec<usize>], n: usize, ii: usize) -> Vec<usize> {
let mut times = vec![ii - 1; n];
let mut changed = true;
while changed {
changed = false;
for i in 0..n {
let min_succ_time = dep_graph[i]
.iter()
.filter_map(|&succ| times[succ].checked_sub(1))
.min()
.unwrap_or(ii - 1);
if min_succ_time < times[i] {
times[i] = min_succ_time;
changed = true;
}
}
}
times
}
fn compute_recurrence_mii(dep_graph: &[Vec<usize>]) -> u32 {
let n = dep_graph.len();
if n == 0 {
return 1;
}
let mut max_cycle_len = 0usize;
for start in 0..n {
let mut visited = vec![false; n];
let mut stack = Vec::new();
let mut on_stack = vec![false; n];
fn dfs(
node: usize,
graph: &[Vec<usize>],
visited: &mut [bool],
on_stack: &mut [bool],
stack: &mut Vec<usize>,
max_cycle: &mut usize,
) {
visited[node] = true;
on_stack[node] = true;
stack.push(node);
for &neighbor in &graph[node] {
if !visited[neighbor] {
dfs(neighbor, graph, visited, on_stack, stack, max_cycle);
} else if on_stack[neighbor] {
if let Some(pos) = stack.iter().position(|&x| x == neighbor) {
let cycle_len = stack.len() - pos;
*max_cycle = (*max_cycle).max(cycle_len);
}
}
}
stack.pop();
on_stack[node] = false;
}
if !visited[start] {
dfs(
start,
dep_graph,
&mut visited,
&mut on_stack,
&mut stack,
&mut max_cycle_len,
);
}
}
if max_cycle_len == 0 {
1
} else {
((max_cycle_len as f64) / 1.0).ceil() as u32
}
}
fn collect_loop_instructions(loop_blocks: &[usize], mf: &MachineFunction) -> Vec<MachineInstr> {
let mut instructions = Vec::new();
for &idx in loop_blocks {
if idx < mf.blocks.len() {
instructions.extend(mf.blocks[idx].instructions.clone());
}
}
instructions
}
fn apply_pipeline(
mf: &mut MachineFunction,
loop_blocks: &[usize],
mut prologue: Vec<MachineBasicBlock>,
mut kernel: Vec<MachineBasicBlock>,
mut epilogue: Vec<MachineBasicBlock>,
) -> bool {
if loop_blocks.is_empty() {
return false;
}
let insert_pos = *loop_blocks.iter().min().unwrap_or(&0);
let mut indices: Vec<usize> = loop_blocks.to_vec();
indices.sort_by(|a, b| b.cmp(a)); for &idx in &indices {
if idx < mf.blocks.len() {
mf.blocks.remove(idx);
}
}
let insert_idx = insert_pos.min(mf.blocks.len());
for block in prologue
.iter_mut()
.chain(kernel.iter_mut())
.chain(epilogue.iter_mut())
{
for succ in &mut block.successors {
*succ += insert_idx;
}
}
let mut new_blocks = Vec::new();
new_blocks.extend(prologue);
new_blocks.extend(kernel);
new_blocks.extend(epilogue);
let _count = new_blocks.len();
for block in new_blocks.into_iter().rev() {
mf.blocks.insert(insert_idx, block);
}
true
}
}
impl Default for MachinePipeliner {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct SchedNode {
pub instr_idx: usize,
pub block_idx: usize,
pub asap: i32,
pub alap: i32,
pub scheduled_cycle: Option<i32>,
pub slack: i32,
pub height: i32,
pub depth: i32,
}
impl SchedNode {
pub fn new(block_idx: usize, instr_idx: usize) -> Self {
Self {
instr_idx,
block_idx,
asap: 0,
alap: 0,
scheduled_cycle: None,
slack: 0,
height: 0,
depth: 0,
}
}
pub fn is_scheduled(&self) -> bool {
self.scheduled_cycle.is_some()
}
pub fn stage(&self, ii: i32) -> Option<i32> {
self.scheduled_cycle.map(|cycle| cycle / ii)
}
}
#[derive(Debug, Clone)]
pub struct NodeSet {
pub nodes: Vec<SchedNode>,
pub unscheduled: HashSet<usize>,
pub order: Vec<usize>,
pub ii: i32,
}
impl NodeSet {
pub fn new(nodes: Vec<SchedNode>, ii: i32) -> Self {
let unscheduled: HashSet<usize> = (0..nodes.len()).collect();
let mut order: Vec<usize> = (0..nodes.len()).collect();
order.sort_by(|&a, &b| {
nodes[b]
.height
.cmp(&nodes[a].height)
.then_with(|| nodes[b].depth.cmp(&nodes[a].depth))
});
Self {
nodes,
unscheduled,
order,
ii,
}
}
pub fn next_node(&self) -> Option<usize> {
self.order
.iter()
.find(|&&idx| self.unscheduled.contains(&idx))
.copied()
}
pub fn schedule(&mut self, node_idx: usize, cycle: i32) {
if let Some(node) = self.nodes.get_mut(node_idx) {
node.scheduled_cycle = Some(cycle);
}
self.unscheduled.remove(&node_idx);
}
pub fn all_scheduled(&self) -> bool {
self.unscheduled.is_empty()
}
pub fn remaining(&self) -> usize {
self.unscheduled.len()
}
}
#[derive(Debug, Clone)]
pub struct ResourceUsage {
pub resource_id: u32,
pub available: u32,
pub usage_per_instr: u32,
pub total_usage: u32,
}
impl ResourceUsage {
pub fn new(resource_id: u32, available: u32) -> Self {
Self {
resource_id,
available,
usage_per_instr: 0,
total_usage: 0,
}
}
pub fn res_mii(&self) -> u32 {
if self.available == 0 {
return u32::MAX;
}
(self.total_usage + self.available - 1) / self.available
}
}
pub struct ResourceIICalc {
pub resources: Vec<ResourceUsage>,
pub res_mii: u32,
}
impl ResourceIICalc {
pub fn new() -> Self {
let resources = vec![
ResourceUsage::new(0, 2), ResourceUsage::new(1, 1), ResourceUsage::new(2, 1), ResourceUsage::new(3, 1), ResourceUsage::new(4, 1), ];
Self {
resources,
res_mii: 1,
}
}
pub fn add_instruction(&mut self, opcode: u32) {
let resource_idx = match opcode {
0 => Some(3), 2 | 3 => Some(1), 4 | 5 => Some(2), 10..=19 => Some(4), _ => Some(0), };
if let Some(idx) = resource_idx {
if let Some(res) = self.resources.get_mut(idx) {
res.total_usage += 1;
}
}
}
pub fn compute_res_mii(&mut self) -> u32 {
self.res_mii = self
.resources
.iter()
.map(|r| r.res_mii())
.max()
.unwrap_or(1);
self.res_mii
}
pub fn reset(&mut self) {
for res in &mut self.resources {
res.total_usage = 0;
}
self.res_mii = 1;
}
}
impl Default for ResourceIICalc {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct RecurrenceMII {
pub rec_mii: u32,
pub cycles: Vec<CycleInfo>,
}
#[derive(Debug, Clone)]
pub struct CycleInfo {
pub nodes: Vec<usize>,
pub total_latency: u32,
pub total_distance: u32,
pub mii_contribution: u32,
}
impl RecurrenceMII {
pub fn new() -> Self {
Self {
rec_mii: 1,
cycles: Vec::new(),
}
}
pub fn find_cycles(&mut self, edges: &[(usize, usize, u32, u32)], num_nodes: usize) {
self.cycles.clear();
let mut adj: Vec<Vec<(usize, u32, u32)>> = vec![Vec::new(); num_nodes];
for &(src, dst, lat, dist) in edges {
adj[src].push((dst, lat, dist));
}
for start in 0..num_nodes {
let mut visited = vec![false; num_nodes];
let mut path = Vec::new();
self.dfs_find_cycle(start, start, &adj, &mut visited, &mut path);
}
self.rec_mii = 1;
for cycle in &self.cycles {
if cycle.total_distance > 0 {
let mii = (cycle.total_latency + cycle.total_distance - 1) / cycle.total_distance;
self.rec_mii = self.rec_mii.max(mii);
let cii = cycle.mii_contribution;
self.rec_mii = self.rec_mii.max(cii);
}
}
}
fn dfs_find_cycle(
&mut self,
current: usize,
target: usize,
adj: &[Vec<(usize, u32, u32)>],
visited: &mut [bool],
path: &mut Vec<(usize, u32, u32)>,
) {
if current == target && !path.is_empty() {
let total_latency: u32 = path.iter().map(|(_, lat, _)| *lat).sum();
let total_distance: u32 = path.iter().map(|(_, _, dist)| *dist).sum();
let mii_contribution = if total_distance > 0 {
(total_latency + total_distance - 1) / total_distance
} else {
total_latency
};
let nodes: Vec<usize> = path.iter().map(|(n, _, _)| *n).collect();
self.cycles.push(CycleInfo {
nodes,
total_latency,
total_distance,
mii_contribution,
});
return;
}
visited[current] = true;
for &(next, latency, distance) in &adj[current] {
if next == target || !visited[next] {
path.push((current, latency, distance));
self.dfs_find_cycle(next, target, adj, visited, path);
path.pop();
}
}
visited[current] = false;
}
}
impl Default for RecurrenceMII {
fn default() -> Self {
Self::new()
}
}
pub struct SMSScheduler {
pub resource_ii: ResourceIICalc,
pub recurrence_ii: RecurrenceMII,
pub min_ii: u32,
pub schedule: Vec<Option<(i32, i32)>>,
pub max_ii: u32,
}
impl SMSScheduler {
pub fn new() -> Self {
Self {
resource_ii: ResourceIICalc::new(),
recurrence_ii: RecurrenceMII::new(),
min_ii: 1,
schedule: Vec::new(),
max_ii: 64,
}
}
pub fn schedule(
&mut self,
nodes: &mut [SchedNode],
edges: &[(usize, usize, u32, u32)],
opcodes: &[u32],
) -> bool {
self.resource_ii.reset();
for &opcode in opcodes {
self.resource_ii.add_instruction(opcode);
}
let res_mii = self.resource_ii.compute_res_mii();
self.recurrence_ii.find_cycles(edges, nodes.len());
let rec_mii = self.recurrence_ii.rec_mii;
let mut ii = res_mii.max(rec_mii).max(1) as i32;
self.min_ii = ii as u32;
while (ii as u32) <= self.max_ii {
let mut node_set = NodeSet::new(nodes.to_vec(), ii);
self.compute_asap_alap(&mut node_set.nodes, edges);
let mut success = true;
let mut resource_table: HashMap<i32, HashSet<u32>> = HashMap::new();
while !node_set.all_scheduled() {
if let Some(node_idx) = node_set.next_node() {
let cycle = self.find_slot(node_idx, &node_set, &resource_table, edges);
if let Some(c) = cycle {
node_set.schedule(node_idx, c);
let res_id = opcodes
.get(node_idx)
.map(|&op| match op {
0 => 3u32,
2 | 3 => 1,
4 | 5 => 2,
10..=19 => 4,
_ => 0,
})
.unwrap_or(0);
resource_table.entry(c).or_default().insert(res_id);
} else {
success = false;
break;
}
} else {
break;
}
}
if success && node_set.all_scheduled() {
self.schedule = node_set
.nodes
.iter()
.map(|n| n.scheduled_cycle.map(|c| (c, c / ii)))
.collect();
self.min_ii = ii as u32;
return true;
}
ii += 1;
}
false
}
fn compute_asap_alap(&self, nodes: &mut [SchedNode], edges: &[(usize, usize, u32, u32)]) {
for node in nodes.iter_mut() {
node.asap = 0;
}
for &(src, dst, latency, _dist) in edges {
let src_asap = nodes[src].asap;
nodes[dst].asap = nodes[dst].asap.max(src_asap + latency as i32);
}
let max_asap = nodes.iter().map(|n| n.asap).max().unwrap_or(0);
for node in nodes.iter_mut() {
node.alap = max_asap;
}
for &(src, dst, latency, _dist) in edges.iter().rev() {
let dst_alap = nodes[dst].alap;
nodes[src].alap = nodes[src].alap.min(dst_alap - latency as i32);
}
for node in nodes.iter_mut() {
node.slack = node.alap - node.asap;
node.height = node.alap;
node.depth = node.asap;
}
}
fn find_slot(
&self,
node_idx: usize,
node_set: &NodeSet,
resource_table: &HashMap<i32, HashSet<u32>>,
edges: &[(usize, usize, u32, u32)],
) -> Option<i32> {
let node = &node_set.nodes[node_idx];
let ii = node_set.ii;
for cycle_offset in 0..ii {
let cycle = node.asap + cycle_offset;
let mut valid = true;
for &(src, dst, latency, dist) in edges {
if dst != node_idx {
continue;
}
if let Some(&pred_idx) = node_set.order.iter().find(|&&i| i == src) {
if let Some(pred_cycle) = node_set.nodes[pred_idx].scheduled_cycle {
let min_cycle = pred_cycle + latency as i32 - dist as i32 * ii;
if cycle < min_cycle {
valid = false;
break;
}
}
}
}
if !valid {
continue;
}
let mod_cycle = cycle % ii;
if let Some(used_res) = resource_table.get(&mod_cycle) {
if used_res.len() >= 4 {
continue;
}
}
return Some(cycle);
}
None
}
}
impl Default for SMSScheduler {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PipelineStage {
pub stage: i32,
pub instructions: Vec<MachineInstr>,
pub source_block: usize,
}
pub struct KernelBuilder {
pub ii: u32,
pub num_stages: u32,
pub stages: Vec<Vec<MachineInstr>>,
}
impl KernelBuilder {
pub fn new(ii: u32, num_stages: u32) -> Self {
Self {
ii,
num_stages,
stages: vec![Vec::new(); num_stages as usize],
}
}
pub fn add_to_stage(&mut self, stage: u32, instr: MachineInstr) {
if (stage as usize) < self.stages.len() {
self.stages[stage as usize].push(instr);
}
}
pub fn build_prologue(&self) -> Vec<MachineBasicBlock> {
let mut blocks = Vec::new();
for prologue_stage in 0..self.num_stages.saturating_sub(1) {
let mut block = MachineBasicBlock {
name: format!("prologue_{}", prologue_stage),
instructions: Vec::new(),
successors: Vec::new(),
..Default::default()
};
for s in 0..=prologue_stage {
let stage_instrs = &self.stages[s as usize];
for instr in stage_instrs {
block.instructions.push(instr.clone());
}
}
blocks.push(block);
}
blocks
}
pub fn build_kernel(&self) -> MachineBasicBlock {
let mut kernel = MachineBasicBlock {
id: 0,
name: "kernel".to_string(),
instructions: Vec::new(),
successors: Vec::new(),
predecessors: Vec::new(),
is_entry: false,
};
for stage_instrs in &self.stages {
for instr in stage_instrs {
kernel.instructions.push(instr.clone());
}
}
kernel
}
pub fn build_epilogue(&self) -> Vec<MachineBasicBlock> {
let mut blocks = Vec::new();
for epilogue_idx in 1..self.num_stages {
let mut block = MachineBasicBlock {
name: format!("epilogue_{}", epilogue_idx),
instructions: Vec::new(),
successors: Vec::new(),
..Default::default()
};
for s in epilogue_idx..self.num_stages {
let stage_instrs = &self.stages[s as usize];
for instr in stage_instrs {
block.instructions.push(instr.clone());
}
}
blocks.push(block);
}
blocks
}
pub fn total_instructions(&self) -> usize {
self.stages.iter().map(|s| s.len()).sum()
}
}
pub struct EnhancedPipeliner {
pub base: MachinePipeliner,
pub sms: SMSScheduler,
pub res_ii: ResourceIICalc,
pub loops_analyzed: usize,
pub sms_attempts: usize,
}
impl EnhancedPipeliner {
pub fn new() -> Self {
Self {
base: MachinePipeliner::new(),
sms: SMSScheduler::new(),
res_ii: ResourceIICalc::new(),
loops_analyzed: 0,
sms_attempts: 0,
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> usize {
self.loops_analyzed = 0;
self.sms_attempts = 0;
let base_result = self.base.run_on_function(mf);
self.loops_analyzed = self.base.pipelines_created;
base_result
}
pub fn run_sms_on_function(&mut self, mf: &mut MachineFunction) -> usize {
let loops = self.base.find_pipelineable_loops(mf);
let mut pipelines = 0;
for loop_blocks in loops {
self.loops_analyzed += 1;
let mut all_instrs: Vec<(usize, usize, &MachineInstr)> = Vec::new();
let mut opcodes: Vec<u32> = Vec::new();
for &block_idx in &loop_blocks {
let block = &mf.blocks[block_idx];
for (instr_idx, instr) in block.instructions.iter().enumerate() {
all_instrs.push((block_idx, instr_idx, instr));
opcodes.push(instr.opcode);
}
}
if all_instrs.is_empty() {
continue;
}
let mut nodes: Vec<SchedNode> = all_instrs
.iter()
.enumerate()
.map(|(_i, &(block_idx, instr_idx, _))| SchedNode::new(block_idx, instr_idx))
.collect();
let mut edges: Vec<(usize, usize, u32, u32)> = Vec::new();
for i in 0..nodes.len().saturating_sub(1) {
edges.push((i, i + 1, 1, 0));
}
self.sms_attempts += 1;
if self.sms.schedule(&mut nodes, &edges, &opcodes) {
let num_stages = self.sms.min_ii;
let mut builder = KernelBuilder::new(num_stages, num_stages);
for (i, _node) in nodes.iter().enumerate() {
if let Some((_cycle, stage)) = self.sms.schedule.get(i).copied().flatten() {
let stage_idx = (stage.rem_euclid(num_stages as i32)) as u32;
if i < all_instrs.len() {
builder.add_to_stage(stage_idx, all_instrs[i].2.clone());
}
}
}
let prologue = builder.build_prologue();
let kernel = builder.build_kernel();
let epilogue = builder.build_epilogue();
if MachinePipeliner::apply_pipeline(
mf,
&loop_blocks,
prologue,
vec![kernel],
epilogue,
) {
pipelines += 1;
}
}
}
pipelines
}
pub fn print_stats(&self) {
eprintln!(
"EnhancedPipeliner: {} loops analyzed, {} SMS attempts",
self.loops_analyzed, self.sms_attempts
);
eprintln!(" Min II: {}", self.sms.min_ii);
eprintln!(" ResMII: {}", self.res_ii.res_mii);
}
}
impl Default for EnhancedPipeliner {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ModuloVariableExpansion {
pub ii: u32,
pub num_stages: u32,
pub register_copies: HashMap<u32, Vec<u32>>,
pub next_vreg: u32,
pub has_rotating_regs: bool,
}
impl ModuloVariableExpansion {
pub fn new(ii: u32, num_stages: u32, has_rotating_regs: bool) -> Self {
Self {
ii,
num_stages,
register_copies: HashMap::new(),
next_vreg: 10000,
has_rotating_regs,
}
}
pub fn expand_variable(&mut self, original_reg: u32, distance: u32) -> Vec<u32> {
let num_copies = if self.has_rotating_regs {
((distance + self.ii - 1) / self.ii).max(1)
} else {
distance.max(1)
};
let mut copies = Vec::with_capacity(num_copies as usize);
for _ in 0..num_copies {
let new_vreg = self.next_vreg;
self.next_vreg += 1;
copies.push(new_vreg);
}
self.register_copies.insert(original_reg, copies.clone());
copies
}
pub fn get_register_for_stage(&self, original_reg: u32, stage: u32) -> Option<u32> {
if self.has_rotating_regs {
self.register_copies.get(&original_reg).map(|copies| {
let idx = (stage as usize) % copies.len();
copies[idx]
})
} else {
self.register_copies
.get(&original_reg)
.and_then(|copies| copies.get(stage as usize).copied())
}
}
pub fn is_expanded(&self, reg: u32) -> bool {
self.register_copies.contains_key(®)
}
pub fn num_copies(&self, reg: u32) -> usize {
self.register_copies.get(®).map(|c| c.len()).unwrap_or(0)
}
pub fn all_copies(&self) -> &HashMap<u32, Vec<u32>> {
&self.register_copies
}
pub fn rewrite_instructions(
&self,
instructions: &[MachineInstr],
stage_map: &HashMap<usize, u32>,
) -> Vec<MachineInstr> {
instructions
.iter()
.enumerate()
.map(|(idx, instr)| {
let stage = stage_map.get(&idx).copied().unwrap_or(0);
let mut new_instr = instr.clone();
if let Some(def) = new_instr.def {
if let Some(&new_def) = self.get_register_for_stage(def, stage).as_ref() {
new_instr.def = Some(new_def);
}
}
for operand in &mut new_instr.operands {
match operand {
MachineOperand::Reg(r) => {
if let Some(&new_r) = self.get_register_for_stage(*r, stage).as_ref() {
*operand = MachineOperand::Reg(new_r);
}
}
_ => {}
}
}
new_instr
})
.collect()
}
pub fn print_stats(&self) {
eprintln!(
"ModuloVariableExpansion (II={}, stages={}):",
self.ii, self.num_stages
);
for (orig, copies) in &self.register_copies {
eprintln!(" r{} -> {} copies: {:?}", orig, copies.len(), copies);
}
}
}
impl Default for ModuloVariableExpansion {
fn default() -> Self {
Self::new(1, 1, false)
}
}
#[derive(Debug, Clone)]
pub struct RotatingRegisterFile {
pub rotate_base: u32,
pub rotate_count: u32,
pub current_offset: u32,
pub enabled: bool,
}
impl RotatingRegisterFile {
pub fn new(base: u32, count: u32) -> Self {
Self {
rotate_base: base,
rotate_count: count,
current_offset: 0,
enabled: count > 0,
}
}
pub fn physical_register(&self, logical_reg: u32) -> u32 {
if !self.enabled || logical_reg < self.rotate_base {
return logical_reg;
}
let offset = (logical_reg - self.rotate_base + self.current_offset) % self.rotate_count;
self.rotate_base + offset
}
pub fn rotate(&mut self) {
if self.enabled {
self.current_offset = (self.current_offset + 1) % self.rotate_count;
}
}
pub fn reset(&mut self) {
self.current_offset = 0;
}
pub fn is_rotating(&self, logical_reg: u32) -> bool {
self.enabled
&& logical_reg >= self.rotate_base
&& logical_reg < self.rotate_base + self.rotate_count
}
pub fn unroll_registers(&self, logical_reg: u32, num_stages: u32) -> Vec<u32> {
let mut regs = Vec::with_capacity(num_stages as usize);
for stage in 0..num_stages {
let offset = (logical_reg - self.rotate_base + stage) % self.rotate_count;
regs.push(self.rotate_base + offset);
}
regs
}
}
impl Default for RotatingRegisterFile {
fn default() -> Self {
Self::new(32, 0)
}
}
#[derive(Debug, Clone)]
pub struct LoopCarriedDep {
pub from: usize,
pub to: usize,
pub distance: u32,
pub latency: u32,
pub dep_type: DepType,
pub is_recurrence: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DepType {
RAW,
WAR,
WAW,
}
#[derive(Debug, Clone)]
pub struct LoopCarriedAnalyzer {
pub dependences: Vec<LoopCarriedDep>,
pub max_distance: u32,
pub rec_mii: u32,
}
impl LoopCarriedAnalyzer {
pub fn new() -> Self {
Self {
dependences: Vec::new(),
max_distance: 0,
rec_mii: 0,
}
}
pub fn analyze(&mut self, dep_graph: &[Vec<usize>], backedge_sources: &[usize]) {
self.dependences.clear();
self.max_distance = 0;
for from in 0..dep_graph.len() {
for &to in &dep_graph[from] {
let is_backedge =
backedge_sources.contains(&from) || backedge_sources.contains(&to);
let distance: u32 = if is_backedge {
1
} else if to <= from {
((from - to) as u32).max(1)
} else {
0
};
let dep = LoopCarriedDep {
from,
to,
distance,
latency: 1,
dep_type: DepType::RAW,
is_recurrence: distance > 0,
};
self.max_distance = self.max_distance.max(distance);
self.dependences.push(dep);
}
}
self.rec_mii = self
.dependences
.iter()
.filter(|d| d.is_recurrence)
.map(|d| {
if d.distance == 0 {
1
} else {
((d.latency + d.distance - 1) / d.distance)
}
})
.max()
.unwrap_or(1);
}
pub fn recurrence_edges(&self) -> Vec<&LoopCarriedDep> {
self.dependences
.iter()
.filter(|d| d.is_recurrence)
.collect()
}
pub fn critical_recurrence(&self) -> Option<&LoopCarriedDep> {
self.dependences
.iter()
.filter(|d| d.is_recurrence)
.max_by_key(|d| {
if d.distance == 0 {
1
} else {
(d.latency + d.distance - 1) / d.distance
}
})
}
pub fn print(&self) {
eprintln!("LoopCarriedAnalyzer:");
eprintln!(" RecMII: {}", self.rec_mii);
eprintln!(" Max distance: {}", self.max_distance);
eprintln!(" Recurrence edges:");
for dep in self.recurrence_edges() {
eprintln!(
" {} -> {} (distance={}, latency={})",
dep.from, dep.to, dep.distance, dep.latency
);
}
}
}
impl Default for LoopCarriedAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct IssueSlot {
pub instructions: Vec<usize>,
pub resource_usage: Vec<u32>,
pub full: bool,
}
#[derive(Debug, Clone)]
pub struct MultiIssueScheduler {
pub ii: u32,
pub issue_width: u32,
pub num_resources: usize,
pub resource_counts: Vec<u32>,
pub slots: Vec<IssueSlot>,
}
impl MultiIssueScheduler {
pub fn new(ii: u32, issue_width: u32, resource_counts: Vec<u32>) -> Self {
let num_resources = resource_counts.len();
let slots: Vec<IssueSlot> = (0..ii)
.map(|_| IssueSlot {
instructions: Vec::new(),
resource_usage: vec![0; num_resources],
full: false,
})
.collect();
Self {
ii,
issue_width,
num_resources,
resource_counts,
slots,
}
}
pub fn try_schedule(&mut self, instr_idx: usize, slot: u32, resources: &[usize]) -> bool {
if slot >= self.ii {
return false;
}
let slot_idx = slot as usize;
let s = &mut self.slots[slot_idx];
if s.instructions.len() >= self.issue_width as usize {
return false;
}
for &res in resources {
if res < self.num_resources {
if s.resource_usage[res] >= self.resource_counts[res] {
return false;
}
}
}
for &res in resources {
if res < self.num_resources {
s.resource_usage[res] += 1;
}
}
s.instructions.push(instr_idx);
if s.instructions.len() >= self.issue_width as usize {
s.full = true;
}
true
}
pub fn find_earliest_slot(
&self,
start_slot: u32,
resources: &[usize],
predecessors: &[usize],
pred_slots: &HashMap<usize, u32>,
) -> Option<u32> {
for offset in 0..self.ii {
let slot = (start_slot + offset) % self.ii;
let deps_satisfied = predecessors.iter().all(|pred| {
pred_slots.get(pred).map_or(true, |&ps| {
let pred_mod = ps % self.ii;
pred_mod < slot || (pred_mod == slot && ps < slot)
})
});
if !deps_satisfied {
continue;
}
let s = &self.slots[slot as usize];
if s.full {
continue;
}
let mut resource_ok = true;
for &res in resources {
if res < self.num_resources {
if s.resource_usage[res] >= self.resource_counts[res] {
resource_ok = false;
break;
}
}
}
if resource_ok {
return Some(slot);
}
}
None
}
pub fn total_instructions(&self) -> usize {
self.slots.iter().map(|s| s.instructions.len()).sum()
}
pub fn slot_utilization(&self, slot: u32) -> Vec<u32> {
if (slot as usize) < self.slots.len() {
self.slots[slot as usize].resource_usage.clone()
} else {
Vec::new()
}
}
pub fn print(&self) {
eprintln!(
"MultiIssueScheduler (II={}, width={}):",
self.ii, self.issue_width
);
for (i, slot) in self.slots.iter().enumerate() {
eprint!(" Slot {}: ", i);
if slot.instructions.is_empty() {
eprint!("(empty)");
} else {
for instr in &slot.instructions {
eprint!("I{} ", instr);
}
}
eprintln!(" [res: {:?}]", slot.resource_usage);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Predicate {
Always,
IfTrue(u32),
IfFalse(u32),
}
#[derive(Debug, Clone)]
pub struct PredicatedInstruction {
pub instr: MachineInstr,
pub predicate: Predicate,
pub is_kernel: bool,
pub stage: u32,
}
pub struct PredicatedPipeliner {
pub base: MachinePipeliner,
pub loop_predicate: u32,
pub enabled: bool,
pub predicated_instrs: Vec<PredicatedInstruction>,
}
impl PredicatedPipeliner {
pub fn new(loop_predicate: u32) -> Self {
Self {
base: MachinePipeliner::new(),
loop_predicate,
enabled: true,
predicated_instrs: Vec::new(),
}
}
pub fn predicate_instructions(
&mut self,
instructions: &[MachineInstr],
stage: u32,
is_kernel: bool,
) {
let predicate = if is_kernel {
Predicate::IfTrue(self.loop_predicate)
} else if stage == 0 {
Predicate::Always
} else {
Predicate::IfTrue(self.loop_predicate + stage)
};
for instr in instructions {
self.predicated_instrs.push(PredicatedInstruction {
instr: instr.clone(),
predicate,
is_kernel,
stage,
});
}
}
pub fn generate_prologue(
&mut self,
_loop_blocks: &[usize],
ii: u32,
all_instructions: &[MachineInstr],
) -> Vec<Vec<PredicatedInstruction>> {
let mut prologue = Vec::new();
for stage in 0..ii.saturating_sub(1) {
let mut stage_instrs = Vec::new();
for iteration in 0..=stage {
for instr in all_instructions {
let predicate = if iteration < stage {
Predicate::Always
} else {
Predicate::IfTrue(self.loop_predicate)
};
stage_instrs.push(PredicatedInstruction {
instr: instr.clone(),
predicate,
is_kernel: false,
stage,
});
}
}
prologue.push(stage_instrs);
}
prologue
}
pub fn generate_kernel(
&mut self,
all_instructions: &[MachineInstr],
num_stages: u32,
) -> Vec<PredicatedInstruction> {
let mut kernel = Vec::new();
for stage in 0..num_stages {
for instr in all_instructions {
kernel.push(PredicatedInstruction {
instr: instr.clone(),
predicate: Predicate::IfTrue(self.loop_predicate),
is_kernel: true,
stage,
});
}
}
kernel
}
pub fn count_active(&self, predicated: &[PredicatedInstruction]) -> usize {
predicated
.iter()
.filter(|pi| !matches!(pi.predicate, Predicate::Always))
.count()
}
pub fn clear(&mut self) {
self.predicated_instrs.clear();
}
}
impl Default for PredicatedPipeliner {
fn default() -> Self {
Self::new(1)
}
}
#[derive(Debug, Clone)]
pub struct PipeliningCostModel {
pub ii: u32,
pub body_size: usize,
pub kernel_size: usize,
pub prologue_size: usize,
pub epilogue_size: usize,
pub trip_count: u32,
pub max_code_expansion: f64,
pub is_profitable: bool,
}
impl PipeliningCostModel {
pub fn new(ii: u32, body_size: usize) -> Self {
Self {
ii,
body_size,
kernel_size: 0,
prologue_size: 0,
epilogue_size: 0,
trip_count: 0,
max_code_expansion: 3.0,
is_profitable: false,
}
}
pub fn set_sizes(&mut self, kernel: usize, prologue: usize, epilogue: usize) {
self.kernel_size = kernel;
self.prologue_size = prologue;
self.epilogue_size = epilogue;
}
pub fn evaluate(&mut self) -> bool {
if self.ii == 0 || self.body_size == 0 {
self.is_profitable = false;
return false;
}
let total_original = self.body_size as f64;
let total_pipelined = (self.prologue_size + self.kernel_size + self.epilogue_size) as f64;
let expansion_ratio = total_pipelined / total_original;
if expansion_ratio > self.max_code_expansion {
self.is_profitable = false;
return false;
}
if self.trip_count > 0 {
let original_cycles = self.trip_count * self.body_size as u32;
let pipelined_cycles = self.prologue_size as u32
+ (self.trip_count - self.ii + 1) * self.kernel_size as u32
+ self.epilogue_size as u32;
self.is_profitable = pipelined_cycles < original_cycles;
} else {
self.is_profitable = (self.ii as f64) < (self.body_size as f64) * 0.75;
}
self.is_profitable
}
pub fn estimated_speedup(&self) -> f64 {
if !self.is_profitable || self.trip_count == 0 {
return 1.0;
}
let original_cycles = self.trip_count * self.body_size as u32;
let pipelined_cycles = self.prologue_size as u32
+ (self.trip_count - self.ii + 1) * self.kernel_size as u32
+ self.epilogue_size as u32;
original_cycles as f64 / pipelined_cycles.max(1) as f64
}
pub fn print(&self) {
eprintln!("PipeliningCostModel:");
eprintln!(" II: {}", self.ii);
eprintln!(" Body size: {} instrs", self.body_size);
eprintln!(" Kernel: {} instrs", self.kernel_size);
eprintln!(" Prologue: {} instrs", self.prologue_size);
eprintln!(" Epilogue: {} instrs", self.epilogue_size);
eprintln!(" Trip count: {}", self.trip_count);
eprintln!(" Profitable: {}", self.is_profitable);
eprintln!(" Est. speedup: {:.2}x", self.estimated_speedup());
}
}
impl Default for PipeliningCostModel {
fn default() -> Self {
Self::new(1, 0)
}
}
#[derive(Debug, Clone)]
pub struct PipelineVerifier {
pub valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl PipelineVerifier {
pub fn new() -> Self {
Self {
valid: true,
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn verify(
&mut self,
original_body: &[MachineInstr],
prologue: &[MachineBasicBlock],
kernel: &[MachineBasicBlock],
epilogue: &[MachineBasicBlock],
ii: u32,
) -> bool {
self.errors.clear();
self.warnings.clear();
self.valid = true;
if kernel.is_empty() {
self.errors.push("Kernel is empty".to_string());
self.valid = false;
}
let total_new: usize = prologue
.iter()
.chain(kernel.iter())
.chain(epilogue.iter())
.map(|b| b.instructions.len())
.sum();
if total_new == 0 && !original_body.is_empty() {
self.errors
.push("Pipeline produced no instructions for non-empty loop".to_string());
self.valid = false;
}
if ii == 0 && !original_body.is_empty() {
self.warnings
.push("Initiation interval is zero for non-empty loop".to_string());
}
if ii > 1 && prologue.len() != (ii - 1) as usize {
self.warnings.push(format!(
"Prologue has {} stages but II={} (expected {})",
prologue.len(),
ii,
ii - 1
));
}
if ii > 1 && epilogue.len() != (ii - 1) as usize {
self.warnings.push(format!(
"Epilogue has {} stages but II={} (expected {})",
epilogue.len(),
ii,
ii - 1
));
}
let p_len = prologue.len();
for i in 0..p_len.saturating_sub(1) {
if !prologue[i]
.successors
.iter()
.any(|&s| s == i + 1 || s == p_len)
{
self.warnings.push(format!(
"Prologue stage {} may lack successor to stage {}",
i,
i + 1
));
}
}
self.valid
}
pub fn is_valid(&self) -> bool {
self.valid
}
pub fn print(&self) {
eprintln!(
"PipelineVerifier: {}",
if self.valid { "PASSED" } else { "FAILED" }
);
for err in &self.errors {
eprintln!(" ERROR: {}", err);
}
for warn in &self.warnings {
eprintln!(" WARNING: {}", warn);
}
}
}
impl Default for PipelineVerifier {
fn default() -> Self {
Self::new()
}
}
pub struct IterativeModuloScheduler {
pub res_mii: u32,
pub rec_mii: u32,
pub current_ii: u32,
pub max_ii: u32,
pub schedule: Vec<Option<(u32, u32)>>,
pub succeeded: bool,
}
impl IterativeModuloScheduler {
pub fn new(res_mii: u32, rec_mii: u32, max_ii: u32) -> Self {
Self {
res_mii,
rec_mii,
current_ii: 0,
max_ii,
schedule: Vec::new(),
succeeded: false,
}
}
pub fn run(
&mut self,
dep_graph: &[Vec<usize>],
latencies: &[u32],
resource_usage: &[Vec<u32>],
num_resources: u32,
) {
let start_ii = self.res_mii.max(self.rec_mii).max(1);
let num_instrs = dep_graph.len();
for ii in start_ii..=self.max_ii {
self.current_ii = ii;
self.schedule = vec![None; num_instrs];
if self.try_schedule(dep_graph, latencies, resource_usage, num_resources, ii) {
self.succeeded = true;
return;
}
}
self.succeeded = false;
}
fn try_schedule(
&mut self,
dep_graph: &[Vec<usize>],
latencies: &[u32],
resource_usage: &[Vec<u32>],
num_resources: u32,
ii: u32,
) -> bool {
let num_instrs = dep_graph.len();
let mut scheduled = 0usize;
let mut ready: Vec<usize> = (0..num_instrs)
.filter(|&i| dep_graph[i].is_empty())
.collect();
while scheduled < num_instrs {
if ready.is_empty() {
return false; }
let node = ready.remove(0);
let mut placed = false;
for stage in 0..(num_instrs as u32) {
for cycle in 0..ii {
if self.can_place(
node,
stage,
cycle,
ii,
dep_graph,
latencies,
resource_usage,
num_resources,
) {
self.schedule[node] = Some((stage, cycle));
scheduled += 1;
placed = true;
for &succ in &dep_graph[node] {
if self.schedule[succ].is_none() {
let all_preds_scheduled = (0..num_instrs)
.filter(|&p| dep_graph[p].contains(&succ))
.all(|p| self.schedule[p].is_some());
if all_preds_scheduled && !ready.contains(&succ) {
ready.push(succ);
}
}
}
break;
}
}
if placed {
break;
}
}
if !placed {
return false;
}
}
true
}
fn can_place(
&self,
node: usize,
stage: u32,
cycle: u32,
ii: u32,
dep_graph: &[Vec<usize>],
latencies: &[u32],
resource_usage: &[Vec<u32>],
num_resources: u32,
) -> bool {
let abs_cycle = stage * ii + cycle;
let num_instrs = dep_graph.len();
for pred in 0..num_instrs {
if dep_graph[pred].contains(&node) {
if let Some((pred_stage, pred_cycle)) = self.schedule[pred] {
let pred_abs = pred_stage * ii + pred_cycle;
if pred_abs + latencies[pred] > abs_cycle {
return false; }
} else {
return false; }
}
}
let mod_cycle = abs_cycle % ii;
for &res in &resource_usage[node] {
if res >= num_resources {
continue;
}
for other in 0..num_instrs {
if other == node {
continue;
}
if let Some((other_stage, other_cycle)) = self.schedule[other] {
if (other_stage * ii + other_cycle) % ii == mod_cycle {
if resource_usage[other].contains(&res) {
return false; }
}
}
}
}
true
}
}
#[derive(Debug, Clone)]
pub struct NodeSplitter {
pub split_map: HashMap<usize, Vec<usize>>,
pub splits_performed: usize,
}
impl NodeSplitter {
pub fn new() -> Self {
Self {
split_map: HashMap::new(),
splits_performed: 0,
}
}
pub fn split_recurrences(
&mut self,
dep_graph: &mut Vec<Vec<usize>>,
latencies: &mut Vec<u32>,
recurrence_edges: &[(usize, usize)],
min_latency: u32,
) {
for &(src, _dst) in recurrence_edges {
if latencies[src] > min_latency * 2 {
let new_node = dep_graph.len();
self.split_map.entry(src).or_default().push(new_node);
dep_graph.push(dep_graph[src].clone());
latencies.push(latencies[src] / 2);
latencies[src] = latencies[src] - latencies[new_node];
dep_graph[src].push(new_node);
self.splits_performed += 1;
}
}
}
}
#[derive(Debug, Clone)]
pub struct HeightBasedOrderer {
pub heights: Vec<u32>,
pub order: Vec<usize>,
}
impl HeightBasedOrderer {
pub fn new() -> Self {
Self {
heights: Vec::new(),
order: Vec::new(),
}
}
pub fn compute(&mut self, dep_graph: &[Vec<usize>], latencies: &[u32]) {
let n = dep_graph.len();
self.heights = vec![0u32; n];
for i in (0..n).rev() {
let mut max_succ_height = 0u32;
for &succ in &dep_graph[i] {
max_succ_height = max_succ_height.max(self.heights[succ]);
}
self.heights[i] = latencies[i] + max_succ_height;
}
let mut indices: Vec<usize> = (0..n).collect();
indices.sort_by_key(|&i| std::cmp::Reverse(self.heights[i]));
self.order = indices;
}
pub fn next_node(&self, scheduled: &[bool]) -> Option<usize> {
self.order.iter().copied().find(|&i| !scheduled[i])
}
}
pub struct CriticalPathReducer {
pub critical_path_length: u32,
pub critical_nodes: Vec<usize>,
pub is_resource_constrained: bool,
}
impl CriticalPathReducer {
pub fn new() -> Self {
Self {
critical_path_length: 0,
critical_nodes: Vec::new(),
is_resource_constrained: false,
}
}
pub fn analyze(
&mut self,
dep_graph: &[Vec<usize>],
latencies: &[u32],
resource_demand: &[u32],
resource_count: u32,
) {
let n = dep_graph.len();
let mut heights = vec![0u32; n];
for i in (0..n).rev() {
let mut max_h = 0u32;
for &succ in &dep_graph[i] {
max_h = max_h.max(heights[succ]);
}
heights[i] = latencies[i] + max_h;
}
self.critical_path_length = heights.iter().copied().max().unwrap_or(0);
let mut current = (0..n).max_by_key(|&i| heights[i]).unwrap_or(0);
self.critical_nodes.push(current);
while heights[current] > latencies[current] {
if let Some(&next) = dep_graph[current].iter().max_by_key(|&&s| heights[s]) {
current = next;
self.critical_nodes.push(current);
} else {
break;
}
}
let total_demand: u32 = resource_demand.iter().sum();
let ideal_ii = (total_demand as f64 / resource_count as f64).ceil() as u32;
self.is_resource_constrained = ideal_ii > self.critical_path_length;
}
}
pub struct LoopUnrollerForPipelining {
pub factor: u32,
pub unrolled: bool,
pub unrolled_instructions: Vec<MachineInstr>,
}
impl LoopUnrollerForPipelining {
pub fn new(factor: u32) -> Self {
Self {
factor,
unrolled: false,
unrolled_instructions: Vec::new(),
}
}
pub fn unroll(&mut self, body_instrs: &[MachineInstr], reg_offset_step: u32) {
self.unrolled_instructions.clear();
for iter in 0..self.factor {
let reg_offset = iter * reg_offset_step;
for mi in body_instrs {
let mut new_mi = MachineInstr {
opcode: mi.opcode,
operands: Vec::new(),
def: mi.def.map(|d| d + reg_offset),
size: 0,
};
for op in &mi.operands {
let new_op = match op {
MachineOperand::Reg(r) => MachineOperand::Reg(r + reg_offset),
_ => op.clone(),
};
new_mi.operands.push(new_op);
}
self.unrolled_instructions.push(new_mi);
}
}
self.unrolled = true;
}
}
pub struct IfConverterForPipelining {
pub conversions: usize,
pub enabled: bool,
}
impl IfConverterForPipelining {
pub fn new() -> Self {
Self {
conversions: 0,
enabled: true,
}
}
pub fn if_convert(&mut self, instrs: &[MachineInstr]) -> Vec<(MachineInstr, bool)> {
let mut result = Vec::new();
let mut in_predicated_region = false;
for mi in instrs {
let is_branch = matches!(mi.opcode, 15 | 16 | 17);
if is_branch && self.enabled {
in_predicated_region = true;
self.conversions += 1;
continue; }
result.push((mi.clone(), in_predicated_region));
}
result
}
}
pub struct SuperblockFormation {
pub blocks: Vec<Vec<MachineInstr>>,
pub duplicated: bool,
pub duplicated_blocks: usize,
}
impl SuperblockFormation {
pub fn new() -> Self {
Self {
blocks: Vec::new(),
duplicated: false,
duplicated_blocks: 0,
}
}
pub fn form_superblock(&mut self, trace: &[Vec<MachineInstr>], profile_counts: &[u64]) {
if trace.is_empty() {
return;
}
self.blocks = trace.to_vec();
for i in 1..trace.len() {
if profile_counts.get(i).copied().unwrap_or(0) > 100 {
self.duplicated_blocks += 1;
self.duplicated = true;
}
}
}
}
pub struct HyperblockFormation {
pub instructions: Vec<(MachineInstr, u32)>, pub exit_count: usize,
}
impl HyperblockFormation {
pub fn new() -> Self {
Self {
instructions: Vec::new(),
exit_count: 0,
}
}
pub fn form_hyperblock(
&mut self,
taken_path: &[MachineInstr],
fallthrough_path: &[MachineInstr],
) {
self.instructions.clear();
for mi in taken_path {
self.instructions.push((mi.clone(), 1));
}
for mi in fallthrough_path {
self.instructions.push((mi.clone(), 0));
}
self.exit_count = 2;
}
}
pub struct PipeHoleOptimizer {
pub slots_filled: usize,
pub instructions_hoisted: usize,
}
impl PipeHoleOptimizer {
pub fn new() -> Self {
Self {
slots_filled: 0,
instructions_hoisted: 0,
}
}
pub fn fill_holes(
&mut self,
schedule: &mut [(u32, u32)],
ii: u32,
num_slots: u32,
dep_graph: &[Vec<usize>],
latencies: &[u32],
) {
let n = schedule.len();
let num_stages = (n as u32 + ii - 1) / ii;
for stage in 0..num_stages {
for slot in 0..num_slots {
let global_cycle = stage * num_slots + slot;
let slot_used = schedule.iter().any(|&(s, c)| s == stage && c == slot);
if slot_used {
continue;
}
for i in 0..n {
let (instr_stage, instr_cycle) = schedule[i];
if instr_stage < stage || (instr_stage == stage && instr_cycle <= slot) {
continue; }
let can_move = dep_graph[i].iter().all(|&pred| {
let (p_stage, p_cycle) = schedule[pred];
let pred_global = p_stage * num_slots + p_cycle;
pred_global + latencies[pred] <= global_cycle
});
if can_move {
schedule[i] = (stage, slot);
self.slots_filled += 1;
self.instructions_hoisted += 1;
break;
}
}
}
}
}
}
pub struct KernelOnlyGenerator {
pub min_trip_count: u32,
pub kernel_only_possible: bool,
pub kernel_instructions: Vec<MachineInstr>,
}
impl KernelOnlyGenerator {
pub fn new(min_trip_count: u32) -> Self {
Self {
min_trip_count,
kernel_only_possible: false,
kernel_instructions: Vec::new(),
}
}
pub fn analyze(&mut self, trip_count: u32, kernel_size: usize) -> bool {
self.kernel_only_possible =
trip_count >= self.min_trip_count && trip_count as usize > kernel_size * 3 / 2;
self.kernel_only_possible
}
pub fn generate_kernel_only(
&mut self,
kernel: &[MachineInstr],
original_trip_count: u32,
stages: u32,
) {
let adjusted_count = original_trip_count.saturating_sub(stages - 1);
self.kernel_instructions.clear();
self.kernel_instructions.push(MachineInstr {
opcode: 20, operands: vec![MachineOperand::Imm(adjusted_count as i64)],
def: Some(0xFFFF), size: 0,
});
self.kernel_instructions.extend_from_slice(kernel);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_test_mf() -> MachineFunction {
let mut mf = MachineFunction::new("test_func");
let entry = MachineBasicBlock {
name: "entry".to_string(),
instructions: vec![MachineInstr {
opcode: 1,
operands: vec![],
def: Some(0),
}],
successors: vec!["loop_header".to_string()],
};
mf.push_block(entry);
let mut hdr_instrs = Vec::new();
for i in 0..3 {
hdr_instrs.push(MachineInstr {
opcode: 1 + (i as u32) % 4,
operands: vec![],
def: Some(1 + i as u32),
});
}
let loop_header = MachineBasicBlock {
name: "loop_header".to_string(),
instructions: hdr_instrs,
successors: vec!["loop_body".to_string()],
};
mf.push_block(loop_header);
let mut body_instrs = Vec::new();
for i in 0..4 {
body_instrs.push(MachineInstr {
opcode: 2 + (i as u32) % 4,
operands: vec![MachineOperand::Reg(1 + i as u32)],
def: Some(10 + i as u32),
});
}
let loop_body = MachineBasicBlock {
name: "loop_body".to_string(),
instructions: body_instrs,
successors: vec!["loop_header".to_string()], };
mf.push_block(loop_body);
mf
}
#[test]
fn test_new_pipeliner() {
let pipeliner = MachinePipeliner::new();
assert_eq!(pipeliner.pipelines_created, 0);
assert_eq!(pipeliner.kernel_instructions, 0);
}
#[test]
fn test_find_pipelineable_loops() {
let mf = make_test_mf();
let pipeliner = MachinePipeliner::new();
let loops = pipeliner.find_pipelineable_loops(&mf);
assert!(!loops.is_empty());
}
#[test]
fn test_find_loops_empty_function() {
let mf = MachineFunction::new("empty");
let pipeliner = MachinePipeliner::new();
let loops = pipeliner.find_pipelineable_loops(&mf);
assert!(loops.is_empty());
}
#[test]
fn test_compute_initiation_interval() {
let mf = make_test_mf();
let pipeliner = MachinePipeliner::new();
let loop_blocks = vec![1, 2];
let ii = pipeliner.compute_initiation_interval(&loop_blocks, &mf);
assert!(ii >= 1);
}
#[test]
fn test_compute_ii_empty_loop() {
let mf = MachineFunction::new("empty");
let pipeliner = MachinePipeliner::new();
let ii = pipeliner.compute_initiation_interval(&[], &mf);
assert_eq!(ii, 0);
}
#[test]
fn test_build_dependence_graph() {
let instrs = vec![
MachineInstr {
opcode: 1,
operands: vec![],
def: Some(0),
},
MachineInstr {
opcode: 2,
operands: vec![MachineOperand::Reg(0)],
def: Some(1),
},
MachineInstr {
opcode: 3,
operands: vec![MachineOperand::Reg(1)],
def: Some(2),
},
];
let graph = MachinePipeliner::build_dependence_graph(&instrs);
assert_eq!(graph.len(), 3);
assert!(graph[0].contains(&1));
assert!(graph[1].contains(&2));
}
#[test]
fn test_build_dependence_graph_empty() {
let graph = MachinePipeliner::build_dependence_graph(&[]);
assert!(graph.is_empty());
}
#[test]
fn test_compute_asap() {
let graph = vec![vec![1], vec![2], vec![]];
let asap = MachinePipeliner::compute_asap(&graph, 3);
assert!(asap[0] < asap[1]);
assert!(asap[1] < asap[2]);
}
#[test]
fn test_compute_alap() {
let graph = vec![vec![1], vec![2], vec![]];
let alap = MachinePipeliner::compute_alap(&graph, 3, 4);
assert!(alap[0] < alap[1] || alap[0] == 0);
}
#[test]
fn test_compute_recurrence_mii_no_cycles() {
let graph: Vec<Vec<usize>> = vec![vec![1], vec![2], vec![]];
let mii = MachinePipeliner::compute_recurrence_mii(&graph);
assert_eq!(mii, 1);
}
#[test]
fn test_compute_recurrence_mii_with_cycle() {
let graph = vec![vec![1], vec![2], vec![0]];
let mii = MachinePipeliner::compute_recurrence_mii(&graph);
assert!(mii >= 3);
}
#[test]
fn test_modulo_schedule_simple() {
let pipeliner = MachinePipeliner::new();
let instrs = vec![
MachineInstr {
opcode: 1,
operands: vec![],
def: Some(0),
},
MachineInstr {
opcode: 2,
operands: vec![MachineOperand::Reg(0)],
def: Some(1),
},
];
let schedule = pipeliner.modulo_schedule(&instrs, 2);
assert_eq!(schedule.len(), 2);
let total: usize = schedule.iter().map(|s| s.len()).sum();
assert_eq!(total, 2);
}
#[test]
fn test_modulo_schedule_empty() {
let pipeliner = MachinePipeliner::new();
let schedule = pipeliner.modulo_schedule(&[], 2);
assert_eq!(schedule.len(), 1);
assert!(schedule[0].is_empty());
}
#[test]
fn test_modulo_schedule_ii_zero() {
let pipeliner = MachinePipeliner::new();
let instrs = vec![MachineInstr {
opcode: 1,
operands: vec![],
def: Some(0),
}];
let schedule = pipeliner.modulo_schedule(&instrs, 0);
assert_eq!(schedule.len(), 1);
}
#[test]
fn test_generate_prologue() {
let pipeliner = MachinePipeliner::new();
let blocks = vec![0, 1];
let prologue = pipeliner.generate_prologue(&blocks, 3);
assert_eq!(prologue.len(), 2);
for (i, block) in prologue.iter().enumerate() {
assert!(block.name.contains(&format!("prologue.{}", i)));
}
}
#[test]
fn test_generate_prologue_ii1() {
let pipeliner = MachinePipeliner::new();
let prologue = pipeliner.generate_prologue(&[0], 1);
assert!(prologue.is_empty());
}
#[test]
fn test_generate_kernel() {
let pipeliner = MachinePipeliner::new();
let blocks = vec![0, 1];
let kernel = pipeliner.generate_kernel(&blocks, 2);
assert!(!kernel.is_empty());
assert!(kernel.iter().any(|b| b.name.contains("kernel")));
}
#[test]
fn test_generate_epilogue() {
let pipeliner = MachinePipeliner::new();
let blocks = vec![0, 1];
let epilogue = pipeliner.generate_epilogue(&blocks, 3);
assert_eq!(epilogue.len(), 2);
}
#[test]
fn test_generate_epilogue_ii1() {
let pipeliner = MachinePipeliner::new();
let epilogue = pipeliner.generate_epilogue(&[0], 1);
assert!(epilogue.is_empty());
}
#[test]
fn test_run_on_function() {
let mut mf = make_test_mf();
let mut pipeliner = MachinePipeliner::new();
let count = pipeliner.run_on_function(&mut mf);
assert!(pipeliner.pipelines_created <= 1);
}
#[test]
fn test_apply_pipeline() {
let mut mf = make_test_mf();
let original_len = mf.blocks.len();
let prologue = vec![MachineBasicBlock {
name: "prologue".to_string(),
instructions: vec![],
successors: vec![],
}];
let kernel = vec![MachineBasicBlock {
name: "kernel".to_string(),
instructions: vec![],
successors: vec![],
}];
let epilogue = vec![];
let result = MachinePipeliner::apply_pipeline(&mut mf, &[1, 2], prologue, kernel, epilogue);
assert!(result);
assert_eq!(mf.blocks.len(), original_len);
}
#[test]
fn test_apply_pipeline_empty() {
let mut mf = MachineFunction::new("empty");
let result = MachinePipeliner::apply_pipeline(&mut mf, &[], vec![], vec![], vec![]);
assert!(!result);
}
#[test]
fn test_mve_new() {
let mve = ModuloVariableExpansion::new(3, 4, false);
assert_eq!(mve.ii, 3);
assert_eq!(mve.num_stages, 4);
assert!(!mve.has_rotating_regs);
}
#[test]
fn test_mve_expand_variable() {
let mut mve = ModuloVariableExpansion::new(2, 4, false);
let copies = mve.expand_variable(10, 3);
assert_eq!(copies.len(), 3);
}
#[test]
fn test_mve_get_register_for_stage() {
let mut mve = ModuloVariableExpansion::new(2, 4, false);
mve.expand_variable(10, 3);
assert!(mve.get_register_for_stage(10, 0).is_some());
assert!(mve.get_register_for_stage(10, 1).is_some());
assert!(mve.get_register_for_stage(10, 2).is_some());
assert!(mve.get_register_for_stage(10, 3).is_none());
}
#[test]
fn test_mve_is_expanded() {
let mut mve = ModuloVariableExpansion::new(2, 4, false);
mve.expand_variable(10, 2);
assert!(mve.is_expanded(10));
assert!(!mve.is_expanded(20));
}
#[test]
fn test_mve_rotating_regs() {
let mut mve = ModuloVariableExpansion::new(2, 4, true);
mve.expand_variable(10, 3);
let r0 = mve.get_register_for_stage(10, 0);
let r1 = mve.get_register_for_stage(10, 1);
let r2 = mve.get_register_for_stage(10, 2);
assert!(r0.is_some());
assert!(r1.is_some());
assert!(r2.is_some());
}
#[test]
fn test_rrf_new() {
let rrf = RotatingRegisterFile::new(32, 16);
assert!(rrf.enabled);
assert_eq!(rrf.rotate_count, 16);
}
#[test]
fn test_rrf_physical_register() {
let rrf = RotatingRegisterFile::new(32, 8);
assert_eq!(rrf.physical_register(32), 32);
assert_eq!(rrf.physical_register(33), 33);
assert_eq!(rrf.physical_register(10), 10);
}
#[test]
fn test_rrf_rotate() {
let mut rrf = RotatingRegisterFile::new(32, 8);
rrf.rotate();
assert_eq!(rrf.physical_register(32), 33);
assert_eq!(rrf.physical_register(39), 32); }
#[test]
fn test_rrf_reset() {
let mut rrf = RotatingRegisterFile::new(32, 8);
rrf.rotate();
rrf.rotate();
rrf.reset();
assert_eq!(rrf.current_offset, 0);
assert_eq!(rrf.physical_register(32), 32);
}
#[test]
fn test_rrf_is_rotating() {
let rrf = RotatingRegisterFile::new(32, 8);
assert!(rrf.is_rotating(32));
assert!(rrf.is_rotating(35));
assert!(!rrf.is_rotating(10));
assert!(!rrf.is_rotating(40));
}
#[test]
fn test_lca_new() {
let lca = LoopCarriedAnalyzer::new();
assert!(lca.dependences.is_empty());
assert_eq!(lca.rec_mii, 0);
}
#[test]
fn test_lca_analyze_no_cycles() {
let mut lca = LoopCarriedAnalyzer::new();
let dep_graph = vec![vec![1], vec![2], vec![]];
let backedges: Vec<usize> = vec![];
lca.analyze(&dep_graph, &backedges);
assert_eq!(lca.rec_mii, 1);
}
#[test]
fn test_lca_analyze_with_backedge() {
let mut lca = LoopCarriedAnalyzer::new();
let dep_graph = vec![vec![1], vec![2], vec![0]];
let backedges = vec![2]; lca.analyze(&dep_graph, &backedges);
assert!(lca.rec_mii >= 1);
assert!(!lca.dependences.is_empty());
}
#[test]
fn test_lca_recurrence_edges() {
let mut lca = LoopCarriedAnalyzer::new();
let dep_graph = vec![vec![1], vec![0]]; let backedges = vec![1];
lca.analyze(&dep_graph, &backedges);
let recs = lca.recurrence_edges();
assert!(!recs.is_empty());
}
#[test]
fn test_mis_new() {
let mis = MultiIssueScheduler::new(4, 2, vec![4, 2, 1]);
assert_eq!(mis.ii, 4);
assert_eq!(mis.issue_width, 2);
assert_eq!(mis.slots.len(), 4);
}
#[test]
fn test_mis_try_schedule() {
let mut mis = MultiIssueScheduler::new(4, 2, vec![4, 2, 1]);
assert!(mis.try_schedule(0, 0, &[0]));
assert!(mis.try_schedule(1, 0, &[1]));
assert!(!mis.try_schedule(2, 0, &[2]));
}
#[test]
fn test_mis_total_instructions() {
let mut mis = MultiIssueScheduler::new(4, 2, vec![4, 2, 1]);
mis.try_schedule(0, 0, &[0]);
mis.try_schedule(1, 1, &[0]);
assert_eq!(mis.total_instructions(), 2);
}
#[test]
fn test_pred_pipeliner_new() {
let pp = PredicatedPipeliner::new(3);
assert!(pp.enabled);
assert_eq!(pp.loop_predicate, 3);
assert!(pp.predicated_instrs.is_empty());
}
#[test]
fn test_pred_pipeliner_predicate_kernel() {
let mut pp = PredicatedPipeliner::new(3);
let instrs = vec![MachineInstr {
opcode: 1,
operands: vec![],
def: Some(1),
}];
pp.predicate_instructions(&instrs, 0, true);
assert_eq!(pp.predicated_instrs.len(), 1);
assert!(pp.predicated_instrs[0].is_kernel);
assert!(matches!(
pp.predicated_instrs[0].predicate,
Predicate::IfTrue(3)
));
}
#[test]
fn test_pred_pipeliner_count_active() {
let pp = PredicatedPipeliner::new(3);
let active = vec![
PredicatedInstruction {
instr: MachineInstr {
opcode: 1,
operands: vec![],
def: None,
},
predicate: Predicate::IfTrue(3),
is_kernel: true,
stage: 0,
},
PredicatedInstruction {
instr: MachineInstr {
opcode: 1,
operands: vec![],
def: None,
},
predicate: Predicate::Always,
is_kernel: false,
stage: 0,
},
];
assert_eq!(pp.count_active(&active), 1);
}
#[test]
fn test_cost_model_new() {
let cm = PipeliningCostModel::new(3, 10);
assert_eq!(cm.ii, 3);
assert_eq!(cm.body_size, 10);
}
#[test]
fn test_cost_model_evaluate_profitable() {
let mut cm = PipeliningCostModel::new(2, 10);
cm.set_sizes(6, 4, 4);
cm.trip_count = 100;
let profitable = cm.evaluate();
assert!(profitable);
}
#[test]
fn test_cost_model_evaluate_not_profitable() {
let mut cm = PipeliningCostModel::new(10, 10);
cm.set_sizes(10, 10, 10);
let profitable = cm.evaluate();
assert!(!profitable);
}
#[test]
fn test_verifier_new() {
let pv = PipelineVerifier::new();
assert!(pv.valid);
assert!(pv.errors.is_empty());
}
#[test]
fn test_verifier_empty_kernel() {
let mut pv = PipelineVerifier::new();
let original = vec![MachineInstr {
opcode: 1,
operands: vec![],
def: Some(1),
}];
let prologue: Vec<MachineBasicBlock> = vec![];
let kernel: Vec<MachineBasicBlock> = vec![];
let epilogue: Vec<MachineBasicBlock> = vec![];
pv.verify(&original, &prologue, &kernel, &epilogue, 2);
assert!(!pv.is_valid());
assert!(!pv.errors.is_empty());
}
#[test]
fn test_verifier_valid_pipeline() {
let mut pv = PipelineVerifier::new();
let original = vec![MachineInstr {
opcode: 1,
operands: vec![],
def: Some(1),
}];
let kernel = vec![MachineBasicBlock {
name: "kernel".to_string(),
instructions: original.clone(),
successors: vec![],
}];
pv.verify(&original, &[], &kernel, &[], 1);
assert!(pv.is_valid());
}
#[test]
fn test_iterative_modulo_scheduler_success() {
let mut ims = IterativeModuloScheduler::new(1, 1, 4);
let dep_graph: Vec<Vec<usize>> = vec![vec![1], vec![]];
let latencies = vec![1, 1];
let resource_usage = vec![vec![0], vec![0]];
ims.run(&dep_graph, &latencies, &resource_usage, 2);
assert!(ims.succeeded);
}
#[test]
fn test_iterative_modulo_scheduler_no_solution() {
let mut ims = IterativeModuloScheduler::new(10, 10, 10);
let dep_graph: Vec<Vec<usize>> = vec![vec![1], vec![0]];
let latencies = vec![100, 100];
let resource_usage = vec![vec![0], vec![0]];
ims.run(&dep_graph, &latencies, &resource_usage, 1);
assert!(!ims.succeeded);
}
#[test]
fn test_height_orderer_linear() {
let mut orderer = HeightBasedOrderer::new();
let dep_graph: Vec<Vec<usize>> = vec![vec![1], vec![2], vec![]];
let latencies = vec![2, 3, 1];
orderer.compute(&dep_graph, &latencies);
assert_eq!(orderer.order.len(), 3);
}
#[test]
fn test_height_orderer_next_node() {
let mut orderer = HeightBasedOrderer::new();
let dep_graph: Vec<Vec<usize>> = vec![vec![1], vec![], vec![]];
let latencies = vec![2, 1, 1];
orderer.compute(&dep_graph, &latencies);
let scheduled = vec![false, false, false];
let next = orderer.next_node(&scheduled);
assert!(next.is_some());
}
#[test]
fn test_critical_path_reducer_analyze() {
let mut reducer = CriticalPathReducer::new();
let dep_graph: Vec<Vec<usize>> = vec![vec![1], vec![2], vec![]];
let latencies = vec![5, 3, 1];
let demand = vec![1, 1, 1];
reducer.analyze(&dep_graph, &latencies, &demand, 4);
assert_eq!(reducer.critical_path_length, 9);
}
#[test]
fn test_loop_unroller_unroll2() {
let mut unroller = LoopUnrollerForPipelining::new(2);
let body = vec![MachineInstr {
opcode: 2,
operands: vec![MachineOperand::Reg(0)],
def: Some(1),
}];
unroller.unroll(&body, 10);
assert!(unroller.unrolled);
assert_eq!(unroller.unrolled_instructions.len(), 2);
}
#[test]
fn test_if_converter_converts() {
let mut converter = IfConverterForPipelining::new();
let instrs = vec![
MachineInstr {
opcode: 18,
operands: vec![],
def: None,
},
MachineInstr {
opcode: 16,
operands: vec![],
def: None,
},
MachineInstr {
opcode: 1,
operands: vec![],
def: Some(1),
},
];
let result = converter.if_convert(&instrs);
assert_eq!(result.len(), 2);
}
#[test]
fn test_kernel_only_large_trip_count() {
let mut r#gen = KernelOnlyGenerator::new(10);
assert!(r#gen.analyze(100, 5));
}
#[test]
fn test_kernel_only_small_trip_count() {
let mut r#gen = KernelOnlyGenerator::new(10);
assert!(!r#gen.analyze(5, 5));
}
}