2025-08-12 11:33:48 +00:00
|
|
|
/*!
|
|
|
|
|
* MIR Printer - Debug output and visualization
|
2025-09-17 07:43:07 +09:00
|
|
|
*
|
2025-08-12 11:33:48 +00:00
|
|
|
* Implements pretty-printing for MIR modules and functions
|
|
|
|
|
*/
|
|
|
|
|
|
2025-09-17 07:43:07 +09:00
|
|
|
use super::{BasicBlock, MirFunction, MirInstruction, MirModule, MirType, ValueId};
|
|
|
|
|
use crate::debug::log as dlog;
|
2025-09-11 03:33:33 +09:00
|
|
|
use std::collections::HashMap;
|
2025-08-12 11:33:48 +00:00
|
|
|
use std::fmt::Write;
|
|
|
|
|
|
|
|
|
|
/// MIR printer for debug output and visualization
|
|
|
|
|
pub struct MirPrinter {
|
|
|
|
|
/// Indentation level
|
2025-08-16 17:39:04 +09:00
|
|
|
#[allow(dead_code)]
|
2025-08-12 11:33:48 +00:00
|
|
|
indent_level: usize,
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
/// Whether to show detailed information
|
|
|
|
|
verbose: bool,
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
/// Whether to show line numbers
|
|
|
|
|
show_line_numbers: bool,
|
2025-08-24 01:58:41 +09:00
|
|
|
|
|
|
|
|
/// Whether to show per-instruction effect category
|
|
|
|
|
show_effects_inline: bool,
|
2025-08-12 11:33:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MirPrinter {
|
|
|
|
|
/// Create a new MIR printer with default settings
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
indent_level: 0,
|
|
|
|
|
verbose: false,
|
|
|
|
|
show_line_numbers: true,
|
2025-08-24 01:58:41 +09:00
|
|
|
show_effects_inline: false,
|
2025-08-12 11:33:48 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
/// Create a verbose MIR printer
|
|
|
|
|
pub fn verbose() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
indent_level: 0,
|
|
|
|
|
verbose: true,
|
|
|
|
|
show_line_numbers: true,
|
2025-08-24 01:58:41 +09:00
|
|
|
show_effects_inline: false,
|
2025-08-12 11:33:48 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
/// Set verbose mode
|
|
|
|
|
pub fn set_verbose(&mut self, verbose: bool) -> &mut Self {
|
|
|
|
|
self.verbose = verbose;
|
|
|
|
|
self
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
/// Set line number display
|
|
|
|
|
pub fn set_show_line_numbers(&mut self, show: bool) -> &mut Self {
|
|
|
|
|
self.show_line_numbers = show;
|
|
|
|
|
self
|
|
|
|
|
}
|
2025-08-24 01:58:41 +09:00
|
|
|
|
|
|
|
|
/// Show per-instruction effect category (pure/readonly/side)
|
|
|
|
|
pub fn set_show_effects_inline(&mut self, show: bool) -> &mut Self {
|
|
|
|
|
self.show_effects_inline = show;
|
|
|
|
|
self
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
/// Print a complete MIR module
|
|
|
|
|
pub fn print_module(&self, module: &MirModule) -> String {
|
|
|
|
|
let mut output = String::new();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
// Module header
|
|
|
|
|
writeln!(output, "; MIR Module: {}", module.name).unwrap();
|
|
|
|
|
if let Some(ref source) = module.metadata.source_file {
|
|
|
|
|
writeln!(output, "; Source: {}", source).unwrap();
|
|
|
|
|
}
|
|
|
|
|
writeln!(output).unwrap();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
// Module statistics
|
|
|
|
|
if self.verbose {
|
|
|
|
|
let stats = module.stats();
|
|
|
|
|
writeln!(output, "; Module Statistics:").unwrap();
|
|
|
|
|
writeln!(output, "; Functions: {}", stats.function_count).unwrap();
|
|
|
|
|
writeln!(output, "; Globals: {}", stats.global_count).unwrap();
|
|
|
|
|
writeln!(output, "; Total Blocks: {}", stats.total_blocks).unwrap();
|
2025-09-17 07:43:07 +09:00
|
|
|
writeln!(
|
|
|
|
|
output,
|
|
|
|
|
"; Total Instructions: {}",
|
|
|
|
|
stats.total_instructions
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2025-08-12 11:33:48 +00:00
|
|
|
writeln!(output, "; Pure Functions: {}", stats.pure_functions).unwrap();
|
|
|
|
|
writeln!(output).unwrap();
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
// Global constants
|
|
|
|
|
if !module.globals.is_empty() {
|
|
|
|
|
writeln!(output, "; Global Constants:").unwrap();
|
|
|
|
|
for (name, value) in &module.globals {
|
|
|
|
|
writeln!(output, "global @{} = {}", name, value).unwrap();
|
|
|
|
|
}
|
|
|
|
|
writeln!(output).unwrap();
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
// Functions
|
2025-08-16 17:39:04 +09:00
|
|
|
for (_name, function) in &module.functions {
|
2025-08-12 11:33:48 +00:00
|
|
|
output.push_str(&self.print_function(function));
|
|
|
|
|
output.push('\n');
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
output
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
/// Print a single MIR function
|
|
|
|
|
pub fn print_function(&self, function: &MirFunction) -> String {
|
|
|
|
|
let mut output = String::new();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
// Function signature
|
2025-09-17 07:43:07 +09:00
|
|
|
write!(
|
|
|
|
|
output,
|
|
|
|
|
"define {} @{}(",
|
|
|
|
|
self.format_type(&function.signature.return_type),
|
|
|
|
|
function.signature.name
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
for (i, param_type) in function.signature.params.iter().enumerate() {
|
|
|
|
|
if i > 0 {
|
|
|
|
|
write!(output, ", ").unwrap();
|
|
|
|
|
}
|
|
|
|
|
write!(output, "{} %{}", self.format_type(param_type), i).unwrap();
|
|
|
|
|
}
|
|
|
|
|
write!(output, ")").unwrap();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
// Effects
|
|
|
|
|
if !function.signature.effects.is_pure() {
|
|
|
|
|
write!(output, " effects({})", function.signature.effects).unwrap();
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
writeln!(output, " {{").unwrap();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
// Function statistics
|
|
|
|
|
if self.verbose {
|
|
|
|
|
let stats = function.stats();
|
|
|
|
|
writeln!(output, " ; Function Statistics:").unwrap();
|
|
|
|
|
writeln!(output, " ; Blocks: {}", stats.block_count).unwrap();
|
|
|
|
|
writeln!(output, " ; Instructions: {}", stats.instruction_count).unwrap();
|
|
|
|
|
writeln!(output, " ; Values: {}", stats.value_count).unwrap();
|
|
|
|
|
writeln!(output, " ; Phi Functions: {}", stats.phi_count).unwrap();
|
|
|
|
|
if stats.is_pure {
|
|
|
|
|
writeln!(output, " ; Pure: yes").unwrap();
|
|
|
|
|
}
|
2025-08-24 01:58:41 +09:00
|
|
|
// Verbose: highlight MIR26-unified ops presence for snapshotting (TypeOp/WeakRef/Barrier)
|
|
|
|
|
let mut type_check = 0usize;
|
|
|
|
|
let mut type_cast = 0usize;
|
|
|
|
|
let mut weak_new = 0usize;
|
|
|
|
|
let mut weak_load = 0usize;
|
|
|
|
|
let mut barrier_read = 0usize;
|
|
|
|
|
let mut barrier_write = 0usize;
|
|
|
|
|
for block in function.blocks.values() {
|
|
|
|
|
for inst in &block.instructions {
|
|
|
|
|
match inst {
|
2025-09-17 07:43:07 +09:00
|
|
|
MirInstruction::Throw { .. } => {
|
|
|
|
|
if dlog::on("NYASH_DEBUG_MIR_PRINTER") {
|
|
|
|
|
eprintln!("[PRINTER] found throw in {}", function.signature.name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Catch { .. } => {
|
|
|
|
|
if dlog::on("NYASH_DEBUG_MIR_PRINTER") {
|
|
|
|
|
eprintln!("[PRINTER] found catch in {}", function.signature.name);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-24 01:58:41 +09:00
|
|
|
MirInstruction::TypeCheck { .. } => type_check += 1,
|
|
|
|
|
MirInstruction::Cast { .. } => type_cast += 1,
|
|
|
|
|
MirInstruction::TypeOp { op, .. } => match op {
|
|
|
|
|
super::TypeOpKind::Check => type_check += 1,
|
|
|
|
|
super::TypeOpKind::Cast => type_cast += 1,
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::WeakNew { .. } => weak_new += 1,
|
|
|
|
|
MirInstruction::WeakLoad { .. } => weak_load += 1,
|
|
|
|
|
MirInstruction::WeakRef { op, .. } => match op {
|
|
|
|
|
super::WeakRefOp::New => weak_new += 1,
|
|
|
|
|
super::WeakRefOp::Load => weak_load += 1,
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::BarrierRead { .. } => barrier_read += 1,
|
|
|
|
|
MirInstruction::BarrierWrite { .. } => barrier_write += 1,
|
|
|
|
|
MirInstruction::Barrier { op, .. } => match op {
|
|
|
|
|
super::BarrierOp::Read => barrier_read += 1,
|
|
|
|
|
super::BarrierOp::Write => barrier_write += 1,
|
|
|
|
|
},
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Some(term) = &block.terminator {
|
|
|
|
|
match term {
|
2025-09-17 07:43:07 +09:00
|
|
|
MirInstruction::Throw { .. } => {
|
|
|
|
|
if dlog::on("NYASH_DEBUG_MIR_PRINTER") {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[PRINTER] found throw(term) in {}",
|
|
|
|
|
function.signature.name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MirInstruction::Catch { .. } => {
|
|
|
|
|
if dlog::on("NYASH_DEBUG_MIR_PRINTER") {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[PRINTER] found catch(term) in {}",
|
|
|
|
|
function.signature.name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-24 01:58:41 +09:00
|
|
|
MirInstruction::TypeCheck { .. } => type_check += 1,
|
|
|
|
|
MirInstruction::Cast { .. } => type_cast += 1,
|
|
|
|
|
MirInstruction::TypeOp { op, .. } => match op {
|
|
|
|
|
super::TypeOpKind::Check => type_check += 1,
|
|
|
|
|
super::TypeOpKind::Cast => type_cast += 1,
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::WeakNew { .. } => weak_new += 1,
|
|
|
|
|
MirInstruction::WeakLoad { .. } => weak_load += 1,
|
|
|
|
|
MirInstruction::WeakRef { op, .. } => match op {
|
|
|
|
|
super::WeakRefOp::New => weak_new += 1,
|
|
|
|
|
super::WeakRefOp::Load => weak_load += 1,
|
|
|
|
|
},
|
|
|
|
|
MirInstruction::BarrierRead { .. } => barrier_read += 1,
|
|
|
|
|
MirInstruction::BarrierWrite { .. } => barrier_write += 1,
|
|
|
|
|
MirInstruction::Barrier { op, .. } => match op {
|
|
|
|
|
super::BarrierOp::Read => barrier_read += 1,
|
|
|
|
|
super::BarrierOp::Write => barrier_write += 1,
|
|
|
|
|
},
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if type_check + type_cast > 0 {
|
2025-09-17 07:43:07 +09:00
|
|
|
writeln!(
|
|
|
|
|
output,
|
|
|
|
|
" ; TypeOp: {} (check: {}, cast: {})",
|
|
|
|
|
type_check + type_cast,
|
|
|
|
|
type_check,
|
|
|
|
|
type_cast
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2025-08-24 01:58:41 +09:00
|
|
|
}
|
|
|
|
|
if weak_new + weak_load > 0 {
|
2025-09-17 07:43:07 +09:00
|
|
|
writeln!(
|
|
|
|
|
output,
|
|
|
|
|
" ; WeakRef: {} (new: {}, load: {})",
|
|
|
|
|
weak_new + weak_load,
|
|
|
|
|
weak_new,
|
|
|
|
|
weak_load
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2025-08-24 01:58:41 +09:00
|
|
|
}
|
|
|
|
|
if barrier_read + barrier_write > 0 {
|
2025-09-17 07:43:07 +09:00
|
|
|
writeln!(
|
|
|
|
|
output,
|
|
|
|
|
" ; Barrier: {} (read: {}, write: {})",
|
|
|
|
|
barrier_read + barrier_write,
|
|
|
|
|
barrier_read,
|
|
|
|
|
barrier_write
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2025-08-24 01:58:41 +09:00
|
|
|
}
|
2025-08-12 11:33:48 +00:00
|
|
|
writeln!(output).unwrap();
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
// Print blocks in order
|
|
|
|
|
let mut block_ids: Vec<_> = function.blocks.keys().copied().collect();
|
|
|
|
|
block_ids.sort();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
for (i, block_id) in block_ids.iter().enumerate() {
|
|
|
|
|
if let Some(block) = function.blocks.get(block_id) {
|
|
|
|
|
if i > 0 {
|
|
|
|
|
writeln!(output).unwrap();
|
|
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
output.push_str(&self.print_basic_block(block, &function.metadata.value_types));
|
2025-08-12 11:33:48 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
writeln!(output, "}}").unwrap();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
output
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
/// Print a basic block
|
2025-09-17 07:43:07 +09:00
|
|
|
pub fn print_basic_block(
|
|
|
|
|
&self,
|
|
|
|
|
block: &BasicBlock,
|
|
|
|
|
types: &HashMap<ValueId, MirType>,
|
|
|
|
|
) -> String {
|
2025-08-12 11:33:48 +00:00
|
|
|
let mut output = String::new();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
// Block header
|
|
|
|
|
write!(output, "{}:", block.id).unwrap();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
// Predecessors
|
|
|
|
|
if !block.predecessors.is_empty() && self.verbose {
|
2025-09-17 07:43:07 +09:00
|
|
|
let preds: Vec<String> = block
|
|
|
|
|
.predecessors
|
|
|
|
|
.iter()
|
2025-08-12 11:33:48 +00:00
|
|
|
.map(|p| format!("{}", p))
|
|
|
|
|
.collect();
|
|
|
|
|
write!(output, " ; preds({})", preds.join(", ")).unwrap();
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
writeln!(output).unwrap();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
// Instructions
|
|
|
|
|
let mut line_num = 0;
|
|
|
|
|
for instruction in block.all_instructions() {
|
|
|
|
|
if self.show_line_numbers {
|
|
|
|
|
write!(output, " {:3}: ", line_num).unwrap();
|
|
|
|
|
} else {
|
|
|
|
|
write!(output, " ").unwrap();
|
|
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
|
|
|
|
let mut line = self.format_instruction(instruction, types);
|
2025-08-24 01:58:41 +09:00
|
|
|
if self.show_effects_inline {
|
|
|
|
|
let eff = instruction.effects();
|
2025-09-17 07:43:07 +09:00
|
|
|
let cat = if eff.is_pure() {
|
|
|
|
|
"pure"
|
|
|
|
|
} else if eff.is_read_only() {
|
|
|
|
|
"readonly"
|
|
|
|
|
} else {
|
|
|
|
|
"side"
|
|
|
|
|
};
|
2025-08-24 01:58:41 +09:00
|
|
|
line.push_str(&format!(" ; eff: {}", cat));
|
|
|
|
|
}
|
|
|
|
|
writeln!(output, "{}", line).unwrap();
|
2025-08-12 11:33:48 +00:00
|
|
|
line_num += 1;
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
// Block effects (if verbose and not pure)
|
|
|
|
|
if self.verbose && !block.effects.is_pure() {
|
|
|
|
|
writeln!(output, " ; effects: {}", block.effects).unwrap();
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
output
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-09-11 03:33:33 +09:00
|
|
|
fn format_dst(&self, dst: &ValueId, types: &HashMap<ValueId, MirType>) -> String {
|
|
|
|
|
if let Some(ty) = types.get(dst) {
|
|
|
|
|
format!("{}: {:?} =", dst, ty)
|
|
|
|
|
} else {
|
|
|
|
|
format!("{} =", dst)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
/// Format a single instruction
|
2025-09-17 07:43:07 +09:00
|
|
|
fn format_instruction(
|
|
|
|
|
&self,
|
|
|
|
|
instruction: &MirInstruction,
|
|
|
|
|
types: &HashMap<ValueId, MirType>,
|
|
|
|
|
) -> String {
|
2025-08-12 11:33:48 +00:00
|
|
|
match instruction {
|
|
|
|
|
MirInstruction::Const { dst, value } => {
|
2025-09-11 03:33:33 +09:00
|
|
|
format!("{} const {}", self.format_dst(dst, types), value)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
MirInstruction::BinOp { dst, op, lhs, rhs } => {
|
2025-09-11 03:33:33 +09:00
|
|
|
format!("{} {} {:?} {}", self.format_dst(dst, types), lhs, op, rhs)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
MirInstruction::UnaryOp { dst, op, operand } => {
|
2025-09-11 03:33:33 +09:00
|
|
|
format!("{} {:?} {}", self.format_dst(dst, types), op, operand)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
MirInstruction::Compare { dst, op, lhs, rhs } => {
|
2025-09-17 07:43:07 +09:00
|
|
|
format!(
|
|
|
|
|
"{} icmp {:?} {}, {}",
|
|
|
|
|
self.format_dst(dst, types),
|
|
|
|
|
op,
|
|
|
|
|
lhs,
|
|
|
|
|
rhs
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
MirInstruction::Load { dst, ptr } => {
|
2025-09-11 03:33:33 +09:00
|
|
|
format!("{} load {}", self.format_dst(dst, types), ptr)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
MirInstruction::Store { value, ptr } => {
|
|
|
|
|
format!("store {} -> {}", value, ptr)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MirInstruction::Call {
|
|
|
|
|
dst,
|
|
|
|
|
func,
|
|
|
|
|
args,
|
|
|
|
|
effects: _,
|
|
|
|
|
} => {
|
|
|
|
|
let args_str = args
|
|
|
|
|
.iter()
|
2025-08-12 11:33:48 +00:00
|
|
|
.map(|v| format!("{}", v))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ");
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
if let Some(dst) = dst {
|
2025-09-17 07:43:07 +09:00
|
|
|
format!(
|
|
|
|
|
"{} call {}({})",
|
|
|
|
|
self.format_dst(dst, types),
|
|
|
|
|
func,
|
|
|
|
|
args_str
|
|
|
|
|
)
|
2025-08-12 11:33:48 +00:00
|
|
|
} else {
|
|
|
|
|
format!("call {}({})", func, args_str)
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
|
|
|
|
MirInstruction::FunctionNew {
|
|
|
|
|
dst,
|
|
|
|
|
params,
|
|
|
|
|
body,
|
|
|
|
|
captures,
|
|
|
|
|
me,
|
|
|
|
|
} => {
|
2025-09-04 03:41:02 +09:00
|
|
|
let p = params.join(", ");
|
2025-09-17 07:43:07 +09:00
|
|
|
let c = captures
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(n, v)| format!("{}={}", n, v))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ");
|
2025-09-04 03:41:02 +09:00
|
|
|
let me_s = me.map(|m| format!(" me={}", m)).unwrap_or_default();
|
2025-09-17 07:43:07 +09:00
|
|
|
let cap_s = if c.is_empty() {
|
|
|
|
|
String::new()
|
|
|
|
|
} else {
|
|
|
|
|
format!(" [{}]", c)
|
|
|
|
|
};
|
|
|
|
|
format!(
|
|
|
|
|
"{} function_new ({}) {{...{}}}{}{}",
|
|
|
|
|
self.format_dst(dst, types),
|
|
|
|
|
p,
|
|
|
|
|
body.len(),
|
|
|
|
|
cap_s,
|
|
|
|
|
me_s
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-09-17 07:43:07 +09:00
|
|
|
MirInstruction::BoxCall {
|
|
|
|
|
dst,
|
|
|
|
|
box_val,
|
|
|
|
|
method,
|
|
|
|
|
method_id,
|
|
|
|
|
args,
|
|
|
|
|
effects: _,
|
|
|
|
|
} => {
|
|
|
|
|
let args_str = args
|
|
|
|
|
.iter()
|
2025-08-12 11:33:48 +00:00
|
|
|
.map(|v| format!("{}", v))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ");
|
2025-08-26 20:48:48 +09:00
|
|
|
let id_suffix = method_id.map(|id| format!("[#{}]", id)).unwrap_or_default();
|
2025-08-12 11:33:48 +00:00
|
|
|
if let Some(dst) = dst {
|
2025-09-17 07:43:07 +09:00
|
|
|
format!(
|
|
|
|
|
"{} call {}.{}{}({})",
|
|
|
|
|
self.format_dst(dst, types),
|
|
|
|
|
box_val,
|
|
|
|
|
method,
|
|
|
|
|
id_suffix,
|
|
|
|
|
args_str
|
|
|
|
|
)
|
2025-08-12 11:33:48 +00:00
|
|
|
} else {
|
2025-08-26 20:48:48 +09:00
|
|
|
format!("call {}.{}{}({})", box_val, method, id_suffix, args_str)
|
2025-08-12 11:33:48 +00:00
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
|
|
|
|
MirInstruction::PluginInvoke {
|
|
|
|
|
dst,
|
|
|
|
|
box_val,
|
|
|
|
|
method,
|
|
|
|
|
args,
|
|
|
|
|
effects: _,
|
|
|
|
|
} => {
|
|
|
|
|
let args_str = args
|
|
|
|
|
.iter()
|
2025-08-30 02:04:00 +09:00
|
|
|
.map(|v| format!("{}", v))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ");
|
|
|
|
|
if let Some(dst) = dst {
|
2025-09-17 07:43:07 +09:00
|
|
|
format!(
|
|
|
|
|
"{} plugin_invoke {}.{}({})",
|
|
|
|
|
self.format_dst(dst, types),
|
|
|
|
|
box_val,
|
|
|
|
|
method,
|
|
|
|
|
args_str
|
|
|
|
|
)
|
2025-08-30 02:04:00 +09:00
|
|
|
} else {
|
|
|
|
|
format!("plugin_invoke {}.{}({})", box_val, method, args_str)
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MirInstruction::Branch {
|
|
|
|
|
condition,
|
|
|
|
|
then_bb,
|
|
|
|
|
else_bb,
|
|
|
|
|
} => {
|
2025-08-12 11:33:48 +00:00
|
|
|
format!("br {}, label {}, label {}", condition, then_bb, else_bb)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
|
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
MirInstruction::Jump { target } => {
|
|
|
|
|
format!("br label {}", target)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
|
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
MirInstruction::Return { value } => {
|
|
|
|
|
if let Some(value) = value {
|
|
|
|
|
format!("ret {}", value)
|
|
|
|
|
} else {
|
|
|
|
|
"ret void".to_string()
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
|
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
MirInstruction::Phi { dst, inputs } => {
|
2025-09-17 07:43:07 +09:00
|
|
|
let inputs_str = inputs
|
|
|
|
|
.iter()
|
2025-08-12 11:33:48 +00:00
|
|
|
.map(|(bb, val)| format!("[{}, {}]", val, bb))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ");
|
2025-09-11 03:33:33 +09:00
|
|
|
format!("{} phi {}", self.format_dst(dst, types), inputs_str)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-09-17 07:43:07 +09:00
|
|
|
MirInstruction::NewBox {
|
|
|
|
|
dst,
|
|
|
|
|
box_type,
|
|
|
|
|
args,
|
|
|
|
|
} => {
|
|
|
|
|
let args_str = args
|
|
|
|
|
.iter()
|
2025-08-12 11:33:48 +00:00
|
|
|
.map(|v| format!("{}", v))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ");
|
2025-09-17 07:43:07 +09:00
|
|
|
format!(
|
|
|
|
|
"{} new {}({})",
|
|
|
|
|
self.format_dst(dst, types),
|
|
|
|
|
box_type,
|
|
|
|
|
args_str
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-26 06:30:01 +09:00
|
|
|
// Legacy -> Unified print: TypeCheck as TypeOp(check)
|
2025-09-17 07:43:07 +09:00
|
|
|
MirInstruction::TypeCheck {
|
|
|
|
|
dst,
|
|
|
|
|
value,
|
|
|
|
|
expected_type,
|
|
|
|
|
} => {
|
2025-08-26 06:30:01 +09:00
|
|
|
// Print using unified TypeOp style to avoid naming divergence
|
2025-09-17 07:43:07 +09:00
|
|
|
format!(
|
|
|
|
|
"{} typeop check {} {}",
|
|
|
|
|
self.format_dst(dst, types),
|
|
|
|
|
value,
|
|
|
|
|
expected_type
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-09-17 07:43:07 +09:00
|
|
|
MirInstruction::Cast {
|
|
|
|
|
dst,
|
|
|
|
|
value,
|
|
|
|
|
target_type,
|
|
|
|
|
} => {
|
|
|
|
|
format!(
|
|
|
|
|
"{} cast {} to {:?}",
|
|
|
|
|
self.format_dst(dst, types),
|
|
|
|
|
value,
|
|
|
|
|
target_type
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-23 19:27:02 +09:00
|
|
|
MirInstruction::TypeOp { dst, op, value, ty } => {
|
2025-09-17 07:43:07 +09:00
|
|
|
let op_str = match op {
|
|
|
|
|
super::TypeOpKind::Check => "check",
|
|
|
|
|
super::TypeOpKind::Cast => "cast",
|
|
|
|
|
};
|
|
|
|
|
format!(
|
|
|
|
|
"{} typeop {} {} {:?}",
|
|
|
|
|
self.format_dst(dst, types),
|
|
|
|
|
op_str,
|
|
|
|
|
value,
|
|
|
|
|
ty
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
MirInstruction::ArrayGet { dst, array, index } => {
|
2025-09-11 03:33:33 +09:00
|
|
|
format!("{} {}[{}]", self.format_dst(dst, types), array, index)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-09-17 07:43:07 +09:00
|
|
|
MirInstruction::ArraySet {
|
|
|
|
|
array,
|
|
|
|
|
index,
|
|
|
|
|
value,
|
|
|
|
|
} => {
|
2025-08-12 11:33:48 +00:00
|
|
|
format!("{}[{}] = {}", array, index, value)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
MirInstruction::Copy { dst, src } => {
|
2025-09-11 03:33:33 +09:00
|
|
|
format!("{} copy {}", self.format_dst(dst, types), src)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
MirInstruction::Debug { value, message } => {
|
|
|
|
|
format!("debug {} \"{}\"", value, message)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-13 05:59:10 +00:00
|
|
|
MirInstruction::Print { value, effects: _ } => {
|
|
|
|
|
format!("print {}", value)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-09-17 07:43:07 +09:00
|
|
|
MirInstruction::Nop => "nop".to_string(),
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-13 06:23:28 +00:00
|
|
|
// Phase 5: Control flow & exception handling
|
2025-09-17 07:43:07 +09:00
|
|
|
MirInstruction::Throw {
|
|
|
|
|
exception,
|
|
|
|
|
effects: _,
|
|
|
|
|
} => {
|
2025-08-13 06:23:28 +00:00
|
|
|
format!("throw {}", exception)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-09-17 07:43:07 +09:00
|
|
|
MirInstruction::Catch {
|
|
|
|
|
exception_type,
|
|
|
|
|
exception_value,
|
|
|
|
|
handler_bb,
|
|
|
|
|
} => {
|
2025-08-13 06:23:28 +00:00
|
|
|
if let Some(ref exc_type) = exception_type {
|
|
|
|
|
format!("catch {} {} -> {}", exc_type, exception_value, handler_bb)
|
|
|
|
|
} else {
|
|
|
|
|
format!("catch * {} -> {}", exception_value, handler_bb)
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MirInstruction::Safepoint => "safepoint".to_string(),
|
|
|
|
|
|
2025-08-13 09:45:22 +00:00
|
|
|
// Phase 6: Box reference operations
|
|
|
|
|
MirInstruction::RefNew { dst, box_val } => {
|
2025-09-11 03:33:33 +09:00
|
|
|
format!("{} ref_new {}", self.format_dst(dst, types), box_val)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MirInstruction::RefGet {
|
|
|
|
|
dst,
|
|
|
|
|
reference,
|
|
|
|
|
field,
|
|
|
|
|
} => {
|
|
|
|
|
format!(
|
|
|
|
|
"{} ref_get {}.{}",
|
|
|
|
|
self.format_dst(dst, types),
|
|
|
|
|
reference,
|
|
|
|
|
field
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-09-17 07:43:07 +09:00
|
|
|
MirInstruction::RefSet {
|
|
|
|
|
reference,
|
|
|
|
|
field,
|
|
|
|
|
value,
|
|
|
|
|
} => {
|
2025-08-13 09:45:22 +00:00
|
|
|
format!("ref_set {}.{} = {}", reference, field, value)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
|
|
|
|
|
2025-08-26 06:30:01 +09:00
|
|
|
// Legacy -> Unified print: WeakNew as weakref new
|
2025-08-13 09:45:22 +00:00
|
|
|
MirInstruction::WeakNew { dst, box_val } => {
|
2025-09-11 03:33:33 +09:00
|
|
|
format!("{} weakref new {}", self.format_dst(dst, types), box_val)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-26 06:30:01 +09:00
|
|
|
// Legacy -> Unified print: WeakLoad as weakref load
|
2025-08-13 09:45:22 +00:00
|
|
|
MirInstruction::WeakLoad { dst, weak_ref } => {
|
2025-09-11 03:33:33 +09:00
|
|
|
format!("{} weakref load {}", self.format_dst(dst, types), weak_ref)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-26 06:30:01 +09:00
|
|
|
// Legacy -> Unified print: BarrierRead as barrier read
|
2025-08-13 09:45:22 +00:00
|
|
|
MirInstruction::BarrierRead { ptr } => {
|
2025-08-26 06:30:01 +09:00
|
|
|
format!("barrier read {}", ptr)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-26 06:30:01 +09:00
|
|
|
// Legacy -> Unified print: BarrierWrite as barrier write
|
2025-08-13 09:45:22 +00:00
|
|
|
MirInstruction::BarrierWrite { ptr } => {
|
2025-08-26 06:30:01 +09:00
|
|
|
format!("barrier write {}", ptr)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-23 19:27:02 +09:00
|
|
|
MirInstruction::WeakRef { dst, op, value } => {
|
2025-09-17 07:43:07 +09:00
|
|
|
let op_str = match op {
|
|
|
|
|
super::WeakRefOp::New => "new",
|
|
|
|
|
super::WeakRefOp::Load => "load",
|
|
|
|
|
};
|
|
|
|
|
format!(
|
|
|
|
|
"{} weakref {} {}",
|
|
|
|
|
self.format_dst(dst, types),
|
|
|
|
|
op_str,
|
|
|
|
|
value
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-23 19:27:02 +09:00
|
|
|
MirInstruction::Barrier { op, ptr } => {
|
2025-09-17 07:43:07 +09:00
|
|
|
let op_str = match op {
|
|
|
|
|
super::BarrierOp::Read => "read",
|
|
|
|
|
super::BarrierOp::Write => "write",
|
|
|
|
|
};
|
2025-08-23 19:27:02 +09:00
|
|
|
format!("barrier {} {}", op_str, ptr)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-13 11:46:01 +00:00
|
|
|
// Phase 7: Async/Future Operations
|
|
|
|
|
MirInstruction::FutureNew { dst, value } => {
|
2025-09-11 03:33:33 +09:00
|
|
|
format!("{} future_new {}", self.format_dst(dst, types), value)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-13 11:46:01 +00:00
|
|
|
MirInstruction::FutureSet { future, value } => {
|
|
|
|
|
format!("future_set {} = {}", future, value)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-13 11:46:01 +00:00
|
|
|
MirInstruction::Await { dst, future } => {
|
2025-09-11 03:33:33 +09:00
|
|
|
format!("{} await {}", self.format_dst(dst, types), future)
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-09-11 03:33:33 +09:00
|
|
|
|
2025-08-14 08:56:39 +00:00
|
|
|
// Phase 9.7: External Function Calls
|
2025-09-17 07:43:07 +09:00
|
|
|
MirInstruction::ExternCall {
|
|
|
|
|
dst,
|
|
|
|
|
iface_name,
|
|
|
|
|
method_name,
|
|
|
|
|
args,
|
|
|
|
|
effects,
|
|
|
|
|
} => {
|
|
|
|
|
let args_str = args
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|v| format!("{}", v))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ");
|
2025-08-14 08:56:39 +00:00
|
|
|
if let Some(dst) = dst {
|
2025-09-17 07:43:07 +09:00
|
|
|
format!(
|
|
|
|
|
"{} extern_call {}.{}({}) [effects: {}]",
|
|
|
|
|
self.format_dst(dst, types),
|
|
|
|
|
iface_name,
|
|
|
|
|
method_name,
|
|
|
|
|
args_str,
|
|
|
|
|
effects
|
|
|
|
|
)
|
2025-08-14 08:56:39 +00:00
|
|
|
} else {
|
2025-09-17 07:43:07 +09:00
|
|
|
format!(
|
|
|
|
|
"extern_call {}.{}({}) [effects: {}]",
|
|
|
|
|
iface_name, method_name, args_str, effects
|
|
|
|
|
)
|
2025-08-14 08:56:39 +00:00
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
}
|
2025-08-12 11:33:48 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
/// Format a MIR type
|
|
|
|
|
fn format_type(&self, mir_type: &super::MirType) -> String {
|
|
|
|
|
match mir_type {
|
|
|
|
|
super::MirType::Integer => "i64".to_string(),
|
|
|
|
|
super::MirType::Float => "f64".to_string(),
|
|
|
|
|
super::MirType::Bool => "i1".to_string(),
|
|
|
|
|
super::MirType::String => "str".to_string(),
|
|
|
|
|
super::MirType::Box(name) => format!("box<{}>", name),
|
|
|
|
|
super::MirType::Array(elem_type) => format!("[{}]", self.format_type(elem_type)),
|
2025-09-17 07:43:07 +09:00
|
|
|
super::MirType::Future(inner_type) => {
|
|
|
|
|
format!("future<{}>", self.format_type(inner_type))
|
|
|
|
|
}
|
2025-08-12 11:33:48 +00:00
|
|
|
super::MirType::Void => "void".to_string(),
|
|
|
|
|
super::MirType::Unknown => "?".to_string(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for MirPrinter {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
2025-09-17 07:43:07 +09:00
|
|
|
use crate::mir::{
|
|
|
|
|
BasicBlockId, EffectMask, FunctionSignature, MirFunction, MirModule, MirType,
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_empty_module_printing() {
|
|
|
|
|
let module = MirModule::new("test".to_string());
|
|
|
|
|
let printer = MirPrinter::new();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
let output = printer.print_module(&module);
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
assert!(output.contains("MIR Module: test"));
|
|
|
|
|
assert!(!output.is_empty());
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_function_printing() {
|
|
|
|
|
let signature = FunctionSignature {
|
|
|
|
|
name: "test_func".to_string(),
|
|
|
|
|
params: vec![MirType::Integer],
|
|
|
|
|
return_type: MirType::Void,
|
|
|
|
|
effects: EffectMask::PURE,
|
|
|
|
|
};
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
let function = MirFunction::new(signature, BasicBlockId::new(0));
|
|
|
|
|
let printer = MirPrinter::new();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
let output = printer.print_function(&function);
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
assert!(output.contains("define void @test_func(i64 %0)"));
|
|
|
|
|
assert!(output.contains("bb0:"));
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
#[test]
|
|
|
|
|
fn test_verbose_printing() {
|
|
|
|
|
let module = MirModule::new("test".to_string());
|
|
|
|
|
let printer = MirPrinter::verbose();
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
let output = printer.print_module(&module);
|
2025-09-17 07:43:07 +09:00
|
|
|
|
2025-08-12 11:33:48 +00:00
|
|
|
assert!(output.contains("Module Statistics"));
|
|
|
|
|
}
|
2025-08-23 19:27:02 +09:00
|
|
|
}
|