use llvm_native_core::codegen::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand};
use std::collections::{HashMap, HashSet, VecDeque};
#[derive(Debug, Clone)]
pub struct Resource {
pub name: String,
pub count: u32,
}
#[derive(Debug, Clone)]
pub struct ResourceModel {
pub issue_width: u32,
pub resources: Vec<Resource>,
pub instruction_latencies: HashMap<u32, u32>,
}
impl ResourceModel {
pub fn default_model() -> Self {
let mut latencies = HashMap::new();
latencies.insert(1, 1); latencies.insert(2, 3); latencies.insert(3, 1); latencies.insert(4, 5); latencies.insert(5, 7); latencies.insert(6, 12); latencies.insert(7, 1);
Self {
issue_width: 4,
resources: vec![
Resource {
name: "ALU".to_string(),
count: 4,
},
Resource {
name: "LoadUnit".to_string(),
count: 2,
},
Resource {
name: "StoreUnit".to_string(),
count: 1,
},
Resource {
name: "BranchUnit".to_string(),
count: 1,
},
],
instruction_latencies: latencies,
}
}
pub fn get_latency(&self, opcode: u32) -> u32 {
self.instruction_latencies
.get(&opcode)
.copied()
.unwrap_or(1)
}
}
#[derive(Debug, Clone)]
struct SchedNode {
pub instr_idx: usize,
pub opcode: u32,
pub latency: u32,
pub critical_path: u32,
pub successors: Vec<usize>,
pub predecessors: Vec<usize>,
pub scheduled: bool,
pub unscheduled_preds: usize,
}
#[derive(Debug, Clone)]
pub struct DepGraph {
pub nodes: Vec<SchedNode>,
pub node_count: usize,
}
impl DepGraph {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
node_count: 0,
}
}
}
pub struct MachineScheduler {
pub target: String,
pub resource_model: ResourceModel,
pub reordered: usize,
}
impl MachineScheduler {
pub fn new(target: &str) -> Self {
Self {
target: target.to_string(),
resource_model: ResourceModel::default_model(),
reordered: 0,
}
}
pub fn schedule(&mut self, mf: &mut MachineFunction) -> usize {
self.reordered = 0;
let block_count = mf.blocks.len();
for bb_idx in 0..block_count {
let block = &mf.blocks[bb_idx];
if block.instructions.len() < 2 {
continue;
}
let graph = self.build_dependence_graph(&block.instructions);
if graph.node_count < 2 {
continue;
}
let cp = self.compute_critical_path(&graph);
let schedule = self.list_schedule(&graph, &cp);
let _conflicts = self.compute_resource_conflicts(&schedule, &block.instructions);
let original_len = mf.blocks[bb_idx].instructions.len();
if schedule.len() == original_len {
let mut new_instructions = Vec::with_capacity(schedule.len());
for &idx in &schedule {
if idx < mf.blocks[bb_idx].instructions.len() {
new_instructions.push(mf.blocks[bb_idx].instructions[idx].clone());
}
}
let mut changed = 0;
for (i, &sched_idx) in schedule.iter().enumerate() {
if sched_idx != i {
changed += 1;
}
}
mf.blocks[bb_idx].instructions = new_instructions;
self.reordered += changed;
}
}
self.reordered
}
pub fn build_dependence_graph(&self, instructions: &[MachineInstr]) -> DepGraph {
let n = instructions.len();
let mut graph = DepGraph::new();
for (idx, mi) in instructions.iter().enumerate() {
let node = SchedNode {
instr_idx: idx,
opcode: mi.opcode,
latency: self.get_instruction_latency(mi),
critical_path: 0,
successors: Vec::new(),
predecessors: Vec::new(),
scheduled: false,
unscheduled_preds: 0,
};
graph.nodes.push(node);
}
graph.node_count = n;
let mut last_def: HashMap<u64, usize> = HashMap::new();
let mut last_use: HashMap<u64, Vec<usize>> = HashMap::new();
for (idx, mi) in instructions.iter().enumerate() {
let hash = self.hash_instruction(mi);
let def_regs = self.get_defined_regs(mi);
let use_regs = self.get_used_regs(mi);
for reg in &use_regs {
let reg_key = Self::reg_to_key(reg);
if let Some(&def_idx) = last_def.get(®_key) {
if def_idx < idx {
graph.nodes[def_idx].successors.push(idx);
graph.nodes[idx].predecessors.push(def_idx);
}
}
last_use.entry(reg_key).or_default().push(idx);
}
for reg in &def_regs {
let reg_key = Self::reg_to_key(reg);
if let Some(&prev_def) = last_def.get(®_key) {
if prev_def < idx {
graph.nodes[prev_def].successors.push(idx);
graph.nodes[idx].predecessors.push(prev_def);
}
}
if let Some(prev_uses) = last_use.get(®_key) {
for &use_idx in prev_uses {
if use_idx < idx {
graph.nodes[use_idx].successors.push(idx);
graph.nodes[idx].predecessors.push(use_idx);
}
}
}
last_def.insert(reg_key, idx);
last_use.remove(®_key);
}
}
for node in &mut graph.nodes {
node.unscheduled_preds = node.predecessors.len();
}
graph
}
fn get_defined_regs(&self, mi: &MachineInstr) -> Vec<MachineOperand> {
let mut defs = Vec::new();
if let Some(def_vreg) = mi.def {
defs.push(MachineOperand::Reg(def_vreg));
}
defs
}
fn get_used_regs(&self, mi: &MachineInstr) -> Vec<MachineOperand> {
mi.operands
.iter()
.filter(|op| matches!(op, MachineOperand::Reg(_) | MachineOperand::PhysReg(_)))
.cloned()
.collect()
}
fn reg_to_key(op: &MachineOperand) -> u64 {
match op {
MachineOperand::Reg(vr) => *vr as u64,
MachineOperand::PhysReg(pr) => (*pr as u64) + (1u64 << 32),
_ => 0,
}
}
fn hash_instruction(&self, mi: &MachineInstr) -> u64 {
let mut hash: u64 = mi.opcode as u64;
for op in &mi.operands {
hash = hash.wrapping_mul(31).wrapping_add(Self::reg_to_key(op));
}
if let Some(def) = mi.def {
hash = hash.wrapping_mul(31).wrapping_add(def as u64);
}
hash
}
pub fn compute_critical_path(&self, graph: &DepGraph) -> Vec<u32> {
let n = graph.node_count;
let mut cp = vec![0u32; n];
for idx in (0..n).rev() {
let node_latency = graph.nodes[idx].latency;
let mut max_succ_cp = 0u32;
for &succ in &graph.nodes[idx].successors {
if succ < n {
max_succ_cp = max_succ_cp.max(cp[succ]);
}
}
cp[idx] = node_latency + max_succ_cp;
}
cp
}
pub fn list_schedule(&self, graph: &DepGraph, cp: &[u32]) -> Vec<usize> {
let n = graph.node_count;
let mut schedule = Vec::with_capacity(n);
let mut unscheduled_preds: Vec<usize> = graph
.nodes
.iter()
.map(|node| node.unscheduled_preds)
.collect();
let mut ready: Vec<usize> = (0..n).filter(|&i| unscheduled_preds[i] == 0).collect();
while !ready.is_empty() {
ready.sort_by(|&a, &b| {
cp[b].cmp(&cp[a]).then_with(|| a.cmp(&b)) });
let selected = ready.remove(0);
schedule.push(selected);
for &succ in &graph.nodes[selected].successors {
if succ < n && unscheduled_preds[succ] > 0 {
unscheduled_preds[succ] -= 1;
if unscheduled_preds[succ] == 0 {
ready.push(succ);
}
}
}
}
schedule
}
pub fn compute_resource_conflicts(
&self,
schedule: &[usize],
instructions: &[MachineInstr],
) -> Vec<u64> {
let mut conflicts = Vec::new();
let resource_count = self.resource_model.resources.len();
let mut current_cycle_resources = vec![0u32; resource_count];
let mut current_cycle_issued = 0u32;
let mut cycle = 0u64;
for &instr_idx in schedule {
if instr_idx >= instructions.len() {
continue;
}
let mi = &instructions[instr_idx];
let mut resource_mask: u64 = 0;
let instr_resources = self.get_instruction_resources(mi);
for res_idx in &instr_resources {
if *res_idx < resource_count {
if current_cycle_resources[*res_idx]
>= self.resource_model.resources[*res_idx].count
{
resource_mask |= 1u64 << res_idx;
}
}
}
if current_cycle_issued >= self.resource_model.issue_width {
cycle += 1;
current_cycle_resources.fill(0);
current_cycle_issued = 0;
}
for res_idx in &instr_resources {
if *res_idx < resource_count {
current_cycle_resources[*res_idx] += 1;
}
}
current_cycle_issued += 1;
if resource_mask != 0 {
conflicts.push(resource_mask);
}
}
conflicts
}
fn get_instruction_resources(&self, mi: &MachineInstr) -> Vec<usize> {
let opcode = mi.opcode;
match opcode {
2 | 3 | 20 | 21 => {
let mut res = Vec::new();
res.push(1); res.push(0); res
}
7 | 8 | 9 => vec![3], 4 | 5 | 6 => vec![4], _ => vec![0], }
}
pub fn get_instruction_latency(&self, instr: &MachineInstr) -> u32 {
self.resource_model.get_latency(instr.opcode)
}
}
impl Default for MachineScheduler {
fn default() -> Self {
Self::new("generic")
}
}
#[derive(Debug, Clone)]
pub struct HeuristicPriority {
pub critical_path: i32,
pub successor_count: u32,
pub register_pressure_delta: i32,
pub resource_demand: u32,
pub combined_score: i64,
}
impl HeuristicPriority {
pub fn new() -> Self {
Self {
critical_path: 0,
successor_count: 0,
register_pressure_delta: 0,
resource_demand: 0,
combined_score: 0,
}
}
pub fn compute(&mut self) {
self.combined_score = self.critical_path as i64 * 100
+ self.successor_count as i64 * 10
+ self.register_pressure_delta as i64 * 50
- self.resource_demand as i64 * 5;
}
}
impl Default for HeuristicPriority {
fn default() -> Self {
Self::new()
}
}
pub struct CriticalPathScheduler {
pub critical_paths: Vec<i32>,
pub computed: bool,
}
impl CriticalPathScheduler {
pub fn new() -> Self {
Self {
critical_paths: Vec::new(),
computed: false,
}
}
pub fn compute(&mut self, nodes: &[SchedNode]) {
self.critical_paths = vec![-1; nodes.len()];
for i in (0..nodes.len()).rev() {
let node = &nodes[i];
let mut max_succ_path = 0;
for &succ_idx in &node.successors {
if succ_idx < self.critical_paths.len() {
max_succ_path = max_succ_path.max(self.critical_paths[succ_idx]);
}
}
self.critical_paths[i] = max_succ_path + node.latency as i32;
}
self.computed = true;
}
pub fn get_critical_path(&self, node_idx: usize) -> i32 {
self.critical_paths.get(node_idx).copied().unwrap_or(0)
}
pub fn compare(&self, a: usize, b: usize) -> std::cmp::Ordering {
let cp_a = self.get_critical_path(a);
let cp_b = self.get_critical_path(b);
cp_b.cmp(&cp_a) }
}
impl Default for CriticalPathScheduler {
fn default() -> Self {
Self::new()
}
}
pub struct ResourceBalancer {
pub usage_per_cycle: HashMap<i32, HashMap<u32, u32>>,
pub resources_available: HashMap<u32, u32>,
pub current_cycle: i32,
}
impl ResourceBalancer {
pub fn new(resources: &[Resource]) -> Self {
let mut available = HashMap::new();
for res in resources {
available.insert(res.name.parse::<u32>().unwrap_or(0), res.count);
}
Self {
usage_per_cycle: HashMap::new(),
resources_available: available,
current_cycle: 0,
}
}
pub fn resources_available_at(&self, cycle: i32, resource_ids: &[u32]) -> bool {
let usage = self.usage_per_cycle.get(&cycle);
for &rid in resource_ids {
let used = usage.and_then(|u| u.get(&rid)).copied().unwrap_or(0);
let available = self.resources_available.get(&rid).copied().unwrap_or(0);
if used >= available {
return false;
}
}
true
}
pub fn reserve_resources(&mut self, cycle: i32, resource_ids: &[u32]) {
let usage = self.usage_per_cycle.entry(cycle).or_default();
for &rid in resource_ids {
*usage.entry(rid).or_insert(0) += 1;
}
}
pub fn find_earliest_cycle(
&self,
start_cycle: i32,
resource_ids: &[u32],
max_cycle: i32,
) -> Option<i32> {
for cycle in start_cycle..=max_cycle {
if self.resources_available_at(cycle, resource_ids) {
return Some(cycle);
}
}
None
}
pub fn utilization_at(&self, cycle: i32) -> f64 {
let usage = self.usage_per_cycle.get(&cycle);
let total_avail: u32 = self.resources_available.values().sum();
if total_avail == 0 {
return 0.0;
}
let total_used: u32 = usage.map(|u| u.values().sum()).unwrap_or(0);
total_used as f64 / total_avail as f64
}
}
impl Default for ResourceBalancer {
fn default() -> Self {
Self::new(&[])
}
}
pub struct RegPressureScheduler {
pub live_regs_per_cycle: HashMap<i32, usize>,
pub max_pressure: usize,
pub target_regs: usize,
}
impl RegPressureScheduler {
pub fn new(target_regs: usize) -> Self {
Self {
live_regs_per_cycle: HashMap::new(),
max_pressure: 0,
target_regs,
}
}
pub fn pressure_impact(
&self,
cycle: i32,
defs: &[u32],
uses: &[u32],
killed_regs: &[u32],
) -> i32 {
let current = self.live_regs_per_cycle.get(&cycle).copied().unwrap_or(0);
let def_pressure = defs.len() as i32;
let kill_benefit = killed_regs.len() as i32;
def_pressure - kill_benefit
}
pub fn would_exceed_limit(&self, cycle: i32, defs: &[u32]) -> bool {
let current = self.live_regs_per_cycle.get(&cycle).copied().unwrap_or(0);
current + defs.len() > self.target_regs
}
pub fn update_pressure(&mut self, cycle: i32, defs: &[u32], killed_regs: &[u32]) {
let entry = self.live_regs_per_cycle.entry(cycle).or_insert(0);
*entry = (*entry + defs.len()).saturating_sub(killed_regs.len());
self.max_pressure = self.max_pressure.max(*entry);
}
pub fn print_stats(&self) {
eprintln!(
"RegPressure: max={}, target={}",
self.max_pressure, self.target_regs
);
}
}
impl Default for RegPressureScheduler {
fn default() -> Self {
Self::new(16)
}
}
pub trait ScheduleDAGMutation {
fn apply(&mut self, nodes: &mut [SchedNode]) -> bool;
fn name(&self) -> &str;
}
pub struct MacroFusionMutation {
pub fusible_pairs: Vec<(u32, u32)>,
pub enabled: bool,
}
impl MacroFusionMutation {
pub fn x86_default() -> Self {
Self {
fusible_pairs: vec![
(0, 1), (10, 11), (20, 21), (22, 23), ],
enabled: true,
}
}
pub fn aarch64_default() -> Self {
Self {
fusible_pairs: vec![
(100, 101), (102, 103), (104, 105), ],
enabled: true,
}
}
pub fn can_fuse(&self, op1: u32, op2: u32) -> bool {
self.enabled
&& self
.fusible_pairs
.iter()
.any(|&(a, b)| a == op1 && b == op2)
}
}
impl ScheduleDAGMutation for MacroFusionMutation {
fn apply(&mut self, nodes: &mut [SchedNode]) -> bool {
if !self.enabled || nodes.len() < 2 {
return false;
}
let mut modified = false;
for i in 0..nodes.len() - 1 {
let op1 = nodes[i].opcode;
let op2 = nodes[i + 1].opcode;
if self.can_fuse(op1, op2) {
if !nodes[i].successors.contains(&(i + 1)) {
nodes[i].successors.push(i + 1);
nodes[i + 1].predecessors.push(i);
modified = true;
}
}
}
modified
}
fn name(&self) -> &str {
"MacroFusion"
}
}
pub struct AntiDepMutation {
pub enabled: bool,
}
impl AntiDepMutation {
pub fn new() -> Self {
Self { enabled: true }
}
}
impl ScheduleDAGMutation for AntiDepMutation {
fn apply(&mut self, nodes: &mut [SchedNode]) -> bool {
if !self.enabled {
return false;
}
let mut modified = false;
for i in 0..nodes.len() {
for j in i + 1..nodes.len() {
if !nodes[i].successors.contains(&j) && !nodes[j].predecessors.contains(&i) {
let has_conflict = nodes[i].opcode % 10 == nodes[j].opcode % 10;
if has_conflict {
nodes[i].successors.push(j);
nodes[j].predecessors.push(i);
modified = true;
}
}
}
}
modified
}
fn name(&self) -> &str {
"AntiDep"
}
}
impl Default for AntiDepMutation {
fn default() -> Self {
Self::new()
}
}
pub struct MachineSchedulerBase {
pub scheduler: MachineScheduler,
pub mutations: Vec<Box<dyn ScheduleDAGMutation>>,
pub cp_scheduler: CriticalPathScheduler,
pub resource_balancer: Option<ResourceBalancer>,
pub reg_pressure: RegPressureScheduler,
pub dag_processed: bool,
}
impl MachineSchedulerBase {
pub fn new(target: &str) -> Self {
Self {
scheduler: MachineScheduler::new(target),
mutations: Vec::new(),
cp_scheduler: CriticalPathScheduler::new(),
resource_balancer: None,
reg_pressure: RegPressureScheduler::new(16),
dag_processed: false,
}
}
pub fn add_mutation(&mut self, mutation: Box<dyn ScheduleDAGMutation>) {
self.mutations.push(mutation);
}
pub fn run_schedule(&mut self, mf: &mut MachineFunction) -> usize {
self.dag_processed = false;
let mut nodes: Vec<SchedNode> =
(0..self.scheduler.resource_model.instruction_latencies.len())
.map(|i| SchedNode {
instr_idx: i,
opcode: i as u32,
latency: self.scheduler.resource_model.get_latency(i as u32),
critical_path: 0,
successors: Vec::new(),
predecessors: Vec::new(),
scheduled: false,
unscheduled_preds: 0,
})
.collect();
for mutation in &mut self.mutations {
mutation.apply(&mut nodes);
}
self.cp_scheduler.compute(&nodes);
let total_reordered = self.scheduler.schedule(mf);
self.dag_processed = true;
total_reordered
}
pub fn postprocess_dag(&mut self, nodes: &mut [SchedNode]) -> usize {
let mut changes = 0;
for i in 0..nodes.len() {
let succs = nodes[i].successors.clone();
for &s in &succs {
for &t in &nodes[s].successors.clone() {
if nodes[i].successors.contains(&t) {
nodes[i].successors.retain(|&x| x != t);
nodes[t].predecessors.retain(|&x| x != i);
changes += 1;
}
}
}
}
let mut fusion = MacroFusionMutation::x86_default();
if fusion.apply(nodes) {
changes += 1;
}
changes
}
pub fn print_schedule(&self) {
eprintln!("MachineSchedulerBase schedule:");
eprintln!(" Target: {}", self.scheduler.target);
eprintln!(" Reordered: {}", self.scheduler.reordered);
eprintln!(
" Issue width: {}",
self.scheduler.resource_model.issue_width
);
self.reg_pressure.print_stats();
}
}
impl Default for MachineSchedulerBase {
fn default() -> Self {
Self::new("generic")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HazardKind {
None,
Structural,
RAW,
WAR,
WAW,
Control,
}
#[derive(Debug, Clone)]
pub struct PipelineStage {
pub name: String,
pub resources: Vec<usize>,
pub duration: u32,
pub allows_forwarding: bool,
}
#[derive(Debug, Clone)]
pub struct HazardRecognizer {
pub resource_count: usize,
pub resource_schedule: HashMap<u32, Vec<u32>>,
pub pipeline_stages: Vec<PipelineStage>,
pub write_registers: HashMap<u64, u32>,
pub read_registers: HashMap<u64, Vec<u32>>,
pub latencies: HashMap<u32, u32>,
pub current_cycle: u32,
pub forwarding_enabled: bool,
}
impl HazardRecognizer {
pub fn new(resource_count: usize, forwarding_enabled: bool) -> Self {
Self {
resource_count,
resource_schedule: HashMap::new(),
pipeline_stages: Vec::new(),
write_registers: HashMap::new(),
read_registers: HashMap::new(),
latencies: HashMap::new(),
current_cycle: 0,
forwarding_enabled,
}
}
pub fn add_pipeline_stage(&mut self, stage: PipelineStage) {
self.pipeline_stages.push(stage);
}
pub fn set_latency(&mut self, opcode: u32, latency: u32) {
self.latencies.insert(opcode, latency);
}
pub fn set_cycle(&mut self, cycle: u32) {
self.current_cycle = cycle;
}
pub fn can_issue(
&self,
opcode: u32,
def_regs: &[u64],
use_regs: &[u64],
resources: &[usize],
) -> HazardKind {
for &res in resources {
if res < self.resource_count {
if let Some(cycle_usage) = self.resource_schedule.get(&self.current_cycle) {
if res < cycle_usage.len() && cycle_usage[res] > 0 {
if cycle_usage[res] >= 1 {
return HazardKind::Structural;
}
}
}
}
}
for ® in use_regs {
if let Some(&write_cycle) = self.write_registers.get(®) {
let latency = self.latencies.get(&opcode).copied().unwrap_or(1);
let available_cycle = write_cycle + latency;
if self.current_cycle < available_cycle {
if !self.can_forward(reg, write_cycle, self.current_cycle) {
return HazardKind::RAW;
}
}
}
}
for ® in def_regs {
if let Some(read_cycles) = self.read_registers.get(®) {
for &read_cycle in read_cycles {
if read_cycle >= self.current_cycle {
return HazardKind::WAR;
}
}
}
}
for ® in def_regs {
if let Some(&write_cycle) = self.write_registers.get(®) {
if write_cycle >= self.current_cycle {
return HazardKind::WAW;
}
}
}
HazardKind::None
}
pub fn emit(&mut self, opcode: u32, def_regs: &[u64], use_regs: &[u64], resources: &[usize]) {
let latency = self.latencies.get(&opcode).copied().unwrap_or(1);
for cycle_offset in 0..latency {
let cycle = self.current_cycle + cycle_offset;
let usage = self
.resource_schedule
.entry(cycle)
.or_insert_with(|| vec![0; self.resource_count]);
for &res in resources {
if res < usage.len() {
usage[res] += 1;
}
}
}
for ® in def_regs {
self.write_registers
.insert(reg, self.current_cycle + latency);
}
for ® in use_regs {
self.read_registers
.entry(reg)
.or_default()
.push(self.current_cycle);
}
}
fn can_forward(&self, reg: u64, producer_cycle: u32, consumer_cycle: u32) -> bool {
if !self.forwarding_enabled {
return false;
}
let distance = consumer_cycle - producer_cycle;
distance <= 2
}
pub fn advance_cycle(&mut self) {
self.current_cycle += 1;
}
pub fn find_earliest_issue(
&self,
opcode: u32,
def_regs: &[u64],
use_regs: &[u64],
resources: &[usize],
max_cycles: u32,
) -> Option<u32> {
let mut probe = self.clone();
for offset in 0..max_cycles {
probe.current_cycle = self.current_cycle + offset;
if probe.can_issue(opcode, def_regs, use_regs, resources) == HazardKind::None {
return Some(self.current_cycle + offset);
}
}
None
}
pub fn reset(&mut self) {
self.resource_schedule.clear();
self.write_registers.clear();
self.read_registers.clear();
self.current_cycle = 0;
}
pub fn scheduled_cycles(&self) -> u32 {
self.current_cycle
}
pub fn utilization_at(&self, cycle: u32) -> Vec<u32> {
self.resource_schedule
.get(&cycle)
.cloned()
.unwrap_or_else(|| vec![0; self.resource_count])
}
}
impl Default for HazardRecognizer {
fn default() -> Self {
Self::new(8, true)
}
}
#[derive(Debug, Clone)]
pub struct ForwardingPath {
pub from_stage: usize,
pub to_stage: usize,
pub latency_reduction: u32,
pub universal: bool,
pub producer_opcodes: Vec<u32>,
pub consumer_opcodes: Vec<u32>,
}
#[derive(Debug, Clone)]
pub struct ForwardingModel {
pub paths: Vec<ForwardingPath>,
pub base_latencies: HashMap<u32, u32>,
pub effective_latencies: HashMap<(u32, u32), u32>,
}
impl ForwardingModel {
pub fn new() -> Self {
Self {
paths: Vec::new(),
base_latencies: HashMap::new(),
effective_latencies: HashMap::new(),
}
}
pub fn add_path(&mut self, path: ForwardingPath) {
self.paths.push(path);
}
pub fn set_latency(&mut self, opcode: u32, latency: u32) {
self.base_latencies.insert(opcode, latency);
}
pub fn get_effective_latency(&self, producer_opcode: u32, consumer_opcode: u32) -> u32 {
let key = (producer_opcode, consumer_opcode);
if let Some(&lat) = self.effective_latencies.get(&key) {
return lat;
}
let base = self
.base_latencies
.get(&producer_opcode)
.copied()
.unwrap_or(1);
let mut best_reduction = 0u32;
for path in &self.paths {
let producer_ok = path.producer_opcodes.is_empty()
|| path.producer_opcodes.contains(&producer_opcode);
let consumer_ok = path.consumer_opcodes.is_empty()
|| path.consumer_opcodes.contains(&consumer_opcode);
if (producer_ok && consumer_ok) || path.universal {
best_reduction = best_reduction.max(path.latency_reduction);
}
}
let effective = base.saturating_sub(best_reduction).max(1);
effective
}
pub fn can_forward(&self, producer_opcode: u32, consumer_opcode: u32) -> bool {
self.get_effective_latency(producer_opcode, consumer_opcode)
< self
.base_latencies
.get(&producer_opcode)
.copied()
.unwrap_or(1)
}
pub fn x86_64_default() -> Self {
let mut model = Self::new();
for op in 1..=10 {
model.set_latency(op, 1); }
model.set_latency(2, 4); model.set_latency(3, 1); model.set_latency(4, 3); model.set_latency(5, 5);
model.add_path(ForwardingPath {
from_stage: 2,
to_stage: 1,
latency_reduction: 1,
universal: false,
producer_opcodes: vec![1],
consumer_opcodes: vec![1],
});
model.add_path(ForwardingPath {
from_stage: 4,
to_stage: 2,
latency_reduction: 2,
universal: false,
producer_opcodes: vec![2],
consumer_opcodes: vec![1],
});
model.add_path(ForwardingPath {
from_stage: 3,
to_stage: 3,
latency_reduction: 3,
universal: false,
producer_opcodes: vec![3],
consumer_opcodes: vec![2],
});
model
}
pub fn aarch64_default() -> Self {
let mut model = Self::new();
for op in 1..=10 {
model.set_latency(op, 1);
}
model.set_latency(2, 4);
model.set_latency(3, 1);
model.add_path(ForwardingPath {
from_stage: 2,
to_stage: 1,
latency_reduction: 1,
universal: true,
producer_opcodes: vec![],
consumer_opcodes: vec![],
});
model
}
}
impl Default for ForwardingModel {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MicroFusion {
pub enabled: bool,
pub fusible_patterns: Vec<(u32, u32)>,
pub target: String,
}
impl MicroFusion {
pub fn new(target: &str) -> Self {
Self {
enabled: !target.is_empty(),
fusible_patterns: Vec::new(),
target: target.to_string(),
}
}
pub fn add_pattern(&mut self, addr_op: u32, mem_op: u32) {
self.fusible_patterns.push((addr_op, mem_op));
}
pub fn can_micro_fuse(
&self,
addr_instr: &MachineInstr,
mem_instr: &MachineInstr,
) -> Option<u32> {
if !self.enabled {
return None;
}
let addr_def = addr_instr.def?;
let uses_addr = mem_instr.operands.iter().any(|op| match op {
MachineOperand::Reg(r) => *r == addr_def,
_ => false,
});
if !uses_addr {
return None;
}
for &(addr_op, mem_op) in &self.fusible_patterns {
if addr_instr.opcode == addr_op && mem_instr.opcode == mem_op {
return Some(mem_op + 100);
}
}
if mem_instr.opcode == 2 || mem_instr.opcode == 3 {
return Some(mem_instr.opcode);
}
None
}
pub fn apply(&self, instructions: &mut Vec<MachineInstr>) -> usize {
let mut fused = 0;
let mut i = 0;
while i + 1 < instructions.len() {
if let Some(_fused_op) = self.can_micro_fuse(&instructions[i], &instructions[i + 1]) {
instructions.remove(i);
fused += 1;
} else {
i += 1;
}
}
fused
}
pub fn x86_default() -> Self {
let mut mf = Self::new("x86_64");
mf.add_pattern(8, 2);
mf.add_pattern(8, 3);
mf.add_pattern(1, 2);
mf
}
}
impl Default for MicroFusion {
fn default() -> Self {
Self::new("")
}
}
#[derive(Debug, Clone)]
pub struct MemoryCluster {
pub base_register: u64,
pub instructions: Vec<usize>,
pub is_load: bool,
pub byte_footprint: u32,
}
#[derive(Debug, Clone)]
pub struct ClusterFormation {
pub enabled: bool,
pub max_cluster_size: usize,
pub min_cluster_size: usize,
pub clusters: Vec<MemoryCluster>,
}
impl ClusterFormation {
pub fn new(max_cluster_size: usize) -> Self {
Self {
enabled: true,
max_cluster_size,
min_cluster_size: 2,
clusters: Vec::new(),
}
}
pub fn form_clusters(&mut self, instructions: &[MachineInstr]) {
self.clusters.clear();
let mut load_groups: HashMap<u64, Vec<usize>> = HashMap::new();
let mut store_groups: HashMap<u64, Vec<usize>> = HashMap::new();
for (idx, instr) in instructions.iter().enumerate() {
let base_reg = self.extract_base_register(instr);
if let Some(base) = base_reg {
match instr.opcode {
2 => {
load_groups.entry(base).or_default().push(idx);
}
3 => {
store_groups.entry(base).or_default().push(idx);
}
_ => {}
}
}
}
for (base, indices) in load_groups {
if indices.len() >= self.min_cluster_size {
let byte_footprint = self.estimate_footprint(instructions, &indices);
self.clusters.push(MemoryCluster {
base_register: base,
instructions: indices,
is_load: true,
byte_footprint,
});
}
}
for (base, indices) in store_groups {
if indices.len() >= self.min_cluster_size {
let byte_footprint = self.estimate_footprint(instructions, &indices);
self.clusters.push(MemoryCluster {
base_register: base,
instructions: indices,
is_load: false,
byte_footprint,
});
}
}
}
fn extract_base_register(&self, instr: &MachineInstr) -> Option<u64> {
for operand in &instr.operands {
match operand {
MachineOperand::Reg(r) => return Some(*r as u64),
MachineOperand::PhysReg(pr) => return Some(*pr as u64 + (1u64 << 32)),
_ => continue,
}
}
None
}
fn estimate_footprint(&self, instructions: &[MachineInstr], indices: &[usize]) -> u32 {
indices.len() as u32 * 4
}
pub fn should_cluster(&self, instr_a: &MachineInstr, instr_b: &MachineInstr) -> bool {
let base_a = self.extract_base_register(instr_a);
let base_b = self.extract_base_register(instr_b);
match (base_a, base_b) {
(Some(a), Some(b)) => a == b,
_ => false,
}
}
pub fn load_clusters(&self) -> Vec<&MemoryCluster> {
let mut loads: Vec<&MemoryCluster> = self.clusters.iter().filter(|c| c.is_load).collect();
loads.sort_by_key(|c| -(c.byte_footprint as i64));
loads
}
pub fn store_clusters(&self) -> Vec<&MemoryCluster> {
let mut stores: Vec<&MemoryCluster> = self.clusters.iter().filter(|c| !c.is_load).collect();
stores.sort_by_key(|c| -(c.byte_footprint as i64));
stores
}
pub fn print_stats(&self) {
eprintln!("ClusterFormation: {} clusters formed", self.clusters.len());
for cluster in &self.clusters {
eprintln!(
" {} cluster: base={}, {} instrs, {} bytes",
if cluster.is_load { "Load" } else { "Store" },
cluster.base_register,
cluster.instructions.len(),
cluster.byte_footprint
);
}
}
}
impl Default for ClusterFormation {
fn default() -> Self {
Self::new(8)
}
}
#[derive(Debug, Clone)]
pub struct SchedulingRegion {
pub start: usize,
pub end: usize,
pub is_top: bool,
pub is_bottom: bool,
pub has_terminator: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SchedulingBarrier {
None,
Call,
Return,
Branch,
InlineAsm,
EHLabel,
DebugInstr,
Fence,
}
#[derive(Debug, Clone)]
pub struct RegionBuilder {
pub regions: Vec<SchedulingRegion>,
pub schedule_across_calls: bool,
pub target: String,
}
impl RegionBuilder {
pub fn new(target: &str, schedule_across_calls: bool) -> Self {
Self {
regions: Vec::new(),
schedule_across_calls,
target: target.to_string(),
}
}
pub fn build_regions(&mut self, instructions: &[MachineInstr]) {
self.regions.clear();
if instructions.is_empty() {
return;
}
let mut region_start = 0usize;
let is_top = true;
for (i, instr) in instructions.iter().enumerate() {
let barrier = self.classify_barrier(instr);
let ends_region = match barrier {
SchedulingBarrier::None => false,
SchedulingBarrier::Call => !self.schedule_across_calls,
SchedulingBarrier::Return | SchedulingBarrier::Branch => true,
SchedulingBarrier::InlineAsm => true,
SchedulingBarrier::EHLabel => true,
SchedulingBarrier::DebugInstr => false,
SchedulingBarrier::Fence => true,
};
if ends_region && i > region_start {
self.regions.push(SchedulingRegion {
start: region_start,
end: i,
is_top: is_top && self.regions.is_empty(),
is_bottom: barrier == SchedulingBarrier::Return
|| barrier == SchedulingBarrier::Branch,
has_terminator: barrier == SchedulingBarrier::Return
|| barrier == SchedulingBarrier::Branch,
});
region_start = i + 1;
}
if barrier == SchedulingBarrier::Return || barrier == SchedulingBarrier::Branch {
if i + 1 < instructions.len() {
region_start = i + 1;
}
}
}
if region_start < instructions.len() {
self.regions.push(SchedulingRegion {
start: region_start,
end: instructions.len() - 1,
is_top: false,
is_bottom: false,
has_terminator: false,
});
}
}
fn classify_barrier(&self, instr: &MachineInstr) -> SchedulingBarrier {
match instr.opcode {
0 => SchedulingBarrier::None,
7 => SchedulingBarrier::Branch, 8 => SchedulingBarrier::Branch, 9 => SchedulingBarrier::Return, 10 => SchedulingBarrier::Call, 11 => SchedulingBarrier::Fence, 12 => SchedulingBarrier::InlineAsm, 13 => SchedulingBarrier::EHLabel, 99 => SchedulingBarrier::DebugInstr, _ => SchedulingBarrier::None,
}
}
pub fn same_region(&self, idx_a: usize, idx_b: usize) -> bool {
self.regions
.iter()
.any(|r| r.start <= idx_a && idx_a <= r.end && r.start <= idx_b && idx_b <= r.end)
}
pub fn get_region(&self, instr_idx: usize) -> Option<&SchedulingRegion> {
self.regions
.iter()
.find(|r| r.start <= instr_idx && instr_idx <= r.end)
}
pub fn region_count(&self) -> usize {
self.regions.len()
}
}
impl Default for RegionBuilder {
fn default() -> Self {
Self::new("generic", false)
}
}
#[derive(Debug, Clone)]
pub struct PostRAScheduler {
pub target: String,
pub resource_model: ResourceModel,
pub hazard_recognizer: HazardRecognizer,
pub forwarding: ForwardingModel,
pub break_anti_deps: bool,
pub reg_liveness: HashMap<u64, Vec<(usize, usize)>>,
}
impl PostRAScheduler {
pub fn new(target: &str) -> Self {
let mut hazard = HazardRecognizer::new(8, true);
hazard.set_latency(1, 1);
hazard.set_latency(2, 4);
hazard.set_latency(3, 1);
hazard.set_latency(7, 1);
Self {
target: target.to_string(),
resource_model: ResourceModel::default_model(),
hazard_recognizer: hazard,
forwarding: ForwardingModel::x86_64_default(),
break_anti_deps: true,
reg_liveness: HashMap::new(),
}
}
pub fn schedule(&mut self, instructions: &mut [MachineInstr]) -> usize {
if instructions.len() < 2 {
return 0;
}
self.compute_liveness(instructions);
let graph = self.build_post_ra_dag(instructions);
let cp = self.compute_physical_critical_path(&graph);
let schedule = self.list_schedule_post_ra(&graph, &cp, instructions);
if schedule.len() == instructions.len() {
let mut reordered = 0;
let mut new_instrs = Vec::with_capacity(schedule.len());
for &idx in &schedule {
if idx < instructions.len() {
new_instrs.push(instructions[idx].clone());
}
}
for (i, &sched_idx) in schedule.iter().enumerate() {
if sched_idx != i {
reordered += 1;
}
}
for (i, instr) in new_instrs.into_iter().enumerate() {
if i < instructions.len() {
instructions[i] = instr;
}
}
reordered
} else {
0
}
}
fn compute_liveness(&mut self, instructions: &[MachineInstr]) {
self.reg_liveness.clear();
let mut last_use: HashMap<u64, usize> = HashMap::new();
let mut first_def: HashMap<u64, usize> = HashMap::new();
for (idx, instr) in instructions.iter().enumerate() {
for operand in &instr.operands {
if let Some(reg_key) = Self::phys_reg_key(operand) {
last_use.insert(reg_key, idx);
}
}
if let Some(def) = instr.def {
let key = def as u64;
first_def.entry(key).or_insert(idx);
let end = last_use.get(&key).copied().unwrap_or(idx);
self.reg_liveness.entry(key).or_default().push((idx, end));
}
}
}
fn phys_reg_key(op: &MachineOperand) -> Option<u64> {
match op {
MachineOperand::Reg(r) => Some(*r as u64),
MachineOperand::PhysReg(pr) => Some(*pr as u64 + (1u64 << 32)),
_ => None,
}
}
fn build_post_ra_dag(&self, instructions: &[MachineInstr]) -> DepGraph {
let n = instructions.len();
let mut graph = DepGraph::new();
for (idx, mi) in instructions.iter().enumerate() {
let node = SchedNode {
instr_idx: idx,
opcode: mi.opcode,
latency: self.resource_model.get_latency(mi.opcode),
critical_path: 0,
successors: Vec::new(),
predecessors: Vec::new(),
scheduled: false,
unscheduled_preds: 0,
};
graph.nodes.push(node);
}
graph.node_count = n;
let mut last_def: HashMap<u64, usize> = HashMap::new();
let mut last_use: HashMap<u64, Vec<usize>> = HashMap::new();
for (idx, mi) in instructions.iter().enumerate() {
let def_regs: Vec<u64> = mi.def.into_iter().map(|d| d as u64).collect();
let use_regs: Vec<u64> = mi
.operands
.iter()
.filter_map(|op| Self::phys_reg_key(op))
.collect();
for ® in &use_regs {
if let Some(&def_idx) = last_def.get(®) {
if def_idx < idx {
graph.nodes[def_idx].successors.push(idx);
graph.nodes[idx].predecessors.push(def_idx);
}
}
last_use.entry(reg).or_default().push(idx);
}
for ® in &def_regs {
if let Some(&prev_def) = last_def.get(®) {
if prev_def < idx {
graph.nodes[prev_def].successors.push(idx);
graph.nodes[idx].predecessors.push(prev_def);
}
}
if let Some(prev_uses) = last_use.get(®) {
for &use_idx in prev_uses {
if use_idx < idx {
graph.nodes[use_idx].successors.push(idx);
graph.nodes[idx].predecessors.push(use_idx);
}
}
}
last_def.insert(reg, idx);
last_use.remove(®);
}
}
for node in &mut graph.nodes {
node.unscheduled_preds = node.predecessors.len();
}
graph
}
fn compute_physical_critical_path(&self, graph: &DepGraph) -> Vec<u32> {
let n = graph.node_count;
let mut cp = vec![0u32; n];
for idx in (0..n).rev() {
let node_latency = graph.nodes[idx].latency;
let mut max_succ_cp = 0u32;
for &succ in &graph.nodes[idx].successors {
if succ < n {
max_succ_cp = max_succ_cp.max(cp[succ]);
}
}
cp[idx] = node_latency + max_succ_cp;
}
cp
}
fn list_schedule_post_ra(
&self,
graph: &DepGraph,
cp: &[u32],
instructions: &[MachineInstr],
) -> Vec<usize> {
let n = graph.node_count;
let mut schedule = Vec::with_capacity(n);
let mut unscheduled_preds: Vec<usize> = graph
.nodes
.iter()
.map(|node| node.unscheduled_preds)
.collect();
let mut ready: Vec<usize> = (0..n).filter(|&i| unscheduled_preds[i] == 0).collect();
while !ready.is_empty() {
ready.sort_by(|&a, &b| cp[b].cmp(&cp[a]).then_with(|| a.cmp(&b)));
let selected = ready.remove(0);
schedule.push(selected);
for &succ in &graph.nodes[selected].successors {
if succ < n && unscheduled_preds[succ] > 0 {
unscheduled_preds[succ] -= 1;
if unscheduled_preds[succ] == 0 {
ready.push(succ);
}
}
}
}
schedule
}
pub fn break_anti_dependencies(
&self,
graph: &mut DepGraph,
instructions: &mut [MachineInstr],
) -> usize {
if !self.break_anti_deps {
return 0;
}
let mut broken = 0;
for i in 0..graph.node_count {
let war_edges: Vec<usize> = graph.nodes[i]
.predecessors
.iter()
.copied()
.filter(|&pred| {
instructions.get(pred).map_or(false, |p_instr| {
instructions.get(i).map_or(false, |i_instr| {
i_instr.def.is_some()
})
})
})
.collect();
if !war_edges.is_empty() && instructions[i].def.is_some() {
let new_reg = (instructions[i].def.unwrap() as u64).wrapping_add(10000);
let old_def = instructions[i].def;
instructions[i].def = Some(new_reg as u32);
for &pred in &war_edges {
graph.nodes[pred].successors.retain(|&x| x != i);
graph.nodes[i].predecessors.retain(|&x| x != pred);
}
broken += war_edges.len();
}
}
broken
}
}
impl Default for PostRAScheduler {
fn default() -> Self {
Self::new("generic")
}
}
#[derive(Debug, Clone)]
pub struct ReservationTable {
pub num_stages: usize,
pub num_units: usize,
pub table: Vec<Vec<bool>>,
pub opcode: u32,
pub duration: u32,
}
impl ReservationTable {
pub fn new(num_stages: usize, num_units: usize, opcode: u32) -> Self {
Self {
num_stages,
num_units,
table: vec![vec![false; num_units]; num_stages],
opcode,
duration: num_stages as u32,
}
}
pub fn reserve(&mut self, cycle: usize, unit: usize) {
if cycle < self.table.len() && unit < self.num_units {
self.table[cycle][unit] = true;
}
}
pub fn is_reserved(&self, cycle: usize, unit: usize) -> bool {
if cycle < self.table.len() && unit < self.num_units {
self.table[cycle][unit]
} else {
false
}
}
pub fn collides_with(&self, other: &ReservationTable, offset: usize) -> bool {
for cycle in 0..self.table.len() {
let other_cycle = cycle.wrapping_sub(offset);
if other_cycle < other.table.len() {
for unit in 0..self.num_units.min(other.num_units) {
if self.table[cycle][unit] && other.table[other_cycle][unit] {
return true;
}
}
}
}
false
}
pub fn usage_at(&self, cycle: usize) -> Vec<usize> {
if cycle < self.table.len() {
self.table[cycle]
.iter()
.enumerate()
.filter(|&(_, &used)| used)
.map(|(unit, _)| unit)
.collect()
} else {
Vec::new()
}
}
pub fn alu_default() -> Self {
let mut rt = Self::new(5, 4, 1);
rt.reserve(0, 0); rt.reserve(1, 1); rt.reserve(2, 1); rt.reserve(3, 2); rt
}
pub fn load_default() -> Self {
let mut rt = Self::new(6, 4, 2);
rt.reserve(0, 0); rt.reserve(1, 1); rt.reserve(2, 3); rt.reserve(3, 3); rt.reserve(4, 3); rt.reserve(5, 2); rt
}
pub fn print(&self) {
eprintln!("Reservation table for opcode {}:", self.opcode);
eprint!("Cycle |");
for unit in 0..self.num_units {
eprint!(" U{}", unit);
}
eprintln!();
eprintln!("{:-<1$}", "", 10 + self.num_units * 3);
for cycle in 0..self.table.len() {
eprint!(" {:3} |", cycle);
for unit in 0..self.num_units {
let c = if self.table[cycle][unit] { 'X' } else { '.' };
eprint!(" {}", c);
}
eprintln!();
}
}
}
impl Default for ReservationTable {
fn default() -> Self {
Self::alu_default()
}
}
#[derive(Debug, Clone)]
pub struct BidirectionalScheduler {
pub target: String,
pub resource_model: ResourceModel,
pub forward_schedule: Vec<usize>,
pub backward_schedule: Vec<usize>,
pub best_schedule: Vec<usize>,
pub forward_length: u32,
pub backward_length: u32,
}
impl BidirectionalScheduler {
pub fn new(target: &str) -> Self {
Self {
target: target.to_string(),
resource_model: ResourceModel::default_model(),
forward_schedule: Vec::new(),
backward_schedule: Vec::new(),
best_schedule: Vec::new(),
forward_length: 0,
backward_length: 0,
}
}
pub fn schedule(&mut self, graph: &DepGraph, instructions: &[MachineInstr]) -> Vec<usize> {
let n = graph.node_count;
if n == 0 {
return Vec::new();
}
let cp = self.compute_critical_paths(graph);
self.forward_schedule = self.top_down_schedule(graph, &cp);
self.forward_length = self.compute_schedule_length(&self.forward_schedule, instructions);
let rev_graph = self.reverse_graph(graph);
let rev_cp = self.compute_critical_paths(&rev_graph);
let mut rev_schedule = self.top_down_schedule(&rev_graph, &rev_cp);
rev_schedule.reverse();
self.backward_schedule = rev_schedule;
self.backward_length = self.compute_schedule_length(&self.backward_schedule, instructions);
if self.forward_length <= self.backward_length {
self.best_schedule = self.forward_schedule.clone();
} else {
self.best_schedule = self.backward_schedule.clone();
}
self.best_schedule.clone()
}
fn compute_critical_paths(&self, graph: &DepGraph) -> Vec<u32> {
let n = graph.node_count;
let mut cp = vec![0u32; n];
for idx in (0..n).rev() {
let node_latency = graph.nodes[idx].latency;
let mut max_succ = 0u32;
for &succ in &graph.nodes[idx].successors {
if succ < n {
max_succ = max_succ.max(cp[succ]);
}
}
cp[idx] = node_latency + max_succ;
}
cp
}
fn top_down_schedule(&self, graph: &DepGraph, cp: &[u32]) -> Vec<usize> {
let n = graph.node_count;
let mut schedule = Vec::with_capacity(n);
let mut unscheduled_preds: Vec<usize> = graph
.nodes
.iter()
.map(|node| node.unscheduled_preds)
.collect();
let mut ready: Vec<usize> = (0..n).filter(|&i| unscheduled_preds[i] == 0).collect();
while !ready.is_empty() {
ready.sort_by(|&a, &b| cp[b].cmp(&cp[a]).then_with(|| a.cmp(&b)));
let selected = ready.remove(0);
schedule.push(selected);
for &succ in &graph.nodes[selected].successors {
if succ < n && unscheduled_preds[succ] > 0 {
unscheduled_preds[succ] -= 1;
if unscheduled_preds[succ] == 0 {
ready.push(succ);
}
}
}
}
schedule
}
fn reverse_graph(&self, graph: &DepGraph) -> DepGraph {
let n = graph.node_count;
let mut rev = DepGraph::new();
for node in &graph.nodes {
rev.nodes.push(SchedNode {
instr_idx: node.instr_idx,
opcode: node.opcode,
latency: node.latency,
critical_path: 0,
successors: node.predecessors.clone(),
predecessors: node.successors.clone(),
scheduled: false,
unscheduled_preds: node.successors.len(),
});
}
rev.node_count = n;
rev
}
fn compute_schedule_length(&self, schedule: &[usize], instructions: &[MachineInstr]) -> u32 {
if schedule.is_empty() {
return 0;
}
let mut completion_times: Vec<u32> = vec![0; schedule.len()];
let mut position: HashMap<usize, usize> = HashMap::new();
for (pos, &idx) in schedule.iter().enumerate() {
position.insert(idx, pos);
}
for (pos, &idx) in schedule.iter().enumerate() {
if idx >= instructions.len() {
continue;
}
let latency = self.resource_model.get_latency(instructions[idx].opcode);
let mut start_time = 0u32;
for earlier_pos in 0..pos {
let earlier_idx = schedule[earlier_pos];
if earlier_idx >= instructions.len() {
continue;
}
let earlier_def = instructions[earlier_idx].def;
let uses_earlier = instructions[idx].operands.iter().any(|op| match op {
MachineOperand::Reg(r) => earlier_def == Some(*r),
_ => false,
});
if uses_earlier {
start_time = start_time.max(completion_times[earlier_pos]);
}
}
completion_times[pos] = start_time + latency;
}
completion_times.last().copied().unwrap_or(0)
}
pub fn print_stats(&self) {
eprintln!("BidirectionalScheduler stats:");
eprintln!(" Forward length: {} cycles", self.forward_length);
eprintln!(" Backward length: {} cycles", self.backward_length);
eprintln!(
" Best: {} ({} schedule)",
self.forward_length.min(self.backward_length),
if self.forward_length <= self.backward_length {
"forward"
} else {
"backward"
}
);
}
}
impl Default for BidirectionalScheduler {
fn default() -> Self {
Self::new("generic")
}
}
#[derive(Debug, Clone)]
pub struct InstrItinerary {
pub name: String,
pub opcodes: Vec<u32>,
pub num_micro_ops: u32,
pub stages: Vec<(String, u32)>,
pub ports: Vec<usize>,
pub dual_issue: bool,
pub reservation: Option<ReservationTable>,
}
#[derive(Debug, Clone)]
pub struct ItineraryData {
pub target: String,
pub itineraries: Vec<InstrItinerary>,
pub opcode_map: HashMap<u32, usize>,
}
impl ItineraryData {
pub fn new(target: &str) -> Self {
Self {
target: target.to_string(),
itineraries: Vec::new(),
opcode_map: HashMap::new(),
}
}
pub fn add_itinerary(&mut self, itinerary: InstrItinerary) {
let idx = self.itineraries.len();
for &opcode in &itinerary.opcodes {
self.opcode_map.insert(opcode, idx);
}
self.itineraries.push(itinerary);
}
pub fn get_itinerary(&self, opcode: u32) -> Option<&InstrItinerary> {
self.opcode_map
.get(&opcode)
.and_then(|&idx| self.itineraries.get(idx))
}
pub fn get_num_micro_ops(&self, opcode: u32) -> u32 {
self.get_itinerary(opcode)
.map(|i| i.num_micro_ops)
.unwrap_or(1)
}
pub fn get_latency(&self, opcode: u32) -> u32 {
self.get_itinerary(opcode)
.map(|i| i.stages.iter().map(|(_, c)| *c).sum())
.unwrap_or(1)
}
pub fn can_dual_issue(&self, op_a: u32, op_b: u32) -> bool {
let it_a = self.get_itinerary(op_a);
let it_b = self.get_itinerary(op_b);
match (it_a, it_b) {
(Some(a), Some(b)) => {
let ports_a: HashSet<usize> = a.ports.iter().copied().collect();
let ports_b: HashSet<usize> = b.ports.iter().copied().collect();
ports_a.is_disjoint(&ports_b) && a.dual_issue && b.dual_issue
}
_ => false,
}
}
pub fn x86_64_default() -> Self {
let mut data = Self::new("x86_64");
data.add_itinerary(InstrItinerary {
name: "ALU".to_string(),
opcodes: vec![1],
num_micro_ops: 1,
stages: vec![("Issue".to_string(), 1), ("Execute".to_string(), 1)],
ports: vec![0, 1, 5, 6],
dual_issue: true,
reservation: Some(ReservationTable::alu_default()),
});
data.add_itinerary(InstrItinerary {
name: "Load".to_string(),
opcodes: vec![2],
num_micro_ops: 1,
stages: vec![
("Issue".to_string(), 1),
("AGU".to_string(), 1),
("Cache".to_string(), 3),
],
ports: vec![2, 3],
dual_issue: true,
reservation: Some(ReservationTable::load_default()),
});
data.add_itinerary(InstrItinerary {
name: "Store".to_string(),
opcodes: vec![3],
num_micro_ops: 1,
stages: vec![
("Issue".to_string(), 1),
("AGU".to_string(), 1),
("StoreData".to_string(), 1),
],
ports: vec![4],
dual_issue: false,
reservation: None,
});
data
}
}
impl Default for ItineraryData {
fn default() -> Self {
Self::new("generic")
}
}
#[derive(Debug, Clone)]
pub struct FullScheduleDAG {
pub graph: DepGraph,
pub regions: Vec<SchedulingRegion>,
pub critical_paths: Vec<u32>,
pub depths: Vec<u32>,
pub heights: Vec<u32>,
pub resource_model: ResourceModel,
pub hazard: HazardRecognizer,
pub forwarding: ForwardingModel,
}
impl FullScheduleDAG {
pub fn from_basic_block(instructions: &[MachineInstr], target: &str) -> Self {
let n = instructions.len();
let resource_model = ResourceModel::default_model();
let hazard = HazardRecognizer::new(8, true);
let forwarding = ForwardingModel::x86_64_default();
let mut graph = DepGraph::new();
for (idx, mi) in instructions.iter().enumerate() {
let latency = resource_model.get_latency(mi.opcode);
graph.nodes.push(SchedNode {
instr_idx: idx,
opcode: mi.opcode,
latency,
critical_path: 0,
successors: Vec::new(),
predecessors: Vec::new(),
scheduled: false,
unscheduled_preds: 0,
});
}
graph.node_count = n;
let mut last_def: HashMap<u64, usize> = HashMap::new();
let mut last_use: HashMap<u64, Vec<usize>> = HashMap::new();
for (idx, mi) in instructions.iter().enumerate() {
let def_regs: Vec<u64> = mi.def.into_iter().map(|d| d as u64).collect();
let use_regs: Vec<u64> = mi
.operands
.iter()
.filter_map(|op| match op {
MachineOperand::Reg(r) => Some(*r as u64),
MachineOperand::PhysReg(pr) => Some(*pr as u64 + (1u64 << 32)),
_ => None,
})
.collect();
for ® in &use_regs {
if let Some(&def_idx) = last_def.get(®) {
if def_idx < idx {
graph.nodes[def_idx].successors.push(idx);
graph.nodes[idx].predecessors.push(def_idx);
}
}
last_use.entry(reg).or_default().push(idx);
}
for ® in &def_regs {
if let Some(&prev_def) = last_def.get(®) {
if prev_def < idx {
graph.nodes[prev_def].successors.push(idx);
graph.nodes[idx].predecessors.push(prev_def);
}
}
if let Some(prev_uses) = last_use.get(®) {
for &use_idx in prev_uses {
if use_idx < idx {
graph.nodes[use_idx].successors.push(idx);
graph.nodes[idx].predecessors.push(use_idx);
}
}
}
last_def.insert(reg, idx);
last_use.remove(®);
}
}
for node in &mut graph.nodes {
node.unscheduled_preds = node.predecessors.len();
}
let mut region_builder = RegionBuilder::new(target, false);
region_builder.build_regions(instructions);
let mut critical_paths = vec![0u32; n];
for idx in (0..n).rev() {
let node_latency = graph.nodes[idx].latency;
let mut max_succ = 0u32;
for &succ in &graph.nodes[idx].successors {
if succ < n {
max_succ = max_succ.max(critical_paths[succ]);
}
}
critical_paths[idx] = node_latency + max_succ;
}
let mut depths = vec![0u32; n];
for idx in 0..n {
let mut max_pred = 0u32;
for &pred in &graph.nodes[idx].predecessors {
if pred < n {
max_pred = max_pred.max(depths[pred] + graph.nodes[pred].latency);
}
}
depths[idx] = max_pred;
}
let heights = critical_paths.clone();
Self {
graph,
regions: region_builder.regions,
critical_paths,
depths,
heights,
resource_model,
hazard,
forwarding,
}
}
pub fn ready_nodes(&self, scheduled: &[bool]) -> Vec<usize> {
(0..self.graph.node_count)
.filter(|&i| {
if scheduled[i] {
return false;
}
self.graph.nodes[i]
.predecessors
.iter()
.all(|&p| scheduled[p])
})
.collect()
}
pub fn estimate_length(&self) -> u32 {
self.critical_paths.iter().copied().max().unwrap_or(0)
}
pub fn max_depth(&self) -> u32 {
self.depths.iter().copied().max().unwrap_or(0)
}
pub fn print_dag(&self) {
eprintln!(
"FullScheduleDAG: {} nodes, {} regions",
self.graph.node_count,
self.regions.len()
);
eprintln!(" Critical path: {}", self.estimate_length());
eprintln!(" Max depth: {}", self.max_depth());
for region in &self.regions {
eprintln!(
" Region [{}-{}] top={} bottom={} term={}",
region.start, region.end, region.is_top, region.is_bottom, region.has_terminator
);
}
}
}
pub struct ScheduleDAGMutator {
pub enforce_mem_order: bool,
pub break_anti_deps: bool,
pub added_edges: usize,
}
impl ScheduleDAGMutator {
pub fn new() -> Self {
Self {
enforce_mem_order: true,
break_anti_deps: true,
added_edges: 0,
}
}
pub fn add_memory_barriers(&mut self, graph: &mut DepGraph, instrs: &[MachineInstr]) {
for i in 0..graph.node_count {
for j in (i + 1)..graph.node_count {
let is_store_i = Self::is_store(&instrs[i].opcode);
let is_load_j = Self::is_load(&instrs[j].opcode);
let is_store_j = Self::is_store(&instrs[j].opcode);
if is_store_i && (is_load_j || is_store_j) {
if !graph.nodes[i].successors.contains(&j) {
graph.nodes[i].successors.push(j);
graph.nodes[j].predecessors.push(i);
graph.nodes[j].unscheduled_preds += 1;
self.added_edges += 1;
}
}
}
}
}
fn is_store(opcode: &u32) -> bool {
matches!(opcode, 3 | 11 | 117) }
fn is_load(opcode: &u32) -> bool {
matches!(opcode, 2 | 12 | 118) }
}
impl Default for ScheduleDAGMutator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CopySuppressor {
pub copies_eliminated: usize,
copy_map: HashMap<u64, u64>,
}
impl CopySuppressor {
pub fn new() -> Self {
Self {
copies_eliminated: 0,
copy_map: HashMap::new(),
}
}
pub fn suppress_copies(&mut self, instrs: &mut Vec<MachineInstr>) {
self.copy_map.clear();
let mut new_instrs = Vec::new();
for mi in instrs.drain(..) {
if mi.opcode == 1 && mi.def.is_some() && mi.operands.len() == 1 {
if let MachineOperand::Reg(src) = mi.operands[0] {
if let Some(dst) = mi.def {
self.copy_map.insert(dst as u64, src as u64);
self.copies_eliminated += 1;
continue; }
}
}
let mut rewritten = mi.clone();
for op in &mut rewritten.operands {
if let MachineOperand::Reg(r) = op {
if let Some(&src) = self.copy_map.get(&(*r as u64)) {
*op = MachineOperand::Reg(src as u32);
}
}
}
new_instrs.push(rewritten);
}
*instrs = new_instrs;
}
}
impl Default for CopySuppressor {
fn default() -> Self {
Self::new()
}
}
pub struct LoadClusterer {
pub cache_line_size: u32,
pub max_cluster_size: usize,
pub loads_clustered: usize,
}
impl LoadClusterer {
pub fn new() -> Self {
Self {
cache_line_size: 64,
max_cluster_size: 8,
loads_clustered: 0,
}
}
pub fn cluster_loads(&mut self, instrs: &mut [MachineInstr]) {
let n = instrs.len();
if n < 2 {
return;
}
let mut load_info: Vec<(usize, i64)> = Vec::new();
for (i, mi) in instrs.iter().enumerate() {
if mi.opcode == 2 {
let addr = mi
.operands
.iter()
.find_map(|op| {
if let MachineOperand::Imm(imm) = op {
Some(*imm)
} else {
None
}
})
.unwrap_or(0);
load_info.push((i, addr));
}
}
if load_info.len() < 2 {
return;
}
load_info.sort_by_key(|(_, addr)| *addr);
let mut cluster_start = 0;
for i in 1..load_info.len() {
let (_, prev_addr) = load_info[cluster_start];
let (_, curr_addr) = load_info[i];
let same_line = (prev_addr as u64 / self.cache_line_size as u64)
== (curr_addr as u64 / self.cache_line_size as u64);
let cluster_full = (i - cluster_start) >= self.max_cluster_size;
if !same_line || cluster_full {
let count = i - cluster_start;
if count > 1 {
self.loads_clustered += count;
}
cluster_start = i;
}
}
let remaining = load_info.len() - cluster_start;
if remaining > 1 {
self.loads_clustered += remaining;
}
}
}
impl Default for LoadClusterer {
fn default() -> Self {
Self::new()
}
}
pub struct StoreClusterer {
pub cache_line_size: u32,
pub max_cluster_size: usize,
pub stores_clustered: usize,
}
impl StoreClusterer {
pub fn new() -> Self {
Self {
cache_line_size: 64,
max_cluster_size: 6,
stores_clustered: 0,
}
}
pub fn cluster_stores(&mut self, instrs: &mut [MachineInstr]) {
let n = instrs.len();
if n < 2 {
return;
}
let mut store_info: Vec<(usize, i64)> = Vec::new();
for (i, mi) in instrs.iter().enumerate() {
if mi.opcode == 3 {
let addr = mi
.operands
.iter()
.find_map(|op| {
if let MachineOperand::Imm(imm) = op {
Some(*imm)
} else {
None
}
})
.unwrap_or(0);
store_info.push((i, addr));
}
}
if store_info.len() < 2 {
return;
}
store_info.sort_by_key(|(_, addr)| *addr);
let mut cluster_start = 0;
for i in 1..store_info.len() {
let (_, prev_addr) = store_info[cluster_start];
let (_, curr_addr) = store_info[i];
let same_line = (prev_addr as u64 / self.cache_line_size as u64)
== (curr_addr as u64 / self.cache_line_size as u64);
let cluster_full = (i - cluster_start) >= self.max_cluster_size;
if !same_line || cluster_full {
let count = i - cluster_start;
if count > 1 {
self.stores_clustered += count;
}
cluster_start = i;
}
}
let remaining = store_info.len() - cluster_start;
if remaining > 1 {
self.stores_clustered += remaining;
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MacroFusionKind {
CmpJcc,
TestJcc,
AddJcc,
AndJcc,
XorJcc,
IncJcc,
SubJcc,
}
#[derive(Debug, Clone)]
pub struct MacroFusionDetector {
pub support_fusion: bool,
pub support_extended_fusion: bool,
pub detected_pairs: Vec<(usize, usize, MacroFusionKind)>,
}
impl MacroFusionDetector {
pub fn new(microarch: &str) -> Self {
let extended = matches!(
microarch,
"sandybridge"
| "ivybridge"
| "haswell"
| "broadwell"
| "skylake"
| "skylake_avx512"
| "icelake"
| "alderlake"
| "raptorlake"
| "meteorlake"
| "lunarlake"
| "zen3"
| "zen4"
| "zen5"
);
Self {
support_fusion: true,
support_extended_fusion: extended,
detected_pairs: Vec::new(),
}
}
pub fn detect_fusion_pairs(&mut self, instrs: &[MachineInstr]) {
let n = instrs.len();
if n < 2 {
return;
}
for i in 0..(n - 1) {
let first_op = instrs[i].opcode;
let second_op = instrs[i + 1].opcode;
if !Self::is_branch(second_op) {
continue;
}
if let Some(kind) = Self::classify_fusion(first_op, self.support_extended_fusion) {
self.detected_pairs.push((i, i + 1, kind));
}
}
}
fn is_branch(opcode: u32) -> bool {
matches!(opcode, 7 | 8 | 9 | 15 | 16 | 17 | 102..=115) }
fn classify_fusion(opcode: u32, extended: bool) -> Option<MacroFusionKind> {
match opcode {
18 => Some(MacroFusionKind::CmpJcc), 26 => Some(MacroFusionKind::TestJcc), 2 if extended => Some(MacroFusionKind::AddJcc), 3 if extended => Some(MacroFusionKind::SubJcc), 6 if extended => Some(MacroFusionKind::AndJcc), 8 if extended => Some(MacroFusionKind::XorJcc), 20 | 21 if extended => Some(MacroFusionKind::IncJcc), _ => None,
}
}
pub fn fused_uop_count(&self) -> usize {
self.detected_pairs.len()
}
}
#[derive(Debug, Clone)]
pub struct MicroFusionDetector {
pub support_fusion: bool,
pub load_op_fusions: Vec<usize>,
pub store_fusions: Vec<usize>,
}
impl MicroFusionDetector {
pub fn new() -> Self {
Self {
support_fusion: true,
load_op_fusions: Vec::new(),
store_fusions: Vec::new(),
}
}
pub fn detect_load_op_fusion(&mut self, instrs: &[MachineInstr]) {
for (i, mi) in instrs.iter().enumerate() {
let is_alu = matches!(mi.opcode, 2 | 3 | 6 | 7 | 8); if is_alu
&& mi
.operands
.iter()
.any(|op| matches!(op, MachineOperand::Reg(0..=31)))
{
self.load_op_fusions.push(i);
}
}
}
pub fn detect_store_fusion(&mut self, instrs: &[MachineInstr]) {
for (i, mi) in instrs.iter().enumerate() {
if mi.opcode == 3 && mi.operands.len() >= 2 {
self.store_fusions.push(i);
}
}
}
}
#[derive(Debug, Clone)]
pub struct LoopAligner {
pub alignment: u32,
pub align_branches: bool,
pub max_nop_bytes: u32,
pub nops_inserted: usize,
}
impl LoopAligner {
pub fn new(alignment: u32) -> Self {
Self {
alignment: alignment.clamp(8, 64),
align_branches: false,
max_nop_bytes: 15,
nops_inserted: 0,
}
}
pub fn compute_alignment_nops(&self, current_offset: u64) -> u32 {
let remainder = (current_offset % self.alignment as u64) as u32;
if remainder == 0 {
return 0;
}
let needed = self.alignment - remainder;
needed.min(self.max_nop_bytes)
}
pub fn generate_nop_sequence(nop_count: u32) -> Vec<u8> {
const NOP_TABLE: &[(u32, &[u8])] = &[
(1, &[0x90]),
(2, &[0x66, 0x90]),
(3, &[0x0F, 0x1F, 0x00]),
(4, &[0x0F, 0x1F, 0x40, 0x00]),
(5, &[0x0F, 0x1F, 0x44, 0x00, 0x00]),
(6, &[0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00]),
(7, &[0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00]),
(8, &[0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]),
(9, &[0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]),
];
let mut remaining = nop_count;
let mut seq = Vec::new();
while remaining > 0 {
let take = remaining.min(9);
if let Some(&(_, nop_bytes)) = NOP_TABLE.iter().find(|(len, _)| *len == take) {
seq.extend_from_slice(nop_bytes);
remaining -= take;
} else {
seq.push(0x90); remaining -= 1;
}
}
seq
}
pub fn align_loop_header(&mut self, current_offset: u64, instrs: &mut Vec<MachineInstr>) {
let nop_count = self.compute_alignment_nops(current_offset);
if nop_count > 0 {
let nop_instr = MachineInstr {
opcode: 0, operands: vec![MachineOperand::Imm(nop_count as i64)],
def: None,
size: 0,
};
instrs.insert(0, nop_instr);
self.nops_inserted += 1;
}
}
}
#[derive(Debug, Clone)]
pub struct PrefixOptimizer {
pub optimize_rex: bool,
pub prefix_bytes_saved: usize,
}
impl PrefixOptimizer {
pub fn new() -> Self {
Self {
optimize_rex: true,
prefix_bytes_saved: 0,
}
}
pub fn optimize(&mut self, instrs: &[MachineInstr]) -> usize {
let mut saved = 0usize;
for mi in instrs {
if let Some(def) = mi.def {
if def >= 8 {
}
}
for op in &mi.operands {
if let MachineOperand::Reg(r) = op {
if *r >= 8 {
}
}
}
}
self.prefix_bytes_saved += saved;
saved
}
}
pub struct SubRegLiveness {
pub live_ranges: HashMap<u32, (usize, usize)>,
pub enabled: bool,
}
impl SubRegLiveness {
pub fn new() -> Self {
Self {
live_ranges: HashMap::new(),
enabled: true,
}
}
pub fn overlaps(&self, reg: u32, cycle_a: usize, cycle_b: usize) -> bool {
if let Some(&(start, end)) = self.live_ranges.get(®) {
let overlap_start = start.max(cycle_a);
let overlap_end = end.min(cycle_b);
overlap_start <= overlap_end
} else {
false
}
}
pub fn record_access(&mut self, reg: u32, cycle: usize) {
let entry = self.live_ranges.entry(reg).or_insert((cycle, cycle));
entry.0 = entry.0.min(cycle);
entry.1 = entry.1.max(cycle);
}
}
#[derive(Debug, Clone)]
pub struct VLIWPacketFormation {
pub max_per_packet: usize,
pub packets: Vec<Vec<usize>>,
}
impl VLIWPacketFormation {
pub fn new(issue_width: usize) -> Self {
Self {
max_per_packet: issue_width,
packets: Vec::new(),
}
}
pub fn form_packets(&mut self, ready_nodes: &[usize], graph: &DepGraph) {
let mut current_packet: Vec<usize> = Vec::new();
let mut used_nodes: HashSet<usize> = HashSet::new();
for &node in ready_nodes {
if current_packet.len() >= self.max_per_packet {
self.packets.push(std::mem::take(&mut current_packet));
}
let has_dep = current_packet.iter().any(|&p| {
graph.nodes[p].successors.contains(&node)
|| graph.nodes[node].predecessors.contains(&p)
});
if has_dep || used_nodes.contains(&node) {
continue;
}
current_packet.push(node);
used_nodes.insert(node);
}
if !current_packet.is_empty() {
self.packets.push(current_packet);
}
}
pub fn packet_count(&self) -> usize {
self.packets.len()
}
pub fn utilization(&self) -> f64 {
let total_instrs: usize = self.packets.iter().map(|p| p.len()).sum();
let max_capacity = self.packets.len() * self.max_per_packet;
if max_capacity == 0 {
0.0
} else {
total_instrs as f64 / max_capacity as f64
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_mi(opcode: u32, def: Option<u32>) -> MachineInstr {
MachineInstr {
opcode,
operands: Vec::new(),
def,
}
}
fn make_mi_with_ops(
opcode: u32,
def: Option<u32>,
operands: Vec<MachineOperand>,
) -> MachineInstr {
MachineInstr {
opcode,
operands,
def,
}
}
#[test]
fn test_resource_model_default() {
let model = ResourceModel::default_model();
assert_eq!(model.issue_width, 4);
assert_eq!(model.resources.len(), 4);
assert_eq!(model.get_latency(1), 1);
assert_eq!(model.get_latency(2), 3);
assert_eq!(model.get_latency(999), 1); }
#[test]
fn test_machine_scheduler_new() {
let ms = MachineScheduler::new("x86_64-unknown-linux-gnu");
assert_eq!(ms.target, "x86_64-unknown-linux-gnu");
assert_eq!(ms.reordered, 0);
assert_eq!(ms.resource_model.issue_width, 4);
}
#[test]
fn test_machine_scheduler_default() {
let ms = MachineScheduler::default();
assert_eq!(ms.target, "generic");
}
#[test]
fn test_build_dependence_graph_empty() {
let ms = MachineScheduler::new("test");
let instructions: Vec<MachineInstr> = Vec::new();
let graph = ms.build_dependence_graph(&instructions);
assert_eq!(graph.node_count, 0);
}
#[test]
fn test_build_dependence_graph_single() {
let ms = MachineScheduler::new("test");
let instructions = vec![make_mi(1, Some(1))];
let graph = ms.build_dependence_graph(&instructions);
assert_eq!(graph.node_count, 1);
assert!(graph.nodes[0].predecessors.is_empty());
assert!(graph.nodes[0].successors.is_empty());
}
#[test]
fn test_build_dependence_graph_raw() {
let ms = MachineScheduler::new("test");
let instructions = vec![
make_mi_with_ops(1, Some(1), vec![]),
make_mi_with_ops(2, None, vec![MachineOperand::Reg(1)]),
];
let graph = ms.build_dependence_graph(&instructions);
assert_eq!(graph.node_count, 2);
assert!(graph.nodes[1].predecessors.contains(&0));
assert!(graph.nodes[0].successors.contains(&1));
}
#[test]
fn test_build_dependence_graph_waw() {
let ms = MachineScheduler::new("test");
let instructions = vec![make_mi(1, Some(1)), make_mi(2, Some(1))];
let graph = ms.build_dependence_graph(&instructions);
assert_eq!(graph.node_count, 2);
assert!(graph.nodes[0].successors.contains(&1));
assert!(graph.nodes[1].predecessors.contains(&0));
}
#[test]
fn test_compute_critical_path_linear() {
let ms = MachineScheduler::new("test");
let mut graph = DepGraph::new();
for i in 0..5 {
graph.nodes.push(SchedNode {
instr_idx: i,
opcode: 1,
latency: 2,
critical_path: 0,
successors: if i + 1 < 5 { vec![i + 1] } else { vec![] },
predecessors: if i > 0 { vec![i - 1] } else { vec![] },
scheduled: false,
unscheduled_preds: if i > 0 { 1 } else { 0 },
});
}
graph.node_count = 5;
let cp = ms.compute_critical_path(&graph);
assert_eq!(cp.len(), 5);
assert_eq!(cp[4], 2); assert_eq!(cp[3], 4); assert_eq!(cp[0], 10); }
#[test]
fn test_list_schedule_simple() {
let ms = MachineScheduler::new("test");
let mut graph = DepGraph::new();
for i in 0..3 {
graph.nodes.push(SchedNode {
instr_idx: i,
opcode: 1,
latency: 1,
critical_path: 0,
successors: Vec::new(),
predecessors: Vec::new(),
scheduled: false,
unscheduled_preds: 0,
});
}
graph.node_count = 3;
let cp = vec![1, 1, 1];
let schedule = ms.list_schedule(&graph, &cp);
assert_eq!(schedule.len(), 3);
assert_eq!(schedule, vec![0, 1, 2]);
}
#[test]
fn test_list_schedule_with_deps() {
let ms = MachineScheduler::new("test");
let mut graph = DepGraph::new();
graph.nodes.push(SchedNode {
instr_idx: 0,
opcode: 1,
latency: 1,
critical_path: 0,
successors: vec![1],
predecessors: vec![],
scheduled: false,
unscheduled_preds: 0,
});
graph.nodes.push(SchedNode {
instr_idx: 1,
opcode: 1,
latency: 1,
critical_path: 0,
successors: vec![2],
predecessors: vec![0],
scheduled: false,
unscheduled_preds: 1,
});
graph.nodes.push(SchedNode {
instr_idx: 2,
opcode: 1,
latency: 1,
critical_path: 0,
successors: vec![],
predecessors: vec![1],
scheduled: false,
unscheduled_preds: 1,
});
graph.node_count = 3;
let cp = vec![3, 2, 1];
let schedule = ms.list_schedule(&graph, &cp);
assert_eq!(schedule, vec![0, 1, 2]);
}
#[test]
fn test_get_instruction_latency() {
let ms = MachineScheduler::new("test");
let mi = MachineInstr::new(1);
let lat = ms.get_instruction_latency(&mi);
assert_eq!(lat, 1);
}
#[test]
fn test_compute_resource_conflicts_empty() {
let ms = MachineScheduler::new("test");
let schedule: Vec<usize> = Vec::new();
let instructions: Vec<MachineInstr> = Vec::new();
let conflicts = ms.compute_resource_conflicts(&schedule, &instructions);
assert!(conflicts.is_empty());
}
#[test]
fn test_schedule_empty_function() {
let mut ms = MachineScheduler::new("test");
let mut mf = MachineFunction::new("empty");
let count = ms.schedule(&mut mf);
assert_eq!(count, 0);
}
#[test]
fn test_schedule_single_block() {
let mut ms = MachineScheduler::new("test");
let mut mf = MachineFunction::new("func");
let mut mbb = MachineBasicBlock {
name: "entry".to_string(),
instructions: vec![make_mi(1, Some(1)), make_mi(2, None), make_mi(3, Some(2))],
successors: Vec::new(),
};
mf.push_block(mbb);
let count = ms.schedule(&mut mf);
assert!(count >= 0);
assert_eq!(mf.blocks[0].instructions.len(), 3);
}
#[test]
fn test_hash_instruction() {
let ms = MachineScheduler::new("test");
let mi = make_mi(42, Some(7));
let hash = ms.hash_instruction(&mi);
assert_ne!(hash, 0);
}
#[test]
fn test_reg_to_key() {
let vreg = MachineOperand::Reg(5);
let preg = MachineOperand::PhysReg(3);
let imm = MachineOperand::Imm(42);
assert_eq!(MachineScheduler::reg_to_key(&vreg), 5);
assert_eq!(MachineScheduler::reg_to_key(&preg), 3 + (1u64 << 32));
assert_eq!(MachineScheduler::reg_to_key(&imm), 0);
}
#[test]
fn test_hazard_recognizer_new() {
let hr = HazardRecognizer::new(8, true);
assert_eq!(hr.resource_count, 8);
assert!(hr.forwarding_enabled);
assert_eq!(hr.current_cycle, 0);
}
#[test]
fn test_hazard_recognizer_default() {
let hr = HazardRecognizer::default();
assert_eq!(hr.resource_count, 8);
}
#[test]
fn test_can_issue_no_hazards() {
let hr = HazardRecognizer::new(4, false);
let result = hr.can_issue(1, &[10], &[20], &[0]);
assert_eq!(result, HazardKind::None);
}
#[test]
fn test_can_issue_structural_hazard() {
let mut hr = HazardRecognizer::new(4, false);
hr.emit(1, &[10], &[], &[0]);
let result = hr.can_issue(1, &[11], &[], &[0]);
assert_eq!(result, HazardKind::Structural);
}
#[test]
fn test_can_issue_raw_hazard() {
let mut hr = HazardRecognizer::new(4, false);
hr.set_latency(1, 3);
hr.emit(1, &[10], &[], &[0]);
hr.advance_cycle();
let result = hr.can_issue(1, &[], &[10], &[0]);
assert_eq!(result, HazardKind::RAW);
}
#[test]
fn test_forwarding_reduces_raw_hazard() {
let mut hr = HazardRecognizer::new(4, true);
hr.set_latency(1, 1);
hr.emit(1, &[10], &[], &[0]);
hr.advance_cycle();
let result = hr.can_issue(1, &[], &[10], &[0]);
assert_eq!(result, HazardKind::None);
}
#[test]
fn test_hazard_reset() {
let mut hr = HazardRecognizer::new(4, false);
hr.emit(1, &[10], &[], &[0]);
hr.reset();
assert_eq!(hr.current_cycle, 0);
assert!(hr.write_registers.is_empty());
assert!(hr.resource_schedule.is_empty());
}
#[test]
fn test_hazard_scheduled_cycles() {
let mut hr = HazardRecognizer::new(4, false);
assert_eq!(hr.scheduled_cycles(), 0);
hr.advance_cycle();
hr.advance_cycle();
assert_eq!(hr.scheduled_cycles(), 2);
}
#[test]
fn test_forwarding_model_new() {
let fm = ForwardingModel::new();
assert!(fm.paths.is_empty());
assert!(fm.base_latencies.is_empty());
}
#[test]
fn test_forwarding_effective_latency_no_paths() {
let mut fm = ForwardingModel::new();
fm.set_latency(1, 3);
assert_eq!(fm.get_effective_latency(1, 1), 3);
}
#[test]
fn test_forwarding_reduces_latency() {
let mut fm = ForwardingModel::new();
fm.set_latency(1, 3);
fm.add_path(ForwardingPath {
from_stage: 2,
to_stage: 1,
latency_reduction: 1,
universal: true,
producer_opcodes: vec![],
consumer_opcodes: vec![],
});
assert_eq!(fm.get_effective_latency(1, 1), 2);
}
#[test]
fn test_forwarding_can_forward() {
let mut fm = ForwardingModel::new();
fm.set_latency(1, 3);
fm.add_path(ForwardingPath {
from_stage: 2,
to_stage: 1,
latency_reduction: 2,
universal: false,
producer_opcodes: vec![1],
consumer_opcodes: vec![1],
});
assert!(fm.can_forward(1, 1));
assert!(!fm.can_forward(2, 1));
}
#[test]
fn test_forwarding_x86_64_default() {
let fm = ForwardingModel::x86_64_default();
assert!(!fm.paths.is_empty());
assert!(fm.base_latencies.len() > 0);
}
#[test]
fn test_forwarding_aarch64_default() {
let fm = ForwardingModel::aarch64_default();
assert!(!fm.paths.is_empty());
}
#[test]
fn test_micro_fusion_new() {
let mf = MicroFusion::new("x86_64");
assert!(mf.enabled);
assert_eq!(mf.target, "x86_64");
}
#[test]
fn test_micro_fusion_disabled_empty_target() {
let mf = MicroFusion::new("");
assert!(!mf.enabled);
}
#[test]
fn test_micro_fusion_can_fuse_load() {
let mf = MicroFusion::x86_default();
let addr = make_mi(8, Some(5)); let mem = make_mi_with_ops(2, None, vec![MachineOperand::Reg(5)]); assert!(mf.can_micro_fuse(&addr, &mem).is_some());
}
#[test]
fn test_micro_fusion_cannot_fuse_no_def() {
let mf = MicroFusion::x86_default();
let addr = make_mi(8, None); let mem = make_mi_with_ops(2, None, vec![MachineOperand::Reg(5)]);
assert!(mf.can_micro_fuse(&addr, &mem).is_none());
}
#[test]
fn test_cluster_formation_new() {
let cf = ClusterFormation::new(8);
assert!(cf.enabled);
assert_eq!(cf.max_cluster_size, 8);
assert!(cf.clusters.is_empty());
}
#[test]
fn test_cluster_formation_load_clusters() {
let mut cf = ClusterFormation::new(8);
let instrs = vec![
make_mi_with_ops(2, Some(1), vec![MachineOperand::Reg(10)]),
make_mi_with_ops(2, Some(2), vec![MachineOperand::Reg(10)]),
make_mi_with_ops(2, Some(3), vec![MachineOperand::Reg(20)]),
];
cf.form_clusters(&instrs);
let loads = cf.load_clusters();
assert_eq!(loads.len(), 1);
assert_eq!(loads[0].instructions.len(), 2);
assert_eq!(loads[0].base_register, 10);
}
#[test]
fn test_cluster_formation_should_cluster() {
let cf = ClusterFormation::new(8);
let a = make_mi_with_ops(2, Some(1), vec![MachineOperand::Reg(10)]);
let b = make_mi_with_ops(2, Some(2), vec![MachineOperand::Reg(10)]);
assert!(cf.should_cluster(&a, &b));
}
#[test]
fn test_region_builder_new() {
let rb = RegionBuilder::new("x86_64", false);
assert_eq!(rb.region_count(), 0);
}
#[test]
fn test_region_builder_empty() {
let mut rb = RegionBuilder::new("generic", false);
rb.build_regions(&[]);
assert_eq!(rb.region_count(), 0);
}
#[test]
fn test_region_builder_single_region() {
let mut rb = RegionBuilder::new("generic", false);
let instrs = vec![make_mi(1, Some(1)), make_mi(1, Some(2))];
rb.build_regions(&instrs);
assert_eq!(rb.region_count(), 1);
}
#[test]
fn test_region_builder_split_on_branch() {
let mut rb = RegionBuilder::new("generic", false);
let instrs = vec![make_mi(1, Some(1)), make_mi(7, None), make_mi(1, Some(3))];
rb.build_regions(&instrs);
assert!(rb.region_count() >= 1);
}
#[test]
fn test_post_ra_scheduler_new() {
let prs = PostRAScheduler::new("x86_64");
assert_eq!(prs.target, "x86_64");
assert!(prs.break_anti_deps);
}
#[test]
fn test_post_ra_schedule_empty() {
let mut prs = PostRAScheduler::new("test");
let mut instrs: Vec<MachineInstr> = vec![];
let count = prs.schedule(&mut instrs);
assert_eq!(count, 0);
}
#[test]
fn test_post_ra_schedule_single() {
let mut prs = PostRAScheduler::new("test");
let mut instrs = vec![make_mi(1, Some(1))];
let count = prs.schedule(&mut instrs);
assert_eq!(count, 0);
}
#[test]
fn test_post_ra_break_anti_deps() {
let prs = PostRAScheduler::new("test");
let mut graph = DepGraph::new();
graph.nodes.push(SchedNode {
instr_idx: 0,
opcode: 1,
latency: 1,
critical_path: 0,
successors: vec![1],
predecessors: vec![],
scheduled: false,
unscheduled_preds: 0,
});
graph.nodes.push(SchedNode {
instr_idx: 1,
opcode: 1,
latency: 1,
critical_path: 0,
successors: vec![],
predecessors: vec![0],
scheduled: false,
unscheduled_preds: 1,
});
graph.node_count = 2;
let mut instrs = vec![
make_mi_with_ops(1, None, vec![MachineOperand::Reg(5)]),
make_mi(1, Some(5)),
];
let broken = prs.break_anti_dependencies(&mut graph, &mut instrs);
assert!(broken >= 0);
}
#[test]
fn test_reservation_table_new() {
let rt = ReservationTable::new(5, 4, 1);
assert_eq!(rt.num_stages, 5);
assert_eq!(rt.num_units, 4);
assert_eq!(rt.duration, 5);
}
#[test]
fn test_reservation_table_reserve() {
let mut rt = ReservationTable::new(5, 4, 1);
rt.reserve(0, 0);
assert!(rt.is_reserved(0, 0));
assert!(!rt.is_reserved(0, 1));
}
#[test]
fn test_reservation_table_collision() {
let mut rt1 = ReservationTable::new(5, 4, 1);
rt1.reserve(0, 0);
let mut rt2 = ReservationTable::new(5, 4, 2);
rt2.reserve(0, 0);
assert!(rt1.collides_with(&rt2, 0));
}
#[test]
fn test_reservation_table_no_collision() {
let mut rt1 = ReservationTable::new(5, 4, 1);
rt1.reserve(0, 0);
let mut rt2 = ReservationTable::new(5, 4, 2);
rt2.reserve(0, 1);
assert!(!rt1.collides_with(&rt2, 0));
}
#[test]
fn test_reservation_table_alu_default() {
let rt = ReservationTable::alu_default();
assert_eq!(rt.opcode, 1);
assert!(rt.is_reserved(0, 0));
}
#[test]
fn test_reservation_table_load_default() {
let rt = ReservationTable::load_default();
assert_eq!(rt.opcode, 2);
}
#[test]
fn test_bidirectional_scheduler_new() {
let bs = BidirectionalScheduler::new("x86_64");
assert_eq!(bs.target, "x86_64");
assert!(bs.forward_schedule.is_empty());
assert!(bs.backward_schedule.is_empty());
}
#[test]
fn test_bidirectional_schedule_empty() {
let mut bs = BidirectionalScheduler::new("test");
let graph = DepGraph::new();
let instrs: Vec<MachineInstr> = vec![];
let result = bs.schedule(&graph, &instrs);
assert!(result.is_empty());
}
#[test]
fn test_bidirectional_schedule_independent() {
let mut bs = BidirectionalScheduler::new("test");
let mut graph = DepGraph::new();
for i in 0..3 {
graph.nodes.push(SchedNode {
instr_idx: i,
opcode: 1,
latency: 1,
critical_path: 0,
successors: vec![],
predecessors: vec![],
scheduled: false,
unscheduled_preds: 0,
});
}
graph.node_count = 3;
let instrs = vec![
make_mi(1, Some(0)),
make_mi(1, Some(1)),
make_mi(1, Some(2)),
];
let result = bs.schedule(&graph, &instrs);
assert_eq!(result.len(), 3);
}
#[test]
fn test_itinerary_data_new() {
let id = ItineraryData::new("x86_64");
assert_eq!(id.target, "x86_64");
assert!(id.itineraries.is_empty());
}
#[test]
fn test_itinerary_data_get_latency_unknown() {
let id = ItineraryData::new("test");
assert_eq!(id.get_latency(999), 1);
}
#[test]
fn test_itinerary_data_x86_64_default() {
let id = ItineraryData::x86_64_default();
assert!(id.itineraries.len() > 0);
let lat = id.get_latency(1);
assert!(lat > 0);
assert_eq!(id.get_num_micro_ops(1), 1);
}
#[test]
fn test_full_schedule_dag_empty() {
let dag = FullScheduleDAG::from_basic_block(&[], "generic");
assert_eq!(dag.graph.node_count, 0);
assert_eq!(dag.estimate_length(), 0);
}
#[test]
fn test_full_schedule_dag_basic() {
let instrs = vec![
make_mi(1, Some(1)),
make_mi_with_ops(2, Some(2), vec![MachineOperand::Reg(1)]),
make_mi_with_ops(3, None, vec![MachineOperand::Reg(2)]),
];
let dag = FullScheduleDAG::from_basic_block(&instrs, "generic");
assert_eq!(dag.graph.node_count, 3);
assert!(dag.estimate_length() > 0);
assert!(dag.regions.len() >= 1);
}
#[test]
fn test_full_schedule_dag_ready_nodes() {
let instrs = vec![make_mi(1, Some(1)), make_mi(1, Some(2))];
let dag = FullScheduleDAG::from_basic_block(&instrs, "generic");
let scheduled = vec![false; 2];
let ready = dag.ready_nodes(&scheduled);
assert_eq!(ready.len(), 2);
}
#[test]
fn test_full_schedule_dag_depths() {
let instrs = vec![
make_mi(1, Some(1)),
make_mi_with_ops(1, Some(2), vec![MachineOperand::Reg(1)]),
];
let dag = FullScheduleDAG::from_basic_block(&instrs, "generic");
assert_eq!(dag.depths[0], 0);
assert!(dag.depths[1] > 0);
}
#[test]
fn test_sched_dag_mutator_new() {
let mutator = ScheduleDAGMutator::new();
assert!(mutator.enforce_mem_order);
assert!(mutator.break_anti_deps);
}
#[test]
fn test_sched_dag_mutator_memory_barriers() {
let mut mutator = ScheduleDAGMutator::new();
let instrs = vec![
make_mi(3, None), make_mi(2, Some(1)), ];
let mut graph = DepGraph::new();
for i in 0..2 {
graph.nodes.push(SchedNode {
instr_idx: i,
opcode: instrs[i].opcode,
latency: 1,
critical_path: 0,
successors: vec![],
predecessors: vec![],
scheduled: false,
unscheduled_preds: 0,
});
}
graph.node_count = 2;
mutator.add_memory_barriers(&mut graph, &instrs);
assert!(mutator.added_edges > 0);
}
#[test]
fn test_copy_suppressor_self_copy() {
let mut suppressor = CopySuppressor::new();
let mut instrs = vec![make_mi_with_ops(1, Some(3), vec![MachineOperand::Reg(3)])];
suppressor.suppress_copies(&mut instrs);
assert!(instrs.is_empty());
assert_eq!(suppressor.copies_eliminated, 1);
}
#[test]
fn test_load_clusterer_empty() {
let mut clusterer = LoadClusterer::new();
let mut instrs: Vec<MachineInstr> = vec![];
clusterer.cluster_loads(&mut instrs);
assert_eq!(clusterer.loads_clustered, 0);
}
#[test]
fn test_store_clusterer_empty() {
let mut clusterer = StoreClusterer::new();
let mut instrs: Vec<MachineInstr> = vec![];
clusterer.cluster_stores(&mut instrs);
assert_eq!(clusterer.stores_clustered, 0);
}
#[test]
fn test_macro_fusion_skylake() {
let mut detector = MacroFusionDetector::new("skylake");
assert!(detector.support_fusion);
assert!(detector.support_extended_fusion);
}
#[test]
fn test_macro_fusion_detect_cmp_jcc() {
let mut detector = MacroFusionDetector::new("skylake");
let instrs = vec![make_mi(18, None), make_mi(16, None)]; detector.detect_fusion_pairs(&instrs);
assert_eq!(detector.fused_uop_count(), 1);
}
#[test]
fn test_macro_fusion_detect_test_jcc() {
let mut detector = MacroFusionDetector::new("icelake");
let instrs = vec![make_mi(26, None), make_mi(17, None)]; detector.detect_fusion_pairs(&instrs);
assert_eq!(detector.fused_uop_count(), 1);
}
#[test]
fn test_micro_fusion_detector_new() {
let mut detector = MicroFusionDetector::new();
assert!(detector.support_fusion);
assert!(detector.load_op_fusions.is_empty());
}
#[test]
fn test_micro_fusion_detect_load_op() {
let mut detector = MicroFusionDetector::new();
let instrs = vec![make_mi_with_ops(2, Some(1), vec![MachineOperand::Reg(0)])]; detector.detect_load_op_fusion(&instrs);
assert!(detector.load_op_fusions.contains(&0));
}
#[test]
fn test_loop_aligner_compute_nops() {
let aligner = LoopAligner::new(16);
assert_eq!(aligner.compute_alignment_nops(0), 0);
assert_eq!(aligner.compute_alignment_nops(1), 15);
assert_eq!(aligner.compute_alignment_nops(16), 0);
assert_eq!(aligner.compute_alignment_nops(17), 15);
}
#[test]
fn test_loop_aligner_nop_sequence() {
let seq = LoopAligner::generate_nop_sequence(0);
assert!(seq.is_empty());
let seq = LoopAligner::generate_nop_sequence(1);
assert_eq!(seq.len(), 1);
assert_eq!(seq[0], 0x90);
let seq = LoopAligner::generate_nop_sequence(3);
assert_eq!(seq.len(), 3);
}
#[test]
fn test_loop_aligner_insert_nops() {
let mut aligner = LoopAligner::new(16);
let mut instrs = vec![make_mi(1, Some(1))];
aligner.align_loop_header(1, &mut instrs);
assert_eq!(instrs.len(), 2); assert_eq!(instrs[0].opcode, 0); }
#[test]
fn test_prefix_optimizer_new() {
let opt = PrefixOptimizer::new();
assert!(opt.optimize_rex);
assert_eq!(opt.prefix_bytes_saved, 0);
}
#[test]
fn test_subreg_liveness_overlap() {
let mut liveness = SubRegLiveness::new();
liveness.record_access(0, 5);
liveness.record_access(0, 10);
assert!(liveness.overlaps(0, 5, 10));
assert!(!liveness.overlaps(0, 20, 25));
}
#[test]
fn test_vliw_packet_formation_empty() {
let mut formation = VLIWPacketFormation::new(4);
let graph = DepGraph::new();
formation.form_packets(&[], &graph);
assert_eq!(formation.packet_count(), 0);
}
#[test]
fn test_vliw_packet_formation_utilization() {
let mut formation = VLIWPacketFormation::new(4);
let mut graph = DepGraph::new();
for i in 0..4 {
graph.nodes.push(SchedNode {
instr_idx: i,
opcode: 1,
latency: 1,
critical_path: 0,
successors: vec![],
predecessors: vec![],
scheduled: false,
unscheduled_preds: 0,
});
}
graph.node_count = 4;
formation.form_packets(&[0, 1, 2, 3], &graph);
assert_eq!(formation.packet_count(), 1);
assert!(formation.utilization() > 0.0);
}
#[test]
fn test_load_clusterer_same_line() {
let mut clusterer = LoadClusterer::new();
let mut instrs = vec![
make_mi_with_ops(2, Some(1), vec![MachineOperand::Imm(0)]),
make_mi_with_ops(2, Some(2), vec![MachineOperand::Imm(8)]),
make_mi_with_ops(2, Some(3), vec![MachineOperand::Imm(16)]),
];
clusterer.cluster_loads(&mut instrs);
assert_eq!(clusterer.loads_clustered, 3);
}
#[test]
fn test_store_clusterer_same_line() {
let mut clusterer = StoreClusterer::new();
let mut instrs = vec![
make_mi_with_ops(3, None, vec![MachineOperand::Imm(0)]),
make_mi_with_ops(3, None, vec![MachineOperand::Imm(8)]),
];
clusterer.cluster_stores(&mut instrs);
assert_eq!(clusterer.stores_clustered, 2);
}
#[test]
fn test_macro_fusion_add_jcc_skylake() {
let mut detector = MacroFusionDetector::new("skylake");
let instrs = vec![make_mi(2, Some(1)), make_mi(16, None)];
detector.detect_fusion_pairs(&instrs);
assert_eq!(detector.fused_uop_count(), 1);
}
#[test]
fn test_macro_fusion_no_fusion_no_branch() {
let mut detector = MacroFusionDetector::new("skylake");
let instrs = vec![make_mi(18, None), make_mi(1, Some(1))];
detector.detect_fusion_pairs(&instrs);
assert_eq!(detector.fused_uop_count(), 0);
}
#[test]
fn test_micro_fusion_store_detect() {
let mut detector = MicroFusionDetector::new();
let instrs = vec![make_mi_with_ops(
3,
None,
vec![MachineOperand::Reg(0), MachineOperand::Imm(0)],
)];
detector.detect_store_fusion(&instrs);
assert_eq!(detector.store_fusions.len(), 1);
}
#[test]
fn test_loop_aligner_nop_sequence_all_sizes() {
for n in 1..=9 {
let seq = LoopAligner::generate_nop_sequence(n);
assert_eq!(seq.len(), n as usize);
}
}
#[test]
fn test_copy_suppressor_propagate() {
let mut suppressor = CopySuppressor::new();
let mut instrs = vec![
make_mi_with_ops(1, Some(4), vec![MachineOperand::Reg(3)]),
make_mi_with_ops(2, None, vec![MachineOperand::Reg(4)]),
];
suppressor.suppress_copies(&mut instrs);
assert_eq!(instrs.len(), 1);
if let MachineOperand::Reg(r) = instrs[0].operands[0] {
assert_eq!(r, 3);
}
}
#[test]
fn test_subreg_liveness_record() {
let mut liveness = SubRegLiveness::new();
liveness.record_access(1, 3);
liveness.record_access(1, 7);
let (start, end) = liveness.live_ranges[&1];
assert_eq!(start, 3);
assert_eq!(end, 7);
}
#[test]
fn test_vliw_packet_formation_with_deps() {
let mut formation = VLIWPacketFormation::new(2);
let mut graph = DepGraph::new();
graph.nodes.push(SchedNode {
instr_idx: 0,
opcode: 1,
latency: 1,
critical_path: 0,
successors: vec![1],
predecessors: vec![],
scheduled: false,
unscheduled_preds: 0,
});
graph.nodes.push(SchedNode {
instr_idx: 1,
opcode: 1,
latency: 1,
critical_path: 0,
successors: vec![],
predecessors: vec![0],
scheduled: false,
unscheduled_preds: 1,
});
graph.node_count = 2;
formation.form_packets(&[0, 1], &graph);
assert_eq!(formation.packet_count(), 2);
}
}