feat(jit): CFG DOT出力機能を追加 (ChatGPT5実装)

- src/jit/lower/cfg_dot.rs: Control Flow GraphをGraphviz DOT形式で出力
- LowerCoreからdump_cfg_dot関数をエクスポート
- NYASH_JIT_DOT環境変数でCFG可視化が可能に

ChatGPT5さんによるリファクタリング作業の一環

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-08-29 02:12:48 +09:00
parent 25fbebd650
commit 770df1566c
3 changed files with 63 additions and 62 deletions

60
src/jit/lower/cfg_dot.rs Normal file
View File

@ -0,0 +1,60 @@
pub fn dump_cfg_dot(func: &crate::mir::MirFunction, path: &str, phi_min: bool) -> std::io::Result<()> {
use std::io::Write;
let mut out = String::new();
out.push_str(&format!("digraph \"{}\" {{\n", func.signature.name));
out.push_str(" node [shape=box, fontsize=10];\n");
// Derive simple bool sets: compare dsts are bool; phi of all-bool inputs are bool
let mut bool_values: std::collections::HashSet<crate::mir::ValueId> = std::collections::HashSet::new();
for (_bb_id, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::Compare { dst, .. } = ins { bool_values.insert(*dst); }
}
}
let mut bool_phi: std::collections::HashSet<crate::mir::ValueId> = std::collections::HashSet::new();
if phi_min {
for (_bb_id, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
if !inputs.is_empty() && inputs.iter().all(|(_, v)| bool_values.contains(v)) {
bool_phi.insert(*dst);
}
}
}
}
}
// Sort blocks for deterministic output
let mut bb_ids: Vec<_> = func.blocks.keys().copied().collect();
bb_ids.sort_by_key(|b| b.0);
// Emit nodes with labels
for bb_id in bb_ids.iter() {
let bb = func.blocks.get(bb_id).unwrap();
let phi_count = bb.instructions.iter().filter(|ins| matches!(ins, crate::mir::MirInstruction::Phi { .. })).count();
let phi_b1_count = bb.instructions.iter().filter(|ins| match ins { crate::mir::MirInstruction::Phi { dst, .. } => bool_phi.contains(dst), _ => false }).count();
let mut label = format!("bb{}", bb_id.0);
if phi_min && phi_count > 0 {
if phi_b1_count > 0 { label = format!("{}\\nphi:{} (b1:{})", label, phi_count, phi_b1_count); }
else { label = format!("{}\\nphi:{}", label, phi_count); }
}
if *bb_id == func.entry_block { label = format!("{}\\nENTRY", label); }
out.push_str(&format!(" n{} [label=\"{}\"];\n", bb_id.0, label));
}
// Emit edges based on terminators
for bb_id in bb_ids.iter() {
let bb = func.blocks.get(bb_id).unwrap();
if let Some(term) = &bb.terminator {
match term {
crate::mir::MirInstruction::Jump { target } => {
out.push_str(&format!(" n{} -> n{};\n", bb_id.0, target.0));
}
crate::mir::MirInstruction::Branch { then_bb, else_bb, .. } => {
// Branch condition is boolean (b1)
out.push_str(&format!(" n{} -> n{} [label=\"then cond:b1\"];\n", bb_id.0, then_bb.0));
out.push_str(&format!(" n{} -> n{} [label=\"else cond:b1\"];\n", bb_id.0, else_bb.0));
}
_ => {}
}
}
}
out.push_str("}\n");
std::fs::write(path, out)
}

View File

@ -1008,65 +1008,5 @@ impl LowerCore {
}
}
/// Emit a simple DOT graph for a MIR function's CFG.
/// Includes block ids, successor edges, and PHI counts per block when phi_min is enabled.
pub fn dump_cfg_dot(func: &crate::mir::MirFunction, path: &str, phi_min: bool) -> std::io::Result<()> {
use std::io::Write;
let mut out = String::new();
out.push_str(&format!("digraph \"{}\" {{\n", func.signature.name));
out.push_str(" node [shape=box, fontsize=10];\n");
// Derive simple bool sets: compare dsts are bool; phi of all-bool inputs are bool
let mut bool_values: std::collections::HashSet<crate::mir::ValueId> = std::collections::HashSet::new();
for (_bb_id, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::Compare { dst, .. } = ins { bool_values.insert(*dst); }
}
}
let mut bool_phi: std::collections::HashSet<crate::mir::ValueId> = std::collections::HashSet::new();
if phi_min {
for (_bb_id, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::Phi { dst, inputs } = ins {
if !inputs.is_empty() && inputs.iter().all(|(_, v)| bool_values.contains(v)) {
bool_phi.insert(*dst);
}
}
}
}
}
// Sort blocks for deterministic output
let mut bb_ids: Vec<_> = func.blocks.keys().copied().collect();
bb_ids.sort_by_key(|b| b.0);
// Emit nodes with labels
for bb_id in bb_ids.iter() {
let bb = func.blocks.get(bb_id).unwrap();
let phi_count = bb.instructions.iter().filter(|ins| matches!(ins, crate::mir::MirInstruction::Phi { .. })).count();
let phi_b1_count = bb.instructions.iter().filter(|ins| match ins { crate::mir::MirInstruction::Phi { dst, .. } => bool_phi.contains(dst), _ => false }).count();
let mut label = format!("bb{}", bb_id.0);
if phi_min && phi_count > 0 {
if phi_b1_count > 0 { label = format!("{}\\nphi:{} (b1:{})", label, phi_count, phi_b1_count); }
else { label = format!("{}\\nphi:{}", label, phi_count); }
}
if *bb_id == func.entry_block { label = format!("{}\\nENTRY", label); }
out.push_str(&format!(" n{} [label=\"{}\"];\n", bb_id.0, label));
}
// Emit edges based on terminators
for bb_id in bb_ids.iter() {
let bb = func.blocks.get(bb_id).unwrap();
if let Some(term) = &bb.terminator {
match term {
crate::mir::MirInstruction::Jump { target } => {
out.push_str(&format!(" n{} -> n{};\n", bb_id.0, target.0));
}
crate::mir::MirInstruction::Branch { then_bb, else_bb, .. } => {
// Branch condition is boolean (b1)
out.push_str(&format!(" n{} -> n{} [label=\"then cond:b1\"];\n", bb_id.0, then_bb.0));
out.push_str(&format!(" n{} -> n{} [label=\"else cond:b1\"];\n", bb_id.0, else_bb.0));
}
_ => {}
}
}
}
out.push_str("}\n");
std::fs::write(path, out)
}
pub use super::cfg_dot::dump_cfg_dot;

View File

@ -2,3 +2,4 @@
pub mod core;
pub mod builder;
pub mod extern_thunks;
pub mod cfg_dot;