use std::collections::{HashMap, VecDeque};
#[derive(Debug, Clone)]
pub struct MCInst {
pub mnemonic: String,
pub operands: Vec<u32>,
pub latency: u32,
pub ports: u32,
pub micro_ops: u32,
pub writes_result: bool,
pub dest_reg: Option<u32>,
pub source_regs: Vec<u32>,
}
#[derive(Debug, Clone)]
pub struct MCAnalysisResult {
pub total_cycles: u64,
pub instructions: usize,
pub ipc: f64,
pub dispatch_stalls: usize,
pub issue_stalls: usize,
pub resource_pressure: HashMap<String, f64>,
pub timeline: Vec<TimelineEvent>,
}
#[derive(Debug, Clone)]
pub struct TimelineEvent {
pub cycle: u64,
pub instruction: usize,
pub event: String,
}
pub struct LLVMMCA {
pub source: String,
pub iterations: usize,
}
impl LLVMMCA {
pub fn new(source: &str) -> Self {
Self {
source: source.to_string(),
iterations: 100,
}
}
pub fn set_iterations(&mut self, n: usize) {
self.iterations = n;
}
pub fn run(&mut self) -> MCAnalysisResult {
let instructions = self.parse_source();
self.simulate_pipeline(&instructions)
}
fn parse_source(&self) -> Vec<MCInst> {
let mut instructions = Vec::new();
let mut reg_map: HashMap<String, u32> = HashMap::new();
let mut next_reg = 0u32;
for line in self.source.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') || line.starts_with(';') {
continue;
}
let parts: Vec<&str> = line.splitn(2, |c: char| c == ' ' || c == '\t').collect();
if parts.is_empty() {
continue;
}
let mnemonic = parts[0].trim().to_lowercase();
let operands_str = if parts.len() > 1 { parts[1].trim() } else { "" };
let mut operands = Vec::new();
let mut dest_reg: Option<u32> = None;
let mut source_regs = Vec::new();
for (i, op_str) in operands_str.split(',').enumerate() {
let op = op_str.trim();
if op.is_empty() {
continue;
}
let reg_id = *reg_map.entry(op.to_string()).or_insert_with(|| {
let id = next_reg;
next_reg += 1;
id
});
operands.push(reg_id);
if i == 0 {
dest_reg = Some(reg_id);
} else {
source_regs.push(reg_id);
}
}
let (latency, ports, micro_ops, writes_result) = self.get_instruction_info(&mnemonic);
instructions.push(MCInst {
mnemonic,
operands,
latency,
ports,
micro_ops,
writes_result,
dest_reg,
source_regs,
});
}
instructions
}
fn get_instruction_info(&self, mnemonic: &str) -> (u32, u32, u32, bool) {
match mnemonic {
"add" | "sub" | "and" | "or" | "xor" | "shl" | "shr" | "mov" => (1, 0xFF, 1, true),
"mul" | "imul" => (3, 0x01, 1, true),
"div" | "idiv" => (20, 0x01, 1, true),
"fadd" | "addss" | "addsd" => (4, 0x10, 1, true),
"fmul" | "mulss" | "mulsd" => (4, 0x10, 1, true),
"fdiv" | "divss" | "divsd" => (14, 0x10, 1, true),
"ldr" | "load" | "movload" | "ld" => (5, 0x20, 1, true),
"str" | "store" | "st" => (1, 0x40, 1, false),
"br" | "b" | "jmp" | "j" | "je" | "jne" | "jg" | "jl" | "call" | "ret" => {
(1, 0x80, 1, false)
}
"cmp" | "test" => (1, 0xFF, 1, false),
_ => (1, 0xFF, 1, true),
}
}
fn simulate_pipeline(&self, instructions: &[MCInst]) -> MCAnalysisResult {
let n = instructions.len();
if n == 0 {
return MCAnalysisResult {
total_cycles: 0,
instructions: 0,
ipc: 0.0,
dispatch_stalls: 0,
issue_stalls: 0,
resource_pressure: HashMap::new(),
timeline: Vec::new(),
};
}
let dispatch_width = 4;
let issue_width = 4;
let retire_width = 4;
let rob_size = 224;
let mut timeline: Vec<TimelineEvent> = Vec::new();
let mut cycle: u64 = 0;
let mut dispatch_idx: usize = 0; let mut rob: VecDeque<InFlight> = VecDeque::new();
let mut ready_queue: Vec<usize> = Vec::new(); let mut retire_idx: usize = 0;
let mut reg_ready: HashMap<u32, u64> = HashMap::new();
let mut dispatch_stalls = 0usize;
let mut issue_stalls = 0usize;
let mut port_usage: HashMap<String, u64> = HashMap::new();
port_usage.insert("Port0".into(), 0);
port_usage.insert("Port1".into(), 0);
port_usage.insert("Port2".into(), 0);
port_usage.insert("Port3".into(), 0);
port_usage.insert("Port4".into(), 0);
port_usage.insert("Port5".into(), 0);
port_usage.insert("Port6".into(), 0);
port_usage.insert("Port7".into(), 0);
let max_cycles = (n * 10) as u64 + 1000; let mut total_retired = 0usize;
while retire_idx < n && cycle < max_cycles {
let mut retired_this_cycle = 0usize;
while retired_this_cycle < retire_width && retire_idx < n {
if let Some(front) = rob.front() {
if front.state == InFlightState::Executed && front.ready_cycle <= cycle {
let inflight = rob.pop_front().unwrap();
timeline.push(TimelineEvent {
cycle,
instruction: inflight.instr_idx,
event: "Retired".into(),
});
if let Some(dest_reg) = instructions[inflight.instr_idx].dest_reg {
reg_ready.insert(
dest_reg,
cycle + 1, );
}
retire_idx += 1;
total_retired += 1;
retired_this_cycle += 1;
} else {
break;
}
} else {
break;
}
}
let mut issued_this_cycle = 0usize;
{
let mut to_issue: Vec<usize> = Vec::new();
for (rob_idx, inflight) in rob.iter().enumerate() {
if issued_this_cycle >= issue_width {
break;
}
if inflight.state != InFlightState::Dispatched {
continue;
}
let inst = &instructions[inflight.instr_idx];
let all_ready = inst.source_regs.iter().all(|reg| {
reg_ready
.get(reg)
.map_or(true, |&ready_cycle| ready_cycle <= cycle)
});
if all_ready {
to_issue.push(rob_idx);
issued_this_cycle += 1;
}
}
for &rob_idx in &to_issue {
if let Some(inflight) = rob.get_mut(rob_idx) {
let inst_idx = inflight.instr_idx;
let inst = &instructions[inst_idx];
let execute_cycles = inst.latency as u64;
inflight.state = InFlightState::Executing;
inflight.ready_cycle = cycle + execute_cycles;
inflight.issue_cycle = cycle;
timeline.push(TimelineEvent {
cycle,
instruction: inst_idx,
event: "Issued".into(),
});
if inst.ports & 0x01 != 0 {
*port_usage.entry("Port0".into()).or_insert(0) += 1;
}
if inst.ports & 0x02 != 0 {
*port_usage.entry("Port1".into()).or_insert(0) += 1;
}
if inst.ports & 0x04 != 0 {
*port_usage.entry("Port2".into()).or_insert(0) += 1;
}
if inst.ports & 0x08 != 0 {
*port_usage.entry("Port3".into()).or_insert(0) += 1;
}
if inst.ports & 0x10 != 0 {
*port_usage.entry("Port4".into()).or_insert(0) += 1;
}
if inst.ports & 0x20 != 0 {
*port_usage.entry("Port5".into()).or_insert(0) += 1;
}
if inst.ports & 0x40 != 0 {
*port_usage.entry("Port6".into()).or_insert(0) += 1;
}
if inst.ports & 0x80 != 0 {
*port_usage.entry("Port7".into()).or_insert(0) += 1;
}
}
}
}
if issued_this_cycle == 0 && !rob.is_empty() {
let has_dispatched = rob.iter().any(|i| i.state == InFlightState::Dispatched);
if has_dispatched {
issue_stalls += 1;
}
}
for inflight in rob.iter_mut() {
if inflight.state == InFlightState::Executing && inflight.ready_cycle <= cycle {
inflight.state = InFlightState::Executed;
timeline.push(TimelineEvent {
cycle,
instruction: inflight.instr_idx,
event: "Executed".into(),
});
}
}
let mut dispatched_this_cycle = 0usize;
while dispatched_this_cycle < dispatch_width && dispatch_idx < n && rob.len() < rob_size
{
let inst = &instructions[dispatch_idx];
if rob.len() + inst.micro_ops as usize <= rob_size {
rob.push_back(InFlight {
instr_idx: dispatch_idx,
state: InFlightState::Dispatched,
ready_cycle: 0,
issue_cycle: 0,
});
timeline.push(TimelineEvent {
cycle,
instruction: dispatch_idx,
event: "Dispatched".into(),
});
dispatch_idx += 1;
dispatched_this_cycle += 1;
} else {
dispatch_stalls += 1;
break;
}
}
cycle += 1;
}
let resource_pressure = self.compute_resource_pressure(&timeline);
let _critical_path = self.compute_critical_path(instructions);
let ipc = if cycle > 0 {
total_retired as f64 / cycle as f64
} else {
0.0
};
MCAnalysisResult {
total_cycles: cycle,
instructions: n,
ipc,
dispatch_stalls,
issue_stalls,
resource_pressure,
timeline,
}
}
fn compute_critical_path(&self, instructions: &[MCInst]) -> u64 {
let n = instructions.len();
if n == 0 {
return 0;
}
let mut completion_time: Vec<u64> = vec![0u64; n];
let mut reg_last_writer: HashMap<u32, u64> = HashMap::new();
for (i, inst) in instructions.iter().enumerate() {
let mut earliest_start = 0u64;
for src_reg in &inst.source_regs {
if let Some(&producer_time) = reg_last_writer.get(src_reg) {
earliest_start = earliest_start.max(producer_time);
}
}
let complete_at = earliest_start + inst.latency as u64;
completion_time[i] = complete_at;
if let Some(dest_reg) = inst.dest_reg {
reg_last_writer.insert(dest_reg, complete_at);
}
}
completion_time.iter().max().copied().unwrap_or(0)
}
fn compute_resource_pressure(&self, timeline: &[TimelineEvent]) -> HashMap<String, f64> {
let mut pressure: HashMap<String, f64> = HashMap::new();
let mut port_usage: HashMap<String, u64> = HashMap::new();
let mut total_cycles: u64 = 0;
for event in timeline {
total_cycles = total_cycles.max(event.cycle);
if event.event == "Issued" {
*port_usage.entry("Port0".into()).or_insert(0) += 1;
}
}
if total_cycles > 0 {
for (port, count) in &port_usage {
let fraction = *count as f64 / total_cycles as f64;
pressure.insert(port.clone(), fraction);
}
}
for port in &[
"Port0", "Port1", "Port2", "Port3", "Port4", "Port5", "Port6", "Port7",
] {
pressure.entry(port.to_string()).or_insert(0.0);
}
pressure
}
pub fn print_report(&self, result: &MCAnalysisResult) -> String {
let mut report = String::new();
report.push_str("=== LLVM MCA Analysis Report ===\n\n");
report.push_str(&format!("Total Instructions: {}\n", result.instructions));
report.push_str(&format!("Total Cycles: {}\n", result.total_cycles));
report.push_str(&format!("IPC: {:.4}\n", result.ipc));
report.push_str(&format!("Dispatch Stalls: {}\n", result.dispatch_stalls));
report.push_str(&format!("Issue Stalls: {}\n", result.issue_stalls));
report.push_str("\n--- Resource Pressure ---\n");
for (port, pressure) in &result.resource_pressure {
report.push_str(&format!(" {}: {:.2}%\n", port, pressure * 100.0));
}
report.push_str("\n--- Timeline (first 20 events) ---\n");
for (i, event) in result.timeline.iter().take(20).enumerate() {
report.push_str(&format!(
" [{}] Cycle {}: Inst {} {}\n",
i, event.cycle, event.instruction, event.event
));
}
if result.timeline.len() > 20 {
report.push_str(&format!(
" ... and {} more events\n",
result.timeline.len() - 20
));
}
report
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum InFlightState {
Dispatched,
Executing,
Executed,
}
#[derive(Debug, Clone)]
struct InFlight {
instr_idx: usize,
state: InFlightState,
ready_cycle: u64,
issue_cycle: u64,
}
#[derive(Debug, Clone)]
pub struct PipelineConfig {
pub dispatch_width: usize,
pub issue_width: usize,
pub retire_width: usize,
pub reorder_buffer_size: usize,
pub load_queue_size: usize,
pub store_queue_size: usize,
pub register_file_size: usize,
pub reservation_station_size: usize,
}
impl Default for PipelineConfig {
fn default() -> Self {
Self {
dispatch_width: 4,
issue_width: 4,
retire_width: 4,
reorder_buffer_size: 224,
load_queue_size: 72,
store_queue_size: 56,
register_file_size: 180,
reservation_station_size: 97,
}
}
}
impl PipelineConfig {
pub fn skylake() -> Self {
Self::default()
}
pub fn zen4() -> Self {
Self {
dispatch_width: 6,
issue_width: 6,
retire_width: 8,
reorder_buffer_size: 320,
load_queue_size: 116,
store_queue_size: 64,
register_file_size: 224,
reservation_station_size: 128,
}
}
pub fn apple_m1() -> Self {
Self {
dispatch_width: 8,
issue_width: 8,
retire_width: 8,
reorder_buffer_size: 630,
load_queue_size: 128,
store_queue_size: 96,
register_file_size: 384,
reservation_station_size: 192,
}
}
pub fn in_order() -> Self {
Self {
dispatch_width: 1,
issue_width: 1,
retire_width: 1,
reorder_buffer_size: 4,
load_queue_size: 4,
store_queue_size: 4,
register_file_size: 32,
reservation_station_size: 0,
}
}
}
pub struct FetchStage {
fetch_ptr: usize,
fetch_width: usize,
total_fetched: usize,
fetch_stalls: usize,
}
impl FetchStage {
pub fn new(fetch_width: usize) -> Self {
Self {
fetch_ptr: 0,
fetch_width,
total_fetched: 0,
fetch_stalls: 0,
}
}
pub fn fetch(
&mut self,
total_insts: usize,
queue_capacity: usize,
queue_len: usize,
) -> Vec<usize> {
let available = queue_capacity.saturating_sub(queue_len);
let can_fetch = self.fetch_width.min(available);
if can_fetch == 0 {
self.fetch_stalls += 1;
return vec![];
}
let end = (self.fetch_ptr + can_fetch).min(total_insts);
let fetched: Vec<usize> = (self.fetch_ptr..end).collect();
self.fetch_ptr = end;
self.total_fetched += fetched.len();
fetched
}
pub fn reset(&mut self) {
self.fetch_ptr = 0;
self.total_fetched = 0;
self.fetch_stalls = 0;
}
pub fn fetch_stalls(&self) -> usize {
self.fetch_stalls
}
pub fn total_fetched(&self) -> usize {
self.total_fetched
}
}
pub struct DecodeStage {
decode_width: usize,
total_decoded: usize,
decode_stalls: usize,
}
impl DecodeStage {
pub fn new(decode_width: usize) -> Self {
Self {
decode_width,
total_decoded: 0,
decode_stalls: 0,
}
}
pub fn decode(&mut self, inst_indices: &[usize], insts: &[MCInst]) -> usize {
let mut uops = 0usize;
for &idx in inst_indices.iter().take(self.decode_width) {
if idx < insts.len() {
uops += insts[idx].micro_ops as usize;
}
}
if uops == 0 && !inst_indices.is_empty() {
self.decode_stalls += 1;
}
self.total_decoded += uops;
uops
}
pub fn reset(&mut self) {
self.total_decoded = 0;
self.decode_stalls = 0;
}
pub fn decode_stalls(&self) -> usize {
self.decode_stalls
}
}
pub struct DispatchStage {
dispatch_width: usize,
total_dispatched: usize,
dispatch_stalls: usize,
}
impl DispatchStage {
pub fn new(dispatch_width: usize) -> Self {
Self {
dispatch_width,
total_dispatched: 0,
dispatch_stalls: 0,
}
}
pub fn dispatch(
&mut self,
inst_indices: &[usize],
rob_used: usize,
rob_capacity: usize,
) -> usize {
let available = rob_capacity.saturating_sub(rob_used);
let can = self.dispatch_width.min(available).min(inst_indices.len());
if can == 0 && !inst_indices.is_empty() {
self.dispatch_stalls += 1;
}
self.total_dispatched += can;
can
}
pub fn reset(&mut self) {
self.total_dispatched = 0;
self.dispatch_stalls = 0;
}
pub fn dispatch_stalls(&self) -> usize {
self.dispatch_stalls
}
pub fn total_dispatched(&self) -> usize {
self.total_dispatched
}
}
pub struct ExecuteStage {
total_exec_cycles: u64,
total_executed: usize,
}
impl ExecuteStage {
pub fn new() -> Self {
Self {
total_exec_cycles: 0,
total_executed: 0,
}
}
pub fn tick(&mut self, inflight: &mut [InFlight], cycle: u64, _insts: &[MCInst]) -> usize {
let mut executed = 0usize;
for entry in inflight.iter_mut() {
if entry.state == InFlightState::Executing && entry.ready_cycle <= cycle {
entry.state = InFlightState::Executed;
executed += 1;
}
}
self.total_executed += executed;
self.total_exec_cycles += 1;
executed
}
pub fn reset(&mut self) {
self.total_exec_cycles = 0;
self.total_executed = 0;
}
}
pub struct RetireStage {
retire_width: usize,
total_retired: usize,
retire_stalls: usize,
}
impl RetireStage {
pub fn new(retire_width: usize) -> Self {
Self {
retire_width,
total_retired: 0,
retire_stalls: 0,
}
}
pub fn retire(&mut self, rob: &mut VecDeque<InFlight>, cycle: u64) -> usize {
let mut retired = 0usize;
while retired < self.retire_width {
match rob.front() {
Some(e) if e.state == InFlightState::Executed && e.ready_cycle <= cycle => {
rob.pop_front();
retired += 1;
}
_ => break,
}
}
if retired == 0 && !rob.is_empty() {
self.retire_stalls += 1;
}
self.total_retired += retired;
retired
}
pub fn reset(&mut self) {
self.total_retired = 0;
self.retire_stalls = 0;
}
pub fn retire_stalls(&self) -> usize {
self.retire_stalls
}
pub fn total_retired(&self) -> usize {
self.total_retired
}
}
pub struct Scheduler {
issue_width: usize,
reservation_station: Vec<Option<usize>>,
resource_manager: ResourceManager,
issue_stalls: usize,
total_issued: usize,
}
impl Scheduler {
pub fn new(issue_width: usize, rs_size: usize) -> Self {
Self {
issue_width,
reservation_station: vec![None; rs_size],
resource_manager: ResourceManager::new(),
issue_stalls: 0,
total_issued: 0,
}
}
pub fn insert_into_rs(&mut self, instr_idx: usize) -> bool {
for slot in self.reservation_station.iter_mut() {
if slot.is_none() {
*slot = Some(instr_idx);
return true;
}
}
false
}
pub fn remove_from_rs(&mut self, instr_idx: usize) {
for slot in self.reservation_station.iter_mut() {
if *slot == Some(instr_idx) {
*slot = None;
return;
}
}
}
pub fn issue(
&mut self,
insts: &[MCInst],
reg_ready: &HashMap<u32, u64>,
cycle: u64,
) -> Vec<usize> {
let mut issued = Vec::new();
for slot in self.reservation_station.iter() {
if issued.len() >= self.issue_width {
break;
}
if let Some(idx) = slot {
let inst = &insts[*idx];
let all_ready = inst
.source_regs
.iter()
.all(|reg| reg_ready.get(reg).map_or(true, |&t| t <= cycle));
if all_ready && self.resource_manager.can_issue(inst, cycle) {
self.resource_manager.reserve(inst, cycle);
issued.push(*idx);
}
}
}
if issued.is_empty() {
let has_any = self.reservation_station.iter().any(|s| s.is_some());
if has_any {
self.issue_stalls += 1;
}
}
self.total_issued += issued.len();
issued
}
pub fn rs_used(&self) -> usize {
self.reservation_station
.iter()
.filter(|s| s.is_some())
.count()
}
pub fn rs_capacity(&self) -> usize {
self.reservation_station.len()
}
pub fn issue_stalls(&self) -> usize {
self.issue_stalls
}
pub fn total_issued(&self) -> usize {
self.total_issued
}
pub fn reset(&mut self) {
for slot in self.reservation_station.iter_mut() {
*slot = None;
}
self.resource_manager.reset();
self.issue_stalls = 0;
self.total_issued = 0;
}
}
#[derive(Debug, Clone)]
pub struct HardwareUnit {
pub name: String,
pub count: usize,
pub pipelined: bool,
pub latency: u32,
}
impl HardwareUnit {
pub fn new(name: &str, count: usize, pipelined: bool, latency: u32) -> Self {
Self {
name: name.to_string(),
count,
pipelined,
latency,
}
}
}
#[derive(Debug, Clone)]
pub struct ResourceState {
pub unit: HardwareUnit,
busy_until: Vec<u64>,
}
impl ResourceState {
pub fn new(unit: HardwareUnit) -> Self {
let busy_until = vec![0u64; unit.count];
Self { unit, busy_until }
}
pub fn is_available(&self, cycle: u64) -> bool {
self.busy_until.iter().any(|&t| t <= cycle)
}
pub fn reserve(&mut self, cycle: u64) -> bool {
for slot in self.busy_until.iter_mut() {
if *slot <= cycle {
*slot = cycle + self.unit.latency as u64;
return true;
}
}
false
}
pub fn next_available(&self) -> u64 {
self.busy_until.iter().min().copied().unwrap_or(0)
}
pub fn reset(&mut self) {
for slot in self.busy_until.iter_mut() {
*slot = 0;
}
}
}
pub struct ResourceManager {
resources: HashMap<String, ResourceState>,
port_map: HashMap<u32, Vec<String>>,
}
impl ResourceManager {
pub fn new() -> Self {
let mut rm = Self {
resources: HashMap::new(),
port_map: HashMap::new(),
};
for (name, latency) in &[
("Port0", 1),
("Port1", 1),
("Port2", 1),
("Port3", 1),
("Port4", 1),
("Port5", 1),
("Port6", 1),
("Port7", 1),
("FPU0", 4),
("FPU1", 4),
("AGU0", 5),
("AGU1", 5),
("BranchUnit", 1),
("Divider", 20),
] {
rm.resources.insert(
name.to_string(),
ResourceState::new(HardwareUnit::new(name, 1, true, *latency)),
);
}
for (mask, ports) in &[
(0x01u32, vec!["Port0"]),
(0x02, vec!["Port1"]),
(0x04, vec!["Port2"]),
(0x08, vec!["Port3"]),
(0x10, vec!["Port4"]),
(0x20, vec!["Port5"]),
(0x40, vec!["Port6"]),
(0x80, vec!["Port7"]),
] {
rm.port_map
.insert(*mask, ports.iter().map(|s| s.to_string()).collect());
}
rm
}
pub fn can_issue(&self, inst: &MCInst, cycle: u64) -> bool {
for bit in 0..8 {
let mask = 1u32 << bit;
if inst.ports & mask != 0 {
if let Some(names) = self.port_map.get(&mask) {
for name in names {
if let Some(rs) = self.resources.get(name) {
if !rs.is_available(cycle) {
return false;
}
}
}
}
}
}
true
}
pub fn reserve(&mut self, inst: &MCInst, cycle: u64) -> bool {
if !self.can_issue(inst, cycle) {
return false;
}
for bit in 0..8 {
let mask = 1u32 << bit;
if inst.ports & mask != 0 {
if let Some(names) = self.port_map.get(&mask) {
for name in names {
if let Some(rs) = self.resources.get_mut(name) {
rs.reserve(cycle);
}
}
}
}
}
true
}
pub fn utilization(&self, total_cycles: u64) -> HashMap<String, f64> {
let mut util = HashMap::new();
let tc = total_cycles.max(1);
for (name, rs) in &self.resources {
let total_busy: u64 = rs.busy_until.iter().sum();
let max_cap = rs.unit.count as u64 * tc;
util.insert(
name.clone(),
if max_cap > 0 {
total_busy as f64 / max_cap as f64
} else {
0.0
},
);
}
util
}
pub fn reset(&mut self) {
for rs in self.resources.values_mut() {
rs.reset();
}
}
}
#[derive(Debug, Clone)]
pub struct DetailedEvent {
pub cycle: u64,
pub inst_idx: usize,
pub mnemonic: String,
pub event_type: EventType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventType {
Fetched,
Decoded,
Dispatched,
Issued,
Executing,
Executed,
Retired,
StalledDispatch,
StalledIssue,
StalledRetire,
}
#[derive(Debug, Clone)]
pub struct Timeline {
events: Vec<DetailedEvent>,
total_cycles: u64,
}
impl Timeline {
pub fn new() -> Self {
Self {
events: Vec::new(),
total_cycles: 0,
}
}
pub fn record(&mut self, cycle: u64, inst_idx: usize, mnemonic: &str, event: EventType) {
self.events.push(DetailedEvent {
cycle,
inst_idx,
mnemonic: mnemonic.to_string(),
event_type: event,
});
self.total_cycles = self.total_cycles.max(cycle + 1);
}
pub fn total_cycles(&self) -> u64 {
self.total_cycles
}
pub fn event_count(&self) -> usize {
self.events.len()
}
pub fn events_at(&self, cycle: u64) -> Vec<&DetailedEvent> {
self.events.iter().filter(|e| e.cycle == cycle).collect()
}
pub fn count_by_type(&self, et: EventType) -> usize {
self.events.iter().filter(|e| e.event_type == et).count()
}
}
#[derive(Debug, Clone)]
pub struct TimelineView {
pub total_cycles: u64,
pub dispatched: usize,
pub issued: usize,
pub executed: usize,
pub retired: usize,
pub dispatch_stalls: usize,
pub issue_stalls: usize,
pub retire_stalls: usize,
pub ipc: f64,
}
impl TimelineView {
pub fn from_timeline(tl: &Timeline) -> Self {
let retired = tl.count_by_type(EventType::Retired);
Self {
total_cycles: tl.total_cycles,
dispatched: tl.count_by_type(EventType::Dispatched),
issued: tl.count_by_type(EventType::Issued),
executed: tl.count_by_type(EventType::Executed),
retired,
dispatch_stalls: tl.count_by_type(EventType::StalledDispatch),
issue_stalls: tl.count_by_type(EventType::StalledIssue),
retire_stalls: tl.count_by_type(EventType::StalledRetire),
ipc: if tl.total_cycles > 0 {
retired as f64 / tl.total_cycles as f64
} else {
0.0
},
}
}
}
#[derive(Debug, Clone)]
pub struct Bottleneck {
pub description: String,
pub severity: f64,
pub bottleneck_type: BottleneckType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BottleneckType {
DispatchWidth,
ResourceContention,
DataDependency,
StructuralHazard,
FrontEnd,
BackEnd,
Retirement,
}
pub fn analyze_bottlenecks(
view: &TimelineView,
resource_pressure: &HashMap<String, f64>,
config: &PipelineConfig,
) -> Vec<Bottleneck> {
let mut bl = Vec::new();
if view.total_cycles > 0 {
let eff =
view.dispatched as f64 / (view.total_cycles as f64 * config.dispatch_width as f64);
if eff < 0.5 {
bl.push(Bottleneck {
description: format!("Low dispatch utilization ({:.1}%)", eff * 100.0),
severity: 1.0 - eff,
bottleneck_type: BottleneckType::DispatchWidth,
});
}
}
for (res, pressure) in resource_pressure {
if *pressure > 0.8 {
bl.push(Bottleneck {
description: format!("High pressure on {}: {:.1}%", res, pressure * 100.0),
severity: *pressure,
bottleneck_type: BottleneckType::ResourceContention,
});
}
}
let total_stalls = (view.dispatch_stalls + view.issue_stalls + view.retire_stalls).max(1);
if view.issue_stalls > view.dispatch_stalls + view.retire_stalls {
bl.push(Bottleneck {
description: "Back-end bound".into(),
severity: view.issue_stalls as f64 / total_stalls as f64,
bottleneck_type: BottleneckType::BackEnd,
});
}
if view.retire_stalls > view.dispatch_stalls && view.retire_stalls > view.issue_stalls {
bl.push(Bottleneck {
description: "Retirement bound".into(),
severity: view.retire_stalls as f64 / total_stalls as f64,
bottleneck_type: BottleneckType::Retirement,
});
}
bl
}
pub fn print_summary(result: &MCAnalysisResult) -> String {
use std::fmt::Write;
let mut s = String::new();
let _ = writeln!(s, "╔══════════════════════════════════════╗");
let _ = writeln!(s, "║ LLVM MCA Performance Summary ║");
let _ = writeln!(s, "╠══════════════════════════════════════╣");
let _ = writeln!(
s,
"║ Instructions: {:>8} ║",
result.instructions
);
let _ = writeln!(
s,
"║ Total Cycles: {:>8} ║",
result.total_cycles
);
let _ = writeln!(s, "║ IPC: {:>8.3} ║", result.ipc);
let _ = writeln!(
s,
"║ Dispatch Stalls: {:>8} ║",
result.dispatch_stalls
);
let _ = writeln!(
s,
"║ Issue Stalls: {:>8} ║",
result.issue_stalls
);
let _ = writeln!(s, "╚══════════════════════════════════════╝");
s
}
pub fn print_timeline(result: &MCAnalysisResult, max_events: usize) -> String {
let mut s = String::from("--- Timeline ---\n");
for (i, e) in result.timeline.iter().take(max_events).enumerate() {
s.push_str(&format!(
" {:>3}: Cycle {:>5} | Inst {:>5} | {}\n",
i, e.cycle, e.instruction, e.event
));
}
if result.timeline.len() > max_events {
s.push_str(&format!(
" ... and {} more\n",
result.timeline.len() - max_events
));
}
s
}
pub fn print_resource_pressure(result: &MCAnalysisResult) -> String {
let mut s = String::from("--- Resource Pressure ---\n");
let mut ports: Vec<(&String, &f64)> = result.resource_pressure.iter().collect();
ports.sort_by(|a, b| b.1.partial_cmp(a.1).unwrap_or(std::cmp::Ordering::Equal));
for (port, p) in &ports {
let bar = "█".repeat(((*p * 50.0) as usize).min(50));
s.push_str(&format!(" {:>10}: {:>6.1}% |{}\n", port, *p * 100.0, bar));
}
s
}
pub fn print_instruction_info(insts: &[MCInst]) -> String {
let mut s = String::from("--- Instruction Info ---\n");
for (i, inst) in insts.iter().enumerate() {
let port_str = if inst.ports == 0xFF {
"ALL".into()
} else {
(0..8)
.filter(|b| inst.ports & (1 << b) != 0)
.map(|b| format!("P{}", b))
.collect::<Vec<_>>()
.join(",")
};
s.push_str(&format!(
" {:>3}: {:>8} | Lat: {:>3} | uOps: {:>2} | Ports: {}\n",
i, inst.mnemonic, inst.latency, inst.micro_ops, port_str
));
}
s
}
#[derive(Debug, Clone)]
pub struct MCADiagnostic {
pub level: DiagnosticLevel,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DiagnosticLevel {
Info,
Warning,
Error,
}
pub fn run_diagnostics(result: &MCAnalysisResult, config: &PipelineConfig) -> Vec<MCADiagnostic> {
let mut diags = Vec::new();
for (port, p) in &result.resource_pressure {
if *p > 0.9 {
diags.push(MCADiagnostic {
level: DiagnosticLevel::Warning,
message: format!("Resource {} over-subscribed ({:.1}%)", port, p * 100.0),
});
}
}
if result.instructions > 0 && result.dispatch_stalls > result.instructions / 2 {
diags.push(MCADiagnostic {
level: DiagnosticLevel::Warning,
message: format!(
"High dispatch stall rate: {} stalls for {} insts",
result.dispatch_stalls, result.instructions
),
});
}
if result.instructions > 10 && result.ipc < 0.5 {
diags.push(MCADiagnostic {
level: DiagnosticLevel::Warning,
message: format!("Very low IPC ({:.3})", result.ipc),
});
}
diags
}
impl LLVMMCA {
pub fn run_with_config(&self, config: &PipelineConfig) -> MCAnalysisResult {
let instructions = self.parse_source();
self.simulate_full_pipeline(&instructions, config)
}
fn simulate_full_pipeline(
&self,
insts: &[MCInst],
config: &PipelineConfig,
) -> MCAnalysisResult {
let n = insts.len();
if n == 0 {
return MCAnalysisResult {
total_cycles: 0,
instructions: 0,
ipc: 0.0,
dispatch_stalls: 0,
issue_stalls: 0,
resource_pressure: HashMap::new(),
timeline: vec![],
};
}
let mut fetch = FetchStage::new(config.dispatch_width);
let mut decode = DecodeStage::new(config.dispatch_width);
let mut dispatch = DispatchStage::new(config.dispatch_width);
let mut execute = ExecuteStage::new();
let mut retire = RetireStage::new(config.retire_width);
let mut scheduler = Scheduler::new(config.issue_width, config.reservation_station_size);
let mut rob: VecDeque<InFlight> = VecDeque::new();
let mut reg_ready: HashMap<u32, u64> = HashMap::new();
let mut timeline = Timeline::new();
let mut cycle: u64 = 0;
let max_cycles = (n * 20) as u64 + 5000;
let mut decoded_queue: VecDeque<usize> = VecDeque::new();
while (retire.total_retired() < n || !rob.is_empty()) && cycle < max_cycles {
let retired = retire.retire(&mut rob, cycle);
for _ in 0..retired {
timeline.record(cycle, 0, "", EventType::Retired);
}
let mut rob_vec: Vec<InFlight> = rob.iter().cloned().collect();
execute.tick(&mut rob_vec, cycle, insts);
for (i, entry) in rob_vec.iter().enumerate() {
if let Some(re) = rob.get_mut(i) {
if re.state != entry.state {
re.state = entry.state;
if entry.state == InFlightState::Executed {
timeline.record(
cycle,
entry.instr_idx,
&insts[entry.instr_idx].mnemonic,
EventType::Executed,
);
}
}
}
}
let issued = scheduler.issue(insts, ®_ready, cycle);
for &idx in &issued {
let inst = &insts[idx];
for re in rob.iter_mut() {
if re.instr_idx == idx && re.state == InFlightState::Dispatched {
re.state = InFlightState::Executing;
re.ready_cycle = cycle + inst.latency as u64;
re.issue_cycle = cycle;
break;
}
}
scheduler.remove_from_rs(idx);
timeline.record(cycle, idx, &inst.mnemonic, EventType::Issued);
}
while !decoded_queue.is_empty() && rob.len() < config.reorder_buffer_size {
let idx = decoded_queue.pop_front().unwrap();
if scheduler.insert_into_rs(idx) {
rob.push_back(InFlight {
instr_idx: idx,
state: InFlightState::Dispatched,
ready_cycle: 0,
issue_cycle: 0,
});
timeline.record(cycle, idx, &insts[idx].mnemonic, EventType::Dispatched);
} else {
decoded_queue.push_front(idx);
timeline.record(cycle, 0, "", EventType::StalledDispatch);
break;
}
}
let fetched = fetch.fetch(n, decoded_queue.capacity(), decoded_queue.len());
for &idx in &fetched {
timeline.record(cycle, idx, &insts[idx].mnemonic, EventType::Fetched);
}
decode.decode(&fetched, insts);
for &idx in &fetched {
decoded_queue.push_back(idx);
}
cycle += 1;
}
let resource_pressure = scheduler.resource_manager.utilization(cycle);
let mca_timeline: Vec<TimelineEvent> = timeline
.events
.iter()
.map(|e| TimelineEvent {
cycle: e.cycle,
instruction: e.inst_idx,
event: format!("{:?}", e.event_type),
})
.collect();
MCAnalysisResult {
total_cycles: cycle,
instructions: n,
ipc: if cycle > 0 {
retire.total_retired() as f64 / cycle as f64
} else {
0.0
},
dispatch_stalls: dispatch.dispatch_stalls() + fetch.fetch_stalls(),
issue_stalls: scheduler.issue_stalls(),
resource_pressure,
timeline: mca_timeline,
}
}
}
#[derive(Debug, Clone)]
pub struct IterationInfo {
pub insts_per_iter: usize,
pub cycles_per_iter: u64,
pub total_cycles: u64,
pub iterations: usize,
}
impl LLVMMCA {
pub fn run_loop_analysis(&mut self) -> (MCAnalysisResult, IterationInfo) {
let insts = self.parse_source();
let n = insts.len();
let iters = self.iterations;
let mut all_insts: Vec<MCInst> = Vec::with_capacity(n * iters);
for _ in 0..iters {
all_insts.extend(insts.clone());
}
let result = self.simulate_pipeline(&all_insts);
let ii = IterationInfo {
insts_per_iter: n,
cycles_per_iter: if iters > 0 {
result.total_cycles / iters as u64
} else {
0
},
total_cycles: result.total_cycles,
iterations: iters,
};
(result, ii)
}
pub fn loop_throughput(&self, ii: &IterationInfo) -> f64 {
if ii.cycles_per_iter > 0 {
1000.0 / ii.cycles_per_iter as f64
} else {
0.0
}
}
}
#[derive(Debug, Clone)]
pub struct CriticalPathInfo {
pub critical_insts: Vec<usize>,
pub path_length: u64,
pub latency_breakdown: HashMap<String, u64>,
}
impl LLVMMCA {
pub fn analyze_critical_path(&self, insts: &[MCInst]) -> CriticalPathInfo {
let n = insts.len();
let mut completion: Vec<u64> = vec![0; n];
let mut predecessor: Vec<Option<usize>> = vec![None; n];
let mut reg_last: HashMap<u32, (usize, u64)> = HashMap::new();
let mut breakdown: HashMap<String, u64> = HashMap::new();
for (i, inst) in insts.iter().enumerate() {
let mut earliest = 0u64;
let mut pred = None;
for src in &inst.source_regs {
if let Some(&(pi, pt)) = reg_last.get(src) {
if pt >= earliest {
earliest = pt;
pred = Some(pi);
}
}
}
let done = earliest + inst.latency as u64;
completion[i] = done;
predecessor[i] = pred;
*breakdown.entry(inst.mnemonic.clone()).or_insert(0) += inst.latency as u64;
if let Some(dest) = inst.dest_reg {
reg_last.insert(dest, (i, done));
}
}
let mut max_i = 0;
let mut max_t = 0u64;
for (i, &t) in completion.iter().enumerate() {
if t > max_t {
max_t = t;
max_i = i;
}
}
let mut critical = Vec::new();
let mut cur = Some(max_i);
while let Some(idx) = cur {
critical.push(idx);
cur = predecessor[idx];
}
critical.reverse();
CriticalPathInfo {
critical_insts: critical,
path_length: max_t,
latency_breakdown: breakdown,
}
}
}
#[derive(Debug, Clone)]
pub struct DispatchGroup {
pub cycle: u64,
pub inst_indices: Vec<usize>,
}
pub fn analyze_dispatch_groups(timeline: &[TimelineEvent]) -> Vec<DispatchGroup> {
let mut groups: Vec<DispatchGroup> = Vec::new();
for e in timeline {
if e.event == "Dispatched" {
if let Some(last) = groups.last_mut() {
if last.cycle == e.cycle {
last.inst_indices.push(e.instruction);
continue;
}
}
groups.push(DispatchGroup {
cycle: e.cycle,
inst_indices: vec![e.instruction],
});
}
}
groups
}
pub fn avg_dispatch_group_size(groups: &[DispatchGroup]) -> f64 {
if groups.is_empty() {
return 0.0;
}
groups.iter().map(|g| g.inst_indices.len()).sum::<usize>() as f64 / groups.len() as f64
}
#[derive(Debug, Clone)]
pub struct PortPressure {
pub cycle: u64,
pub port_usage: HashMap<String, usize>,
}
pub fn build_port_pressure_map(
timeline: &[TimelineEvent],
insts: &[MCInst],
max_cycle: u64,
) -> Vec<PortPressure> {
let pn = [
"Port0", "Port1", "Port2", "Port3", "Port4", "Port5", "Port6", "Port7",
];
let mut map = Vec::new();
for cyc in 0..=max_cycle {
let mut usage = HashMap::new();
for n in &pn {
usage.insert(n.to_string(), 0usize);
}
for e in timeline {
if e.cycle == cyc && e.event == "Issued" && e.instruction < insts.len() {
let inst = &insts[e.instruction];
for (bit, name) in pn.iter().enumerate() {
if inst.ports & (1u32 << bit) != 0 {
*usage.entry(name.to_string()).or_insert(0) += 1;
}
}
}
}
map.push(PortPressure {
cycle: cyc,
port_usage: usage,
});
}
map
}
#[derive(Debug, Clone)]
pub struct StatisticalSummary {
pub min_inflight: usize,
pub max_inflight: usize,
pub avg_inflight: f64,
pub avg_dispatch_rate: f64,
pub avg_issue_rate: f64,
pub avg_retire_rate: f64,
pub stall_pct: f64,
}
pub fn compute_statistics(
result: &MCAnalysisResult,
dispatch_groups: &[DispatchGroup],
) -> StatisticalSummary {
let cycles = result.total_cycles.max(1) as f64;
let dispatched = result
.timeline
.iter()
.filter(|e| e.event == "Dispatched")
.count();
let issued = result
.timeline
.iter()
.filter(|e| e.event == "Issued")
.count();
let retired = result
.timeline
.iter()
.filter(|e| e.event == "Retired")
.count();
let gsz: Vec<usize> = dispatch_groups
.iter()
.map(|g| g.inst_indices.len())
.collect();
let adr = if !gsz.is_empty() {
gsz.iter().sum::<usize>() as f64 / gsz.len() as f64
} else {
0.0
};
StatisticalSummary {
min_inflight: 0,
max_inflight: result.instructions,
avg_inflight: 0.0,
avg_dispatch_rate: dispatched as f64 / cycles,
avg_issue_rate: issued as f64 / cycles,
avg_retire_rate: retired as f64 / cycles,
stall_pct: (result.dispatch_stalls + result.issue_stalls) as f64 / cycles * 100.0,
}
}
pub struct RegisterFile {
pub num_registers: usize,
pub register_width: u32,
pub read_ports: usize,
pub write_ports: usize,
rename_map: Vec<Option<usize>>,
free_list: Vec<usize>,
busy: Vec<bool>,
read_port_busy: Vec<bool>,
write_port_busy: Vec<bool>,
}
impl RegisterFile {
pub fn new(num_regs: usize, read_ports: usize, write_ports: usize) -> Self {
let mut free_list = Vec::with_capacity(num_regs);
for i in 0..num_regs {
free_list.push(i);
}
RegisterFile {
num_registers: num_regs,
register_width: 64,
read_ports,
write_ports,
rename_map: vec![None; 32], free_list,
busy: vec![false; num_regs],
read_port_busy: vec![false; read_ports],
write_port_busy: vec![false; write_ports],
}
}
pub fn rename_dest(&mut self, arch_reg: usize) -> Option<usize> {
if let Some(phys) = self.free_list.pop() {
self.rename_map[arch_reg] = Some(phys);
self.busy[phys] = true;
Some(phys)
} else {
None
}
}
pub fn lookup_src(&self, arch_reg: usize) -> Option<usize> {
if arch_reg < self.rename_map.len() {
self.rename_map[arch_reg]
} else {
None
}
}
pub fn free_register(&mut self, phys_reg: usize) {
if phys_reg < self.busy.len() && self.busy[phys_reg] {
self.busy[phys_reg] = false;
self.free_list.push(phys_reg);
}
}
pub fn can_read(&self) -> bool {
self.read_port_busy.iter().any(|b| !b)
}
pub fn reserve_read_port(&mut self) -> Option<usize> {
for (i, busy) in self.read_port_busy.iter_mut().enumerate() {
if !*busy {
*busy = true;
return Some(i);
}
}
None
}
pub fn can_write(&self) -> bool {
self.write_port_busy.iter().any(|b| !b)
}
pub fn reserve_write_port(&mut self) -> Option<usize> {
for (i, busy) in self.write_port_busy.iter_mut().enumerate() {
if !*busy {
*busy = true;
return Some(i);
}
}
None
}
pub fn advance_cycle(&mut self) {
for b in &mut self.read_port_busy {
*b = false;
}
for b in &mut self.write_port_busy {
*b = false;
}
}
pub fn free_count(&self) -> usize {
self.free_list.len()
}
}
pub struct MemoryDependencyAnalyzer {
store_addresses: Vec<(usize, u64)>,
unknown_stores: usize,
recent_ops: Vec<MemOp>,
}
#[derive(Debug, Clone)]
struct MemOp {
inst_idx: usize,
address: u64,
size: u32,
is_store: bool,
}
impl MemoryDependencyAnalyzer {
pub fn new() -> Self {
MemoryDependencyAnalyzer {
store_addresses: Vec::new(),
unknown_stores: 0,
recent_ops: Vec::new(),
}
}
pub fn record_store(&mut self, inst_idx: usize, addr: u64, size: u32) {
if addr == 0 {
self.unknown_stores += 1;
} else {
self.store_addresses.push((inst_idx, addr));
}
self.recent_ops.push(MemOp {
inst_idx,
address: addr,
size,
is_store: true,
});
}
pub fn record_load(&mut self, inst_idx: usize, addr: u64, size: u32) {
self.recent_ops.push(MemOp {
inst_idx,
address: addr,
size,
is_store: false,
});
}
pub fn find_aliasing_store(&self, addr: u64, size: u32) -> Option<usize> {
if self.unknown_stores > 0 {
return Some(0);
}
for &(inst_idx, store_addr) in self.store_addresses.iter().rev() {
if addresses_overlap(addr, size, store_addr, 8) {
return Some(inst_idx);
}
}
None
}
pub fn reset(&mut self) {
self.store_addresses.clear();
self.unknown_stores = 0;
self.recent_ops.clear();
}
}
fn addresses_overlap(a1: u64, s1: u32, a2: u64, s2: u32) -> bool {
let end1 = a1.saturating_add(s1 as u64);
let end2 = a2.saturating_add(s2 as u64);
a1 < end2 && a2 < end1
}
pub struct LoadStoreQueue {
load_queue: Vec<LSQEntry>,
store_queue: Vec<LSQEntry>,
load_queue_size: usize,
store_queue_size: usize,
forwarding_enabled: bool,
}
#[derive(Debug, Clone)]
struct LSQEntry {
inst_idx: usize,
address: u64,
size: u32,
valid: bool,
data: u64,
}
impl LoadStoreQueue {
pub fn new(ldq_size: usize, stq_size: usize) -> Self {
LoadStoreQueue {
load_queue: Vec::new(),
store_queue: Vec::new(),
load_queue_size: ldq_size,
store_queue_size: stq_size,
forwarding_enabled: true,
}
}
pub fn enqueue_load(&mut self, inst_idx: usize, addr: u64, size: u32) -> bool {
if self.load_queue.len() >= self.load_queue_size {
return false; }
self.load_queue.push(LSQEntry {
inst_idx,
address: addr,
size,
valid: true,
data: 0,
});
true
}
pub fn enqueue_store(&mut self, inst_idx: usize, addr: u64, size: u32, data: u64) -> bool {
if self.store_queue.len() >= self.store_queue_size {
return false; }
self.store_queue.push(LSQEntry {
inst_idx,
address: addr,
size,
valid: true,
data,
});
true
}
pub fn can_forward(&self, load_addr: u64, load_size: u32) -> Option<u64> {
if !self.forwarding_enabled {
return None;
}
for entry in self.store_queue.iter().rev() {
if entry.valid && entry.address == load_addr && entry.size >= load_size {
return Some(entry.data);
}
}
None
}
pub fn retire_load(&mut self) {
if !self.load_queue.is_empty() {
self.load_queue.remove(0);
}
}
pub fn retire_store(&mut self) {
if !self.store_queue.is_empty() {
self.store_queue.remove(0);
}
}
pub fn load_queue_available(&self) -> bool {
self.load_queue.len() < self.load_queue_size
}
pub fn store_queue_available(&self) -> bool {
self.store_queue.len() < self.store_queue_size
}
pub fn reset(&mut self) {
self.load_queue.clear();
self.store_queue.clear();
}
}
pub struct BranchPredictor {
pht: Vec<u8>,
index_mask: usize,
correct: u64,
mispredicts: u64,
}
impl BranchPredictor {
pub fn new(entries: usize) -> Self {
assert!(entries.is_power_of_two());
BranchPredictor {
pht: vec![2; entries], index_mask: entries - 1,
correct: 0,
mispredicts: 0,
}
}
pub fn predict(&self, pc: u64) -> bool {
let idx = self.get_index(pc);
let counter = self.pht[idx];
counter >= 2 }
pub fn update(&mut self, pc: u64, taken: bool) {
let idx = self.get_index(pc);
let was_predicted = self.pht[idx] >= 2;
if was_predicted == taken {
self.correct += 1;
} else {
self.mispredicts += 1;
}
if taken {
self.pht[idx] = (self.pht[idx] + 1).min(3);
} else {
self.pht[idx] = self.pht[idx].saturating_sub(1);
}
}
fn get_index(&self, pc: u64) -> usize {
((pc >> 2) as usize) & self.index_mask
}
pub fn accuracy(&self) -> f64 {
let total = self.correct + self.mispredicts;
if total == 0 {
return 100.0;
}
self.correct as f64 / total as f64 * 100.0
}
pub fn total_predictions(&self) -> u64 {
self.correct + self.mispredicts
}
pub fn reset(&mut self) {
self.pht.fill(2);
self.correct = 0;
self.mispredicts = 0;
}
}
pub struct InstructionIterator<'a> {
instructions: &'a [MCInst],
pos: usize,
iterations: usize,
max_iterations: usize,
}
impl<'a> InstructionIterator<'a> {
pub fn new(instructions: &'a [MCInst], max_iters: usize) -> Self {
InstructionIterator {
instructions,
pos: 0,
iterations: 0,
max_iterations: max_iters,
}
}
pub fn next(&mut self) -> Option<&'a MCInst> {
if self.pos >= self.instructions.len() {
if self.iterations + 1 >= self.max_iterations {
return None;
}
self.pos = 0;
self.iterations += 1;
}
let inst = &self.instructions[self.pos];
self.pos += 1;
Some(inst)
}
pub fn reset(&mut self) {
self.pos = 0;
self.iterations = 0;
}
pub fn current_iteration(&self) -> usize {
self.iterations
}
pub fn total_processed(&self) -> usize {
self.iterations * self.instructions.len() + self.pos
}
}
pub struct IterativeRefinement {
max_iterations: usize,
convergence_threshold: f64,
pressure_history: Vec<Vec<f64>>,
}
impl IterativeRefinement {
pub fn new() -> Self {
IterativeRefinement {
max_iterations: 10,
convergence_threshold: 0.01, pressure_history: Vec::new(),
}
}
pub fn has_converged(&self) -> bool {
if self.pressure_history.len() < 2 {
return false;
}
let prev = self.pressure_history.last().unwrap();
let prev_prev = self
.pressure_history
.get(self.pressure_history.len() - 2)
.unwrap();
let max_change = prev
.iter()
.zip(prev_prev.iter())
.map(|(a, b)| (a - b).abs())
.fold(0.0f64, f64::max);
max_change < self.convergence_threshold
}
pub fn record_pressure(&mut self, pressure: Vec<f64>) {
self.pressure_history.push(pressure);
}
pub fn iteration_count(&self) -> usize {
self.pressure_history.len()
}
pub fn max_iterations_reached(&self) -> bool {
self.pressure_history.len() >= self.max_iterations
}
pub fn reset(&mut self) {
self.pressure_history.clear();
}
}
impl LLVMMCA {
pub fn run_with_microarchitecture(
&mut self,
reg_file: &mut RegisterFile,
predictor: &mut BranchPredictor,
lsq: &mut LoadStoreQueue,
) -> MCAnalysisResult {
let instructions = self.parse_source();
let mut total_cycles = 0;
let mut dispatch_stalls = 0;
let mut issue_stalls = 0;
for _iter in 0..self.iterations {
for (idx, inst) in instructions.iter().enumerate() {
if !reg_file.can_read() {
issue_stalls += 1;
total_cycles += 1;
reg_file.advance_cycle();
}
reg_file.reserve_read_port();
if !reg_file.can_write() {
dispatch_stalls += 1;
total_cycles += 1;
reg_file.advance_cycle();
}
reg_file.reserve_write_port();
if inst.mnemonic.contains("br") || inst.mnemonic.contains("jmp") {
let pc = idx as u64;
let _predicted = predictor.predict(pc);
predictor.update(pc, true); }
if inst.mnemonic.contains("load") {
if !lsq.load_queue_available() {
dispatch_stalls += 1;
}
lsq.enqueue_load(idx, idx as u64 * 8, 8);
}
if inst.mnemonic.contains("store") {
if !lsq.store_queue_available() {
dispatch_stalls += 1;
}
lsq.enqueue_store(idx, idx as u64 * 8, 8, 0);
}
total_cycles += 1;
reg_file.advance_cycle();
}
}
MCAnalysisResult {
total_cycles,
instructions: instructions.len(),
ipc: if total_cycles > 0 {
instructions.len() as f64 / total_cycles as f64
} else {
0.0
},
dispatch_stalls,
issue_stalls,
resource_pressure: HashMap::new(),
timeline: Vec::new(),
}
}
pub fn run_with_refinement(
&mut self,
refinement: &mut IterativeRefinement,
) -> MCAnalysisResult {
let mut result = self.run();
let pressure: Vec<f64> = result.resource_pressure.iter().map(|(_, &p)| p).collect();
refinement.record_pressure(pressure);
while !refinement.has_converged() && !refinement.max_iterations_reached() {
result = self.run();
let pressure: Vec<f64> = result.resource_pressure.iter().map(|(_, &p)| p).collect();
refinement.record_pressure(pressure);
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
fn simple_asm() -> &'static str {
"add r0, r1, r2\nadd r3, r0, r4\nmul r5, r3, r6\n"
}
fn loop_asm() -> &'static str {
"add r0, r1, r2\nsub r3, r4, r5\nmul r6, r0, r3\n"
}
#[test]
fn test_create_mca() {
let mca = LLVMMCA::new("add r0, r1, r2");
assert_eq!(mca.iterations, 100);
}
#[test]
fn test_set_iterations() {
let mut mca = LLVMMCA::new("add r0, r1, r2");
mca.set_iterations(500);
assert_eq!(mca.iterations, 500);
}
#[test]
fn test_parse_empty_source() {
let mca = LLVMMCA::new("");
let insts = mca.parse_source();
assert!(insts.is_empty());
}
#[test]
fn test_parse_simple_source() {
let mca = LLVMMCA::new(simple_asm());
let insts = mca.parse_source();
assert_eq!(insts.len(), 3);
assert_eq!(insts[0].mnemonic, "add");
assert_eq!(insts[1].mnemonic, "add");
assert_eq!(insts[2].mnemonic, "mul");
}
#[test]
fn test_parse_comments() {
let source = "# This is a comment\nadd r0, r1, r2\n; Another comment\n";
let mca = LLVMMCA::new(source);
let insts = mca.parse_source();
assert_eq!(insts.len(), 1);
}
#[test]
fn test_get_instruction_info_add() {
let mca = LLVMMCA::new("");
let (latency, ports, uops, writes) = mca.get_instruction_info("add");
assert_eq!(latency, 1);
assert_eq!(uops, 1);
assert!(writes);
}
#[test]
fn test_get_instruction_info_mul() {
let mca = LLVMMCA::new("");
let (latency, _, uops, _) = mca.get_instruction_info("mul");
assert_eq!(latency, 3);
assert_eq!(uops, 1);
}
#[test]
fn test_get_instruction_info_div() {
let mca = LLVMMCA::new("");
let (latency, _, _, _) = mca.get_instruction_info("div");
assert_eq!(latency, 20);
}
#[test]
fn test_get_instruction_info_unknown() {
let mca = LLVMMCA::new("");
let (latency, _, uops, _) = mca.get_instruction_info("unknown_op");
assert_eq!(latency, 1);
assert_eq!(uops, 1);
}
#[test]
fn test_compute_critical_path_empty() {
let mca = LLVMMCA::new("");
let path = mca.compute_critical_path(&[]);
assert_eq!(path, 0);
}
#[test]
fn test_compute_critical_path_simple() {
let mca = LLVMMCA::new("");
let insts = mca.parse_source();
let path = mca.compute_critical_path(&insts);
if insts.len() == 3 {
assert_eq!(path, 5);
}
}
#[test]
fn test_compute_resource_pressure_empty() {
let mca = LLVMMCA::new("");
let pressure = mca.compute_resource_pressure(&[]);
assert!(pressure.contains_key("Port0"));
}
#[test]
fn test_simulate_pipeline_empty() {
let mca = LLVMMCA::new("");
let result = mca.simulate_pipeline(&[]);
assert_eq!(result.total_cycles, 0);
assert_eq!(result.instructions, 0);
assert_eq!(result.ipc, 0.0);
}
#[test]
fn test_simulate_pipeline_simple() {
let mca = LLVMMCA::new(simple_asm());
let insts = mca.parse_source();
let result = mca.simulate_pipeline(&insts);
assert!(result.total_cycles > 0);
assert_eq!(result.instructions, 3);
assert!(result.ipc > 0.0);
assert!(!result.timeline.is_empty());
}
#[test]
fn test_print_report() {
let mca = LLVMMCA::new(simple_asm());
let insts = mca.parse_source();
let result = mca.simulate_pipeline(&insts);
let report = mca.print_report(&result);
assert!(report.contains("LLVM MCA Analysis Report"));
assert!(report.contains("IPC:"));
assert!(report.contains("Resource Pressure"));
}
#[test]
fn test_run_full_pipeline() {
let mut mca = LLVMMCA::new(simple_asm());
let result = mca.run();
assert!(result.instructions > 0);
assert!(result.total_cycles > 0);
}
#[test]
fn test_run_with_loop_kernel() {
let mut mca = LLVMMCA::new(loop_asm());
mca.set_iterations(10);
let result = mca.run();
assert!(result.instructions > 0);
}
}