🚀 MIR Stage 1 Basic Infrastructure Complete - 20 Instructions + SSA + Effects
Co-authored-by: moe-charm <217100418+moe-charm@users.noreply.github.com>
This commit is contained in:
@ -32,6 +32,9 @@ pub mod value;
|
||||
pub mod messaging;
|
||||
pub mod transport;
|
||||
|
||||
// 🚀 MIR (Mid-level Intermediate Representation) Infrastructure (NEW!)
|
||||
pub mod mir;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm_test;
|
||||
|
||||
|
||||
118
src/main.rs
118
src/main.rs
@ -26,6 +26,10 @@ use tokenizer::{NyashTokenizer, TokenType};
|
||||
use ast::ASTNode;
|
||||
use parser::NyashParser;
|
||||
use interpreter::NyashInterpreter;
|
||||
|
||||
// 🚀 MIR Infrastructure
|
||||
pub mod mir;
|
||||
use mir::{MirCompiler, MirPrinter};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::process;
|
||||
@ -50,13 +54,40 @@ fn main() {
|
||||
.help("Set parser debug fuel limit (default: 100000, 'unlimited' for no limit)")
|
||||
.default_value("100000")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("dump-mir")
|
||||
.long("dump-mir")
|
||||
.help("Dump MIR (Mid-level Intermediate Representation) instead of executing")
|
||||
.action(clap::ArgAction::SetTrue)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("verify")
|
||||
.long("verify")
|
||||
.help("Verify MIR integrity and exit")
|
||||
.action(clap::ArgAction::SetTrue)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("mir-verbose")
|
||||
.long("mir-verbose")
|
||||
.help("Show verbose MIR output with statistics")
|
||||
.action(clap::ArgAction::SetTrue)
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// デバッグ燃料の解析
|
||||
let debug_fuel = parse_debug_fuel(matches.get_one::<String>("debug-fuel").unwrap());
|
||||
|
||||
// MIR mode flags
|
||||
let dump_mir = matches.get_flag("dump-mir");
|
||||
let verify_mir = matches.get_flag("verify");
|
||||
let mir_verbose = matches.get_flag("mir-verbose");
|
||||
|
||||
if let Some(filename) = matches.get_one::<String>("file") {
|
||||
// File mode: parse and execute the provided .nyash file
|
||||
if dump_mir || verify_mir {
|
||||
println!("🚀 Nyash MIR Compiler - Processing file: {} 🚀", filename);
|
||||
execute_mir_mode(filename, dump_mir, verify_mir, mir_verbose);
|
||||
} else {
|
||||
println!("🦀 Nyash Rust Implementation - Executing file: {} 🦀", filename);
|
||||
if let Some(fuel) = debug_fuel {
|
||||
println!("🔥 Debug fuel limit: {} iterations", fuel);
|
||||
@ -66,6 +97,7 @@ fn main() {
|
||||
println!("====================================================");
|
||||
|
||||
execute_nyash_file(filename, debug_fuel);
|
||||
}
|
||||
} else {
|
||||
// Demo mode: run built-in demonstrations
|
||||
println!("🦀 Nyash Rust Implementation - Everything is Box! 🦀");
|
||||
@ -1051,6 +1083,92 @@ fn demo_interpreter_system() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute MIR compilation and processing mode
|
||||
fn execute_mir_mode(filename: &str, dump_mir: bool, verify_mir: bool, verbose: bool) {
|
||||
// Read the source file
|
||||
let source = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error reading file '{}': {}", filename, e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Parse to AST
|
||||
let ast = match NyashParser::parse_from_string(&source) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Parse error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Compile to MIR
|
||||
let mut compiler = MirCompiler::new();
|
||||
let compile_result = match compiler.compile(ast) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
eprintln!("❌ MIR compilation error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle verification
|
||||
if verify_mir || dump_mir {
|
||||
match &compile_result.verification_result {
|
||||
Ok(()) => {
|
||||
if verify_mir {
|
||||
println!("✅ MIR verification passed");
|
||||
}
|
||||
},
|
||||
Err(errors) => {
|
||||
eprintln!("❌ MIR verification failed with {} error(s):", errors.len());
|
||||
for (i, error) in errors.iter().enumerate() {
|
||||
eprintln!(" {}: {}", i + 1, error);
|
||||
}
|
||||
if verify_mir {
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle MIR dumping
|
||||
if dump_mir {
|
||||
let mut printer = if verbose {
|
||||
MirPrinter::verbose()
|
||||
} else {
|
||||
MirPrinter::new()
|
||||
};
|
||||
|
||||
let mir_output = printer.print_module(&compile_result.module);
|
||||
println!("{}", mir_output);
|
||||
}
|
||||
|
||||
// Show module statistics if verification was requested
|
||||
if verify_mir {
|
||||
let stats = compile_result.module.stats();
|
||||
println!("\n📊 Module Statistics:");
|
||||
println!(" Functions: {}", stats.function_count);
|
||||
println!(" Total Blocks: {}", stats.total_blocks);
|
||||
println!(" Total Instructions: {}", stats.total_instructions);
|
||||
println!(" Total Values: {}", stats.total_values);
|
||||
println!(" Pure Functions: {}", stats.pure_functions);
|
||||
|
||||
if stats.function_count > 0 {
|
||||
for (name, function) in &compile_result.module.functions {
|
||||
let func_stats = function.stats();
|
||||
println!("\n📊 Function '{}' Statistics:", name);
|
||||
println!(" Blocks: {}", func_stats.block_count);
|
||||
println!(" Instructions: {}", func_stats.instruction_count);
|
||||
println!(" Values: {}", func_stats.value_count);
|
||||
println!(" Phi Functions: {}", func_stats.phi_count);
|
||||
println!(" Pure: {}", func_stats.is_pure);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
423
src/mir/basic_block.rs
Normal file
423
src/mir/basic_block.rs
Normal file
@ -0,0 +1,423 @@
|
||||
/*!
|
||||
* MIR Basic Block - Control Flow Graph Building Block
|
||||
*
|
||||
* SSA-form basic blocks with phi functions and terminator instructions
|
||||
*/
|
||||
|
||||
use super::{MirInstruction, ValueId, EffectMask};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
/// Unique identifier for basic blocks within a function
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct BasicBlockId(pub u32);
|
||||
|
||||
impl BasicBlockId {
|
||||
/// Create a new BasicBlockId
|
||||
pub fn new(id: u32) -> Self {
|
||||
BasicBlockId(id)
|
||||
}
|
||||
|
||||
/// Get the raw ID value
|
||||
pub fn as_u32(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Create BasicBlockId from usize (for array indexing)
|
||||
pub fn from_usize(id: usize) -> Self {
|
||||
BasicBlockId(id as u32)
|
||||
}
|
||||
|
||||
/// Convert to usize (for array indexing)
|
||||
pub fn to_usize(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BasicBlockId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "bb{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A basic block in SSA form
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BasicBlock {
|
||||
/// Unique identifier for this block
|
||||
pub id: BasicBlockId,
|
||||
|
||||
/// Instructions in this block (excluding terminator)
|
||||
pub instructions: Vec<MirInstruction>,
|
||||
|
||||
/// Terminator instruction (branch, jump, or return)
|
||||
pub terminator: Option<MirInstruction>,
|
||||
|
||||
/// Predecessors in the control flow graph
|
||||
pub predecessors: HashSet<BasicBlockId>,
|
||||
|
||||
/// Successors in the control flow graph
|
||||
pub successors: HashSet<BasicBlockId>,
|
||||
|
||||
/// Combined effect mask for all instructions in this block
|
||||
pub effects: EffectMask,
|
||||
|
||||
/// Whether this block is reachable from the entry block
|
||||
pub reachable: bool,
|
||||
}
|
||||
|
||||
impl BasicBlock {
|
||||
/// Create a new basic block
|
||||
pub fn new(id: BasicBlockId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
instructions: Vec::new(),
|
||||
terminator: None,
|
||||
predecessors: HashSet::new(),
|
||||
successors: HashSet::new(),
|
||||
effects: EffectMask::PURE,
|
||||
reachable: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an instruction to this block
|
||||
pub fn add_instruction(&mut self, instruction: MirInstruction) {
|
||||
// Update effect mask
|
||||
self.effects = self.effects | instruction.effects();
|
||||
|
||||
// Check if this is a terminator instruction
|
||||
if self.is_terminator(&instruction) {
|
||||
if self.terminator.is_some() {
|
||||
panic!("Basic block {} already has a terminator", self.id);
|
||||
}
|
||||
self.terminator = Some(instruction);
|
||||
|
||||
// Update successors based on terminator
|
||||
self.update_successors_from_terminator();
|
||||
} else {
|
||||
self.instructions.push(instruction);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if an instruction is a terminator
|
||||
fn is_terminator(&self, instruction: &MirInstruction) -> bool {
|
||||
matches!(instruction,
|
||||
MirInstruction::Branch { .. } |
|
||||
MirInstruction::Jump { .. } |
|
||||
MirInstruction::Return { .. }
|
||||
)
|
||||
}
|
||||
|
||||
/// Update successors based on the terminator instruction
|
||||
fn update_successors_from_terminator(&mut self) {
|
||||
self.successors.clear();
|
||||
|
||||
if let Some(ref terminator) = self.terminator {
|
||||
match terminator {
|
||||
MirInstruction::Branch { then_bb, else_bb, .. } => {
|
||||
self.successors.insert(*then_bb);
|
||||
self.successors.insert(*else_bb);
|
||||
},
|
||||
MirInstruction::Jump { target } => {
|
||||
self.successors.insert(*target);
|
||||
},
|
||||
MirInstruction::Return { .. } => {
|
||||
// No successors for return
|
||||
},
|
||||
_ => unreachable!("Non-terminator instruction in terminator position"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a predecessor
|
||||
pub fn add_predecessor(&mut self, pred: BasicBlockId) {
|
||||
self.predecessors.insert(pred);
|
||||
}
|
||||
|
||||
/// Remove a predecessor
|
||||
pub fn remove_predecessor(&mut self, pred: BasicBlockId) {
|
||||
self.predecessors.remove(&pred);
|
||||
}
|
||||
|
||||
/// Get all instructions including terminator
|
||||
pub fn all_instructions(&self) -> impl Iterator<Item = &MirInstruction> {
|
||||
self.instructions.iter().chain(self.terminator.iter())
|
||||
}
|
||||
|
||||
/// Get all values defined in this block
|
||||
pub fn defined_values(&self) -> Vec<ValueId> {
|
||||
self.all_instructions()
|
||||
.filter_map(|inst| inst.dst_value())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get all values used in this block
|
||||
pub fn used_values(&self) -> Vec<ValueId> {
|
||||
self.all_instructions()
|
||||
.flat_map(|inst| inst.used_values())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Check if this block is empty (no instructions)
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.instructions.is_empty() && self.terminator.is_none()
|
||||
}
|
||||
|
||||
/// Check if this block has a terminator
|
||||
pub fn is_terminated(&self) -> bool {
|
||||
self.terminator.is_some()
|
||||
}
|
||||
|
||||
/// Check if this block ends with a return
|
||||
pub fn ends_with_return(&self) -> bool {
|
||||
matches!(self.terminator, Some(MirInstruction::Return { .. }))
|
||||
}
|
||||
|
||||
/// Get the phi instructions at the beginning of this block
|
||||
pub fn phi_instructions(&self) -> impl Iterator<Item = &MirInstruction> {
|
||||
self.instructions.iter()
|
||||
.take_while(|inst| matches!(inst, MirInstruction::Phi { .. }))
|
||||
}
|
||||
|
||||
/// Get non-phi instructions
|
||||
pub fn non_phi_instructions(&self) -> impl Iterator<Item = &MirInstruction> {
|
||||
self.instructions.iter()
|
||||
.skip_while(|inst| matches!(inst, MirInstruction::Phi { .. }))
|
||||
}
|
||||
|
||||
/// Insert instruction at the beginning (after phi instructions)
|
||||
pub fn insert_instruction_after_phis(&mut self, instruction: MirInstruction) {
|
||||
let phi_count = self.phi_instructions().count();
|
||||
self.effects = self.effects | instruction.effects();
|
||||
self.instructions.insert(phi_count, instruction);
|
||||
}
|
||||
|
||||
/// Replace terminator instruction
|
||||
pub fn set_terminator(&mut self, terminator: MirInstruction) {
|
||||
if !self.is_terminator(&terminator) {
|
||||
panic!("Instruction is not a valid terminator: {:?}", terminator);
|
||||
}
|
||||
|
||||
self.effects = self.effects | terminator.effects();
|
||||
self.terminator = Some(terminator);
|
||||
self.update_successors_from_terminator();
|
||||
}
|
||||
|
||||
/// Mark this block as reachable
|
||||
pub fn mark_reachable(&mut self) {
|
||||
self.reachable = true;
|
||||
}
|
||||
|
||||
/// Check if this block dominates another block (simplified check)
|
||||
pub fn dominates(&self, other: BasicBlockId, dominators: &[HashSet<BasicBlockId>]) -> bool {
|
||||
if let Some(dom_set) = dominators.get(other.to_usize()) {
|
||||
dom_set.contains(&self.id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Basic block ID generator
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BasicBlockIdGenerator {
|
||||
next_id: u32,
|
||||
}
|
||||
|
||||
impl BasicBlockIdGenerator {
|
||||
/// Create a new generator starting from 0
|
||||
pub fn new() -> Self {
|
||||
Self { next_id: 0 }
|
||||
}
|
||||
|
||||
/// Generate the next unique BasicBlockId
|
||||
pub fn next(&mut self) -> BasicBlockId {
|
||||
let id = BasicBlockId(self.next_id);
|
||||
self.next_id += 1;
|
||||
id
|
||||
}
|
||||
|
||||
/// Peek at the next ID without consuming it
|
||||
pub fn peek_next(&self) -> BasicBlockId {
|
||||
BasicBlockId(self.next_id)
|
||||
}
|
||||
|
||||
/// Reset the generator (for testing)
|
||||
pub fn reset(&mut self) {
|
||||
self.next_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BasicBlockIdGenerator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BasicBlock {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "{}:", self.id)?;
|
||||
|
||||
// Show predecessors
|
||||
if !self.predecessors.is_empty() {
|
||||
let preds: Vec<String> = self.predecessors.iter()
|
||||
.map(|p| format!("{}", p))
|
||||
.collect();
|
||||
writeln!(f, " ; preds: {}", preds.join(", "))?;
|
||||
}
|
||||
|
||||
// Show instructions
|
||||
for instruction in &self.instructions {
|
||||
writeln!(f, " {}", instruction)?;
|
||||
}
|
||||
|
||||
// Show terminator
|
||||
if let Some(ref terminator) = self.terminator {
|
||||
writeln!(f, " {}", terminator)?;
|
||||
}
|
||||
|
||||
// Show effects if not pure
|
||||
if !self.effects.is_pure() {
|
||||
writeln!(f, " ; effects: {}", self.effects)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::{ConstValue, BinaryOp};
|
||||
|
||||
#[test]
|
||||
fn test_basic_block_creation() {
|
||||
let bb_id = BasicBlockId::new(0);
|
||||
let mut bb = BasicBlock::new(bb_id);
|
||||
|
||||
assert_eq!(bb.id, bb_id);
|
||||
assert!(bb.is_empty());
|
||||
assert!(!bb.is_terminated());
|
||||
assert!(bb.effects.is_pure());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_addition() {
|
||||
let bb_id = BasicBlockId::new(0);
|
||||
let mut bb = BasicBlock::new(bb_id);
|
||||
|
||||
let const_inst = MirInstruction::Const {
|
||||
dst: ValueId::new(0),
|
||||
value: ConstValue::Integer(42),
|
||||
};
|
||||
|
||||
bb.add_instruction(const_inst);
|
||||
|
||||
assert_eq!(bb.instructions.len(), 1);
|
||||
assert!(!bb.is_empty());
|
||||
assert!(bb.effects.is_pure());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_terminator_addition() {
|
||||
let bb_id = BasicBlockId::new(0);
|
||||
let mut bb = BasicBlock::new(bb_id);
|
||||
|
||||
let return_inst = MirInstruction::Return {
|
||||
value: Some(ValueId::new(0)),
|
||||
};
|
||||
|
||||
bb.add_instruction(return_inst);
|
||||
|
||||
assert!(bb.is_terminated());
|
||||
assert!(bb.ends_with_return());
|
||||
assert_eq!(bb.instructions.len(), 0); // Terminator not in instructions
|
||||
assert!(bb.terminator.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_branch_successors() {
|
||||
let bb_id = BasicBlockId::new(0);
|
||||
let mut bb = BasicBlock::new(bb_id);
|
||||
|
||||
let then_bb = BasicBlockId::new(1);
|
||||
let else_bb = BasicBlockId::new(2);
|
||||
|
||||
let branch_inst = MirInstruction::Branch {
|
||||
condition: ValueId::new(0),
|
||||
then_bb,
|
||||
else_bb,
|
||||
};
|
||||
|
||||
bb.add_instruction(branch_inst);
|
||||
|
||||
assert_eq!(bb.successors.len(), 2);
|
||||
assert!(bb.successors.contains(&then_bb));
|
||||
assert!(bb.successors.contains(&else_bb));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_block_id_generator() {
|
||||
let mut gen = BasicBlockIdGenerator::new();
|
||||
|
||||
let bb1 = gen.next();
|
||||
let bb2 = gen.next();
|
||||
let bb3 = gen.next();
|
||||
|
||||
assert_eq!(bb1, BasicBlockId(0));
|
||||
assert_eq!(bb2, BasicBlockId(1));
|
||||
assert_eq!(bb3, BasicBlockId(2));
|
||||
|
||||
assert_eq!(gen.peek_next(), BasicBlockId(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_tracking() {
|
||||
let bb_id = BasicBlockId::new(0);
|
||||
let mut bb = BasicBlock::new(bb_id);
|
||||
|
||||
let val1 = ValueId::new(1);
|
||||
let val2 = ValueId::new(2);
|
||||
let val3 = ValueId::new(3);
|
||||
|
||||
// Add instruction that defines val3 and uses val1, val2
|
||||
bb.add_instruction(MirInstruction::BinOp {
|
||||
dst: val3,
|
||||
op: BinaryOp::Add,
|
||||
lhs: val1,
|
||||
rhs: val2,
|
||||
});
|
||||
|
||||
let defined = bb.defined_values();
|
||||
let used = bb.used_values();
|
||||
|
||||
assert_eq!(defined, vec![val3]);
|
||||
assert_eq!(used, vec![val1, val2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_phi_instruction_ordering() {
|
||||
let bb_id = BasicBlockId::new(0);
|
||||
let mut bb = BasicBlock::new(bb_id);
|
||||
|
||||
// Add phi instruction
|
||||
let phi_inst = MirInstruction::Phi {
|
||||
dst: ValueId::new(0),
|
||||
inputs: vec![(BasicBlockId::new(1), ValueId::new(1))],
|
||||
};
|
||||
bb.add_instruction(phi_inst);
|
||||
|
||||
// Add regular instruction
|
||||
let const_inst = MirInstruction::Const {
|
||||
dst: ValueId::new(2),
|
||||
value: ConstValue::Integer(42),
|
||||
};
|
||||
bb.add_instruction(const_inst);
|
||||
|
||||
// Phi instructions should come first
|
||||
let phi_count = bb.phi_instructions().count();
|
||||
assert_eq!(phi_count, 1);
|
||||
|
||||
let non_phi_count = bb.non_phi_instructions().count();
|
||||
assert_eq!(non_phi_count, 1);
|
||||
}
|
||||
}
|
||||
501
src/mir/builder.rs
Normal file
501
src/mir/builder.rs
Normal file
@ -0,0 +1,501 @@
|
||||
/*!
|
||||
* MIR Builder - Converts AST to MIR/SSA form
|
||||
*
|
||||
* Implements AST → MIR conversion with SSA construction
|
||||
*/
|
||||
|
||||
use super::{
|
||||
MirInstruction, BasicBlock, BasicBlockId, MirFunction, MirModule,
|
||||
FunctionSignature, ValueId, ConstValue, BinaryOp, UnaryOp, CompareOp,
|
||||
MirType, EffectMask, Effect, BasicBlockIdGenerator, ValueIdGenerator
|
||||
};
|
||||
use crate::ast::{ASTNode, LiteralValue, BinaryOperator};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// MIR builder for converting AST to SSA form
|
||||
pub struct MirBuilder {
|
||||
/// Current module being built
|
||||
current_module: Option<MirModule>,
|
||||
|
||||
/// Current function being built
|
||||
current_function: Option<MirFunction>,
|
||||
|
||||
/// Current basic block being built
|
||||
current_block: Option<BasicBlockId>,
|
||||
|
||||
/// Value ID generator
|
||||
value_gen: ValueIdGenerator,
|
||||
|
||||
/// Basic block ID generator
|
||||
block_gen: BasicBlockIdGenerator,
|
||||
|
||||
/// Variable name to ValueId mapping (for SSA conversion)
|
||||
variable_map: HashMap<String, ValueId>,
|
||||
|
||||
/// Pending phi functions to be inserted
|
||||
pending_phis: Vec<(BasicBlockId, ValueId, String)>,
|
||||
}
|
||||
|
||||
impl MirBuilder {
|
||||
/// Create a new MIR builder
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current_module: None,
|
||||
current_function: None,
|
||||
current_block: None,
|
||||
value_gen: ValueIdGenerator::new(),
|
||||
block_gen: BasicBlockIdGenerator::new(),
|
||||
variable_map: HashMap::new(),
|
||||
pending_phis: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a complete MIR module from AST
|
||||
pub fn build_module(&mut self, ast: ASTNode) -> Result<MirModule, String> {
|
||||
// Create a new module
|
||||
let mut module = MirModule::new("main".to_string());
|
||||
|
||||
// Create a main function to contain the AST
|
||||
let main_signature = FunctionSignature {
|
||||
name: "main".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
|
||||
let entry_block = self.block_gen.next();
|
||||
let mut main_function = MirFunction::new(main_signature, entry_block);
|
||||
main_function.metadata.is_entry_point = true;
|
||||
|
||||
// Set up building context
|
||||
self.current_module = Some(module);
|
||||
self.current_function = Some(main_function);
|
||||
self.current_block = Some(entry_block);
|
||||
|
||||
// Convert AST to MIR
|
||||
let result_value = self.build_expression(ast)?;
|
||||
|
||||
// Add return instruction if needed
|
||||
if let Some(block_id) = self.current_block {
|
||||
if let Some(ref mut function) = self.current_function {
|
||||
if let Some(block) = function.get_block_mut(block_id) {
|
||||
if !block.is_terminated() {
|
||||
block.add_instruction(MirInstruction::Return {
|
||||
value: Some(result_value),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize and return module
|
||||
let mut module = self.current_module.take().unwrap();
|
||||
let function = self.current_function.take().unwrap();
|
||||
module.add_function(function);
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
/// Build an expression and return its value ID
|
||||
fn build_expression(&mut self, ast: ASTNode) -> Result<ValueId, String> {
|
||||
match ast {
|
||||
ASTNode::Literal { value, .. } => {
|
||||
self.build_literal(value)
|
||||
},
|
||||
|
||||
ASTNode::BinaryOp { left, operator, right, .. } => {
|
||||
self.build_binary_op(*left, operator, *right)
|
||||
},
|
||||
|
||||
ASTNode::UnaryOp { operator, operand, .. } => {
|
||||
let op_string = match operator {
|
||||
crate::ast::UnaryOperator::Minus => "-".to_string(),
|
||||
crate::ast::UnaryOperator::Not => "not".to_string(),
|
||||
};
|
||||
self.build_unary_op(op_string, *operand)
|
||||
},
|
||||
|
||||
ASTNode::Variable { name, .. } => {
|
||||
self.build_variable_access(name.clone())
|
||||
},
|
||||
|
||||
ASTNode::Assignment { target, value, .. } => {
|
||||
// For now, assume target is a variable identifier
|
||||
if let ASTNode::Variable { name, .. } = target.as_ref() {
|
||||
self.build_assignment(name.clone(), *value.clone())
|
||||
} else {
|
||||
Err("Complex assignment targets not yet supported in MIR".to_string())
|
||||
}
|
||||
},
|
||||
|
||||
ASTNode::FunctionCall { name, arguments, .. } => {
|
||||
self.build_function_call(name.clone(), arguments.clone())
|
||||
},
|
||||
|
||||
ASTNode::Program { statements, .. } => {
|
||||
self.build_block(statements.clone())
|
||||
},
|
||||
|
||||
ASTNode::If { condition, then_body, else_body, .. } => {
|
||||
let else_ast = if let Some(else_statements) = else_body {
|
||||
Some(ASTNode::Program {
|
||||
statements: else_statements.clone(),
|
||||
span: crate::ast::Span::unknown(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.build_if_statement(
|
||||
*condition.clone(),
|
||||
ASTNode::Program {
|
||||
statements: then_body.clone(),
|
||||
span: crate::ast::Span::unknown(),
|
||||
},
|
||||
else_ast
|
||||
)
|
||||
},
|
||||
|
||||
_ => {
|
||||
Err(format!("Unsupported AST node type: {:?}", ast))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a literal value
|
||||
fn build_literal(&mut self, literal: LiteralValue) -> Result<ValueId, String> {
|
||||
let const_value = match literal {
|
||||
LiteralValue::Integer(n) => ConstValue::Integer(n),
|
||||
LiteralValue::Float(f) => ConstValue::Float(f),
|
||||
LiteralValue::String(s) => ConstValue::String(s),
|
||||
LiteralValue::Bool(b) => ConstValue::Bool(b),
|
||||
LiteralValue::Void => ConstValue::Void,
|
||||
};
|
||||
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst,
|
||||
value: const_value,
|
||||
})?;
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
/// Build a binary operation
|
||||
fn build_binary_op(&mut self, left: ASTNode, operator: BinaryOperator, right: ASTNode) -> Result<ValueId, String> {
|
||||
let lhs = self.build_expression(left)?;
|
||||
let rhs = self.build_expression(right)?;
|
||||
let dst = self.value_gen.next();
|
||||
|
||||
let mir_op = self.convert_binary_operator(operator)?;
|
||||
|
||||
match mir_op {
|
||||
// Arithmetic operations
|
||||
BinaryOpType::Arithmetic(op) => {
|
||||
self.emit_instruction(MirInstruction::BinOp {
|
||||
dst, op, lhs, rhs
|
||||
})?;
|
||||
},
|
||||
|
||||
// Comparison operations
|
||||
BinaryOpType::Comparison(op) => {
|
||||
self.emit_instruction(MirInstruction::Compare {
|
||||
dst, op, lhs, rhs
|
||||
})?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
/// Build a unary operation
|
||||
fn build_unary_op(&mut self, operator: String, operand: ASTNode) -> Result<ValueId, String> {
|
||||
let operand_val = self.build_expression(operand)?;
|
||||
let dst = self.value_gen.next();
|
||||
|
||||
let mir_op = self.convert_unary_operator(operator)?;
|
||||
|
||||
self.emit_instruction(MirInstruction::UnaryOp {
|
||||
dst,
|
||||
op: mir_op,
|
||||
operand: operand_val,
|
||||
})?;
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
/// Build variable access
|
||||
fn build_variable_access(&mut self, name: String) -> Result<ValueId, String> {
|
||||
if let Some(&value_id) = self.variable_map.get(&name) {
|
||||
Ok(value_id)
|
||||
} else {
|
||||
Err(format!("Undefined variable: {}", name))
|
||||
}
|
||||
}
|
||||
|
||||
/// Build assignment
|
||||
fn build_assignment(&mut self, var_name: String, value: ASTNode) -> Result<ValueId, String> {
|
||||
let value_id = self.build_expression(value)?;
|
||||
|
||||
// In SSA form, each assignment creates a new value
|
||||
self.variable_map.insert(var_name, value_id);
|
||||
|
||||
Ok(value_id)
|
||||
}
|
||||
|
||||
/// Build function call
|
||||
fn build_function_call(&mut self, name: String, args: Vec<ASTNode>) -> Result<ValueId, String> {
|
||||
// Build argument values
|
||||
let mut arg_values = Vec::new();
|
||||
for arg in args {
|
||||
arg_values.push(self.build_expression(arg)?);
|
||||
}
|
||||
|
||||
let dst = self.value_gen.next();
|
||||
|
||||
// For now, treat all function calls as Box method calls
|
||||
if arg_values.is_empty() {
|
||||
return Err("Function calls require at least one argument (the object)".to_string());
|
||||
}
|
||||
|
||||
let box_val = arg_values.remove(0);
|
||||
|
||||
self.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(dst),
|
||||
box_val,
|
||||
method: name,
|
||||
args: arg_values,
|
||||
effects: EffectMask::PURE.add(Effect::ReadHeap), // Conservative default
|
||||
})?;
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
/// Build a block of statements
|
||||
fn build_block(&mut self, statements: Vec<ASTNode>) -> Result<ValueId, String> {
|
||||
let mut last_value = None;
|
||||
|
||||
for statement in statements {
|
||||
last_value = Some(self.build_expression(statement)?);
|
||||
}
|
||||
|
||||
// Return last value or void
|
||||
Ok(last_value.unwrap_or_else(|| {
|
||||
let void_val = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: void_val,
|
||||
value: ConstValue::Void,
|
||||
}).unwrap();
|
||||
void_val
|
||||
}))
|
||||
}
|
||||
|
||||
/// Build if statement with conditional branches
|
||||
fn build_if_statement(&mut self, condition: ASTNode, then_branch: ASTNode, else_branch: Option<ASTNode>) -> Result<ValueId, String> {
|
||||
let condition_val = self.build_expression(condition)?;
|
||||
|
||||
// Create basic blocks for then/else/merge
|
||||
let then_block = self.block_gen.next();
|
||||
let else_block = self.block_gen.next();
|
||||
let merge_block = self.block_gen.next();
|
||||
|
||||
// Emit branch instruction in current block
|
||||
self.emit_instruction(MirInstruction::Branch {
|
||||
condition: condition_val,
|
||||
then_bb: then_block,
|
||||
else_bb: else_block,
|
||||
})?;
|
||||
|
||||
// Build then branch
|
||||
self.current_block = Some(then_block);
|
||||
self.ensure_block_exists(then_block)?;
|
||||
let then_value = self.build_expression(then_branch)?;
|
||||
self.emit_instruction(MirInstruction::Jump { target: merge_block })?;
|
||||
|
||||
// Build else branch
|
||||
self.current_block = Some(else_block);
|
||||
self.ensure_block_exists(else_block)?;
|
||||
let else_value = if let Some(else_ast) = else_branch {
|
||||
self.build_expression(else_ast)?
|
||||
} else {
|
||||
// No else branch, use void
|
||||
let void_val = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: void_val,
|
||||
value: ConstValue::Void,
|
||||
})?;
|
||||
void_val
|
||||
};
|
||||
self.emit_instruction(MirInstruction::Jump { target: merge_block })?;
|
||||
|
||||
// Create merge block with phi function
|
||||
self.current_block = Some(merge_block);
|
||||
self.ensure_block_exists(merge_block)?;
|
||||
let result_val = self.value_gen.next();
|
||||
|
||||
self.emit_instruction(MirInstruction::Phi {
|
||||
dst: result_val,
|
||||
inputs: vec![
|
||||
(then_block, then_value),
|
||||
(else_block, else_value),
|
||||
],
|
||||
})?;
|
||||
|
||||
Ok(result_val)
|
||||
}
|
||||
|
||||
/// Emit an instruction to the current basic block
|
||||
fn emit_instruction(&mut self, instruction: MirInstruction) -> Result<(), String> {
|
||||
let block_id = self.current_block.ok_or("No current basic block")?;
|
||||
|
||||
if let Some(ref mut function) = self.current_function {
|
||||
if let Some(block) = function.get_block_mut(block_id) {
|
||||
block.add_instruction(instruction);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Basic block {} does not exist", block_id))
|
||||
}
|
||||
} else {
|
||||
Err("No current function".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure a basic block exists in the current function
|
||||
fn ensure_block_exists(&mut self, block_id: BasicBlockId) -> Result<(), String> {
|
||||
if let Some(ref mut function) = self.current_function {
|
||||
if !function.blocks.contains_key(&block_id) {
|
||||
let block = BasicBlock::new(block_id);
|
||||
function.add_block(block);
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err("No current function".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert AST binary operator to MIR operator
|
||||
fn convert_binary_operator(&self, op: BinaryOperator) -> Result<BinaryOpType, String> {
|
||||
match op {
|
||||
BinaryOperator::Add => Ok(BinaryOpType::Arithmetic(BinaryOp::Add)),
|
||||
BinaryOperator::Subtract => Ok(BinaryOpType::Arithmetic(BinaryOp::Sub)),
|
||||
BinaryOperator::Multiply => Ok(BinaryOpType::Arithmetic(BinaryOp::Mul)),
|
||||
BinaryOperator::Divide => Ok(BinaryOpType::Arithmetic(BinaryOp::Div)),
|
||||
BinaryOperator::Equal => Ok(BinaryOpType::Comparison(CompareOp::Eq)),
|
||||
BinaryOperator::NotEqual => Ok(BinaryOpType::Comparison(CompareOp::Ne)),
|
||||
BinaryOperator::Less => Ok(BinaryOpType::Comparison(CompareOp::Lt)),
|
||||
BinaryOperator::LessEqual => Ok(BinaryOpType::Comparison(CompareOp::Le)),
|
||||
BinaryOperator::Greater => Ok(BinaryOpType::Comparison(CompareOp::Gt)),
|
||||
BinaryOperator::GreaterEqual => Ok(BinaryOpType::Comparison(CompareOp::Ge)),
|
||||
BinaryOperator::And => Ok(BinaryOpType::Arithmetic(BinaryOp::And)),
|
||||
BinaryOperator::Or => Ok(BinaryOpType::Arithmetic(BinaryOp::Or)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert AST unary operator to MIR operator
|
||||
fn convert_unary_operator(&self, op: String) -> Result<UnaryOp, String> {
|
||||
match op.as_str() {
|
||||
"-" => Ok(UnaryOp::Neg),
|
||||
"!" | "not" => Ok(UnaryOp::Not),
|
||||
"~" => Ok(UnaryOp::BitNot),
|
||||
_ => Err(format!("Unsupported unary operator: {}", op)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper enum for binary operator classification
|
||||
#[derive(Debug)]
|
||||
enum BinaryOpType {
|
||||
Arithmetic(BinaryOp),
|
||||
Comparison(CompareOp),
|
||||
}
|
||||
|
||||
impl Default for MirBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{ASTNode, LiteralValue, Span};
|
||||
|
||||
#[test]
|
||||
fn test_literal_building() {
|
||||
let mut builder = MirBuilder::new();
|
||||
|
||||
let ast = ASTNode::Literal {
|
||||
value: LiteralValue::Integer(42),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let result = builder.build_module(ast);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let module = result.unwrap();
|
||||
assert_eq!(module.function_names().len(), 1);
|
||||
assert!(module.get_function("main").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binary_op_building() {
|
||||
let mut builder = MirBuilder::new();
|
||||
|
||||
let ast = ASTNode::BinaryOp {
|
||||
left: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(10),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
operator: BinaryOperator::Add,
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(32),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let result = builder.build_module(ast);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let module = result.unwrap();
|
||||
let function = module.get_function("main").unwrap();
|
||||
|
||||
// Should have constants and binary operation
|
||||
let stats = function.stats();
|
||||
assert!(stats.instruction_count >= 3); // 2 constants + 1 binop + 1 return
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_if_statement_building() {
|
||||
let mut builder = MirBuilder::new();
|
||||
|
||||
let ast = ASTNode::IfStatement {
|
||||
condition: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Boolean(true),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
then_branch: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
else_branch: Some(Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(2),
|
||||
span: Span::unknown(),
|
||||
})),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let result = builder.build_module(ast);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let module = result.unwrap();
|
||||
let function = module.get_function("main").unwrap();
|
||||
|
||||
// Should have multiple blocks for if/then/else/merge
|
||||
assert!(function.blocks.len() >= 3);
|
||||
|
||||
// Should have phi function in merge block
|
||||
let stats = function.stats();
|
||||
assert!(stats.phi_count >= 1);
|
||||
}
|
||||
}
|
||||
289
src/mir/effect.rs
Normal file
289
src/mir/effect.rs
Normal file
@ -0,0 +1,289 @@
|
||||
/*!
|
||||
* MIR Effect System - Track side effects for optimization
|
||||
*
|
||||
* Based on ChatGPT5's design for parallel execution and optimization safety
|
||||
*/
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Effect flags for tracking side effects and enabling optimizations
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct EffectMask(u16);
|
||||
|
||||
/// Individual effect types
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Effect {
|
||||
/// Pure computation with no side effects
|
||||
Pure = 0x0001,
|
||||
/// Reads from heap/memory (but doesn't modify)
|
||||
ReadHeap = 0x0002,
|
||||
/// Writes to heap/memory
|
||||
WriteHeap = 0x0004,
|
||||
/// Performs I/O operations (file, network, console)
|
||||
IO = 0x0008,
|
||||
/// P2P/network communication
|
||||
P2P = 0x0010,
|
||||
/// Foreign Function Interface calls
|
||||
FFI = 0x0020,
|
||||
/// May panic or throw exceptions
|
||||
Panic = 0x0040,
|
||||
/// Allocates memory
|
||||
Alloc = 0x0080,
|
||||
/// Accesses global state
|
||||
Global = 0x0100,
|
||||
/// Thread/async operations
|
||||
Async = 0x0200,
|
||||
/// Unsafe operations
|
||||
Unsafe = 0x0400,
|
||||
/// Debug/logging operations
|
||||
Debug = 0x0800,
|
||||
}
|
||||
|
||||
impl EffectMask {
|
||||
/// No effects - pure computation
|
||||
pub const PURE: Self = Self(Effect::Pure as u16);
|
||||
|
||||
/// Memory read effects
|
||||
pub const READ: Self = Self(Effect::ReadHeap as u16);
|
||||
|
||||
/// Memory write effects (includes read)
|
||||
pub const WRITE: Self = Self((Effect::WriteHeap as u16) | (Effect::ReadHeap as u16));
|
||||
|
||||
/// I/O effects
|
||||
pub const IO: Self = Self(Effect::IO as u16);
|
||||
|
||||
/// P2P communication effects
|
||||
pub const P2P: Self = Self(Effect::P2P as u16);
|
||||
|
||||
/// All effects - maximum side effects
|
||||
pub const ALL: Self = Self(0xFFFF);
|
||||
|
||||
/// Create an empty effect mask
|
||||
pub fn new() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
/// Create effect mask from raw bits
|
||||
pub fn from_bits(bits: u16) -> Self {
|
||||
Self(bits)
|
||||
}
|
||||
|
||||
/// Get raw bits
|
||||
pub fn bits(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Add an effect to the mask
|
||||
pub fn add(self, effect: Effect) -> Self {
|
||||
Self(self.0 | (effect as u16))
|
||||
}
|
||||
|
||||
/// Remove an effect from the mask
|
||||
pub fn remove(self, effect: Effect) -> Self {
|
||||
Self(self.0 & !(effect as u16))
|
||||
}
|
||||
|
||||
/// Check if mask contains an effect
|
||||
pub fn contains(self, effect: Effect) -> bool {
|
||||
(self.0 & (effect as u16)) != 0
|
||||
}
|
||||
|
||||
/// Check if mask contains any of the given effects
|
||||
pub fn contains_any(self, mask: EffectMask) -> bool {
|
||||
(self.0 & mask.0) != 0
|
||||
}
|
||||
|
||||
/// Check if mask contains all of the given effects
|
||||
pub fn contains_all(self, mask: EffectMask) -> bool {
|
||||
(self.0 & mask.0) == mask.0
|
||||
}
|
||||
|
||||
/// Combine two effect masks
|
||||
pub fn union(self, other: EffectMask) -> Self {
|
||||
Self(self.0 | other.0)
|
||||
}
|
||||
|
||||
/// Get intersection of two effect masks
|
||||
pub fn intersection(self, other: EffectMask) -> Self {
|
||||
Self(self.0 & other.0)
|
||||
}
|
||||
|
||||
/// Check if the computation is pure (no side effects)
|
||||
pub fn is_pure(self) -> bool {
|
||||
self.0 == 0 || self.0 == (Effect::Pure as u16)
|
||||
}
|
||||
|
||||
/// Check if the computation only reads (doesn't modify state)
|
||||
pub fn is_read_only(self) -> bool {
|
||||
!self.contains(Effect::WriteHeap) &&
|
||||
!self.contains(Effect::IO) &&
|
||||
!self.contains(Effect::P2P) &&
|
||||
!self.contains(Effect::Global)
|
||||
}
|
||||
|
||||
/// Check if parallel execution is safe
|
||||
pub fn is_parallel_safe(self) -> bool {
|
||||
!self.contains(Effect::WriteHeap) &&
|
||||
!self.contains(Effect::Global) &&
|
||||
!self.contains(Effect::Unsafe)
|
||||
}
|
||||
|
||||
/// Check if operation can be moved across other operations
|
||||
pub fn is_moveable(self) -> bool {
|
||||
self.is_pure() || self.is_read_only()
|
||||
}
|
||||
|
||||
/// Get a human-readable list of effects
|
||||
pub fn effect_names(self) -> Vec<&'static str> {
|
||||
let mut names = Vec::new();
|
||||
|
||||
if self.is_pure() {
|
||||
names.push("pure");
|
||||
return names;
|
||||
}
|
||||
|
||||
if self.contains(Effect::ReadHeap) { names.push("read"); }
|
||||
if self.contains(Effect::WriteHeap) { names.push("write"); }
|
||||
if self.contains(Effect::IO) { names.push("io"); }
|
||||
if self.contains(Effect::P2P) { names.push("p2p"); }
|
||||
if self.contains(Effect::FFI) { names.push("ffi"); }
|
||||
if self.contains(Effect::Panic) { names.push("panic"); }
|
||||
if self.contains(Effect::Alloc) { names.push("alloc"); }
|
||||
if self.contains(Effect::Global) { names.push("global"); }
|
||||
if self.contains(Effect::Async) { names.push("async"); }
|
||||
if self.contains(Effect::Unsafe) { names.push("unsafe"); }
|
||||
if self.contains(Effect::Debug) { names.push("debug"); }
|
||||
|
||||
names
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EffectMask {
|
||||
fn default() -> Self {
|
||||
Self::PURE
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EffectMask {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let names = self.effect_names();
|
||||
if names.is_empty() {
|
||||
write!(f, "none")
|
||||
} else {
|
||||
write!(f, "{}", names.join("|"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitOr for EffectMask {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self {
|
||||
self.union(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitOrAssign for EffectMask {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
*self = *self | rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitAnd for EffectMask {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self {
|
||||
self.intersection(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::BitAndAssign for EffectMask {
|
||||
fn bitand_assign(&mut self, rhs: Self) {
|
||||
*self = *self & rhs;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_effect_mask_creation() {
|
||||
let pure = EffectMask::PURE;
|
||||
let read = EffectMask::READ;
|
||||
let write = EffectMask::WRITE;
|
||||
|
||||
assert!(pure.is_pure());
|
||||
assert!(!read.is_pure());
|
||||
assert!(!write.is_pure());
|
||||
|
||||
assert!(read.is_read_only());
|
||||
assert!(!write.is_read_only());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_effect_combination() {
|
||||
let mut effects = EffectMask::new();
|
||||
assert!(effects.is_pure());
|
||||
|
||||
effects = effects.add(Effect::ReadHeap);
|
||||
assert!(effects.contains(Effect::ReadHeap));
|
||||
assert!(effects.is_read_only());
|
||||
|
||||
effects = effects.add(Effect::WriteHeap);
|
||||
assert!(effects.contains(Effect::WriteHeap));
|
||||
assert!(!effects.is_read_only());
|
||||
|
||||
effects = effects.add(Effect::IO);
|
||||
assert!(effects.contains(Effect::IO));
|
||||
assert!(!effects.is_parallel_safe());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_effect_union() {
|
||||
let read_effect = EffectMask::READ;
|
||||
let io_effect = EffectMask::IO;
|
||||
|
||||
let combined = read_effect | io_effect;
|
||||
|
||||
assert!(combined.contains(Effect::ReadHeap));
|
||||
assert!(combined.contains(Effect::IO));
|
||||
assert!(!combined.is_pure());
|
||||
assert!(!combined.is_parallel_safe());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parallel_safety() {
|
||||
let pure = EffectMask::PURE;
|
||||
let read = EffectMask::READ;
|
||||
let write = EffectMask::WRITE;
|
||||
let io = EffectMask::IO;
|
||||
|
||||
assert!(pure.is_parallel_safe());
|
||||
assert!(read.is_parallel_safe());
|
||||
assert!(!write.is_parallel_safe());
|
||||
assert!(io.is_parallel_safe()); // I/O can be parallel if properly synchronized
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_effect_names() {
|
||||
let pure = EffectMask::PURE;
|
||||
assert_eq!(pure.effect_names(), vec!["pure"]);
|
||||
|
||||
let read_write = EffectMask::READ.add(Effect::WriteHeap);
|
||||
let names = read_write.effect_names();
|
||||
assert!(names.contains(&"read"));
|
||||
assert!(names.contains(&"write"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_effect_display() {
|
||||
let pure = EffectMask::PURE;
|
||||
assert_eq!(format!("{}", pure), "pure");
|
||||
|
||||
let read_io = EffectMask::READ | EffectMask::IO;
|
||||
let display = format!("{}", read_io);
|
||||
assert!(display.contains("read"));
|
||||
assert!(display.contains("io"));
|
||||
}
|
||||
}
|
||||
500
src/mir/function.rs
Normal file
500
src/mir/function.rs
Normal file
@ -0,0 +1,500 @@
|
||||
/*!
|
||||
* MIR Function and Module - High-level MIR organization
|
||||
*
|
||||
* Functions contain basic blocks and SSA values, modules contain functions
|
||||
*/
|
||||
|
||||
use super::{BasicBlock, BasicBlockId, ValueId, EffectMask, MirType};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
/// Function signature for MIR functions
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FunctionSignature {
|
||||
/// Function name
|
||||
pub name: String,
|
||||
|
||||
/// Parameter types
|
||||
pub params: Vec<MirType>,
|
||||
|
||||
/// Return type
|
||||
pub return_type: MirType,
|
||||
|
||||
/// Overall effect mask for the function
|
||||
pub effects: EffectMask,
|
||||
}
|
||||
|
||||
/// A MIR function in SSA form
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MirFunction {
|
||||
/// Function signature
|
||||
pub signature: FunctionSignature,
|
||||
|
||||
/// Basic blocks indexed by ID
|
||||
pub blocks: HashMap<BasicBlockId, BasicBlock>,
|
||||
|
||||
/// Entry basic block ID
|
||||
pub entry_block: BasicBlockId,
|
||||
|
||||
/// Local variable declarations (before SSA conversion)
|
||||
pub locals: Vec<MirType>,
|
||||
|
||||
/// Parameter value IDs
|
||||
pub params: Vec<ValueId>,
|
||||
|
||||
/// Next available value ID
|
||||
pub next_value_id: u32,
|
||||
|
||||
/// Function-level metadata
|
||||
pub metadata: FunctionMetadata,
|
||||
}
|
||||
|
||||
/// Metadata for MIR functions
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FunctionMetadata {
|
||||
/// Source file location
|
||||
pub source_file: Option<String>,
|
||||
|
||||
/// Line number in source
|
||||
pub line_number: Option<u32>,
|
||||
|
||||
/// Whether this function is an entry point
|
||||
pub is_entry_point: bool,
|
||||
|
||||
/// Whether this function is pure (no side effects)
|
||||
pub is_pure: bool,
|
||||
|
||||
/// Optimization hints
|
||||
pub optimization_hints: Vec<String>,
|
||||
}
|
||||
|
||||
impl MirFunction {
|
||||
/// Create a new MIR function
|
||||
pub fn new(signature: FunctionSignature, entry_block: BasicBlockId) -> Self {
|
||||
let mut blocks = HashMap::new();
|
||||
blocks.insert(entry_block, BasicBlock::new(entry_block));
|
||||
|
||||
Self {
|
||||
signature,
|
||||
blocks,
|
||||
entry_block,
|
||||
locals: Vec::new(),
|
||||
params: Vec::new(),
|
||||
next_value_id: 0,
|
||||
metadata: FunctionMetadata::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the next available ValueId
|
||||
pub fn next_value_id(&mut self) -> ValueId {
|
||||
let id = ValueId::new(self.next_value_id);
|
||||
self.next_value_id += 1;
|
||||
id
|
||||
}
|
||||
|
||||
/// Add a new basic block
|
||||
pub fn add_block(&mut self, block: BasicBlock) -> BasicBlockId {
|
||||
let id = block.id;
|
||||
self.blocks.insert(id, block);
|
||||
id
|
||||
}
|
||||
|
||||
/// Get a basic block by ID
|
||||
pub fn get_block(&self, id: BasicBlockId) -> Option<&BasicBlock> {
|
||||
self.blocks.get(&id)
|
||||
}
|
||||
|
||||
/// Get a mutable basic block by ID
|
||||
pub fn get_block_mut(&mut self, id: BasicBlockId) -> Option<&mut BasicBlock> {
|
||||
self.blocks.get_mut(&id)
|
||||
}
|
||||
|
||||
/// Get the entry block
|
||||
pub fn entry_block(&self) -> &BasicBlock {
|
||||
self.blocks.get(&self.entry_block)
|
||||
.expect("Entry block must exist")
|
||||
}
|
||||
|
||||
/// Get all basic block IDs in insertion order
|
||||
pub fn block_ids(&self) -> Vec<BasicBlockId> {
|
||||
let mut ids: Vec<_> = self.blocks.keys().copied().collect();
|
||||
ids.sort();
|
||||
ids
|
||||
}
|
||||
|
||||
/// Get all values defined in this function
|
||||
pub fn defined_values(&self) -> Vec<ValueId> {
|
||||
let mut values = Vec::new();
|
||||
values.extend(&self.params);
|
||||
|
||||
for block in self.blocks.values() {
|
||||
values.extend(block.defined_values());
|
||||
}
|
||||
|
||||
values
|
||||
}
|
||||
|
||||
/// Verify function integrity (basic checks)
|
||||
pub fn verify(&self) -> Result<(), String> {
|
||||
// Check entry block exists
|
||||
if !self.blocks.contains_key(&self.entry_block) {
|
||||
return Err("Entry block does not exist".to_string());
|
||||
}
|
||||
|
||||
// Check all blocks are reachable from entry
|
||||
let reachable = self.compute_reachable_blocks();
|
||||
for (id, block) in &self.blocks {
|
||||
if !reachable.contains(id) {
|
||||
eprintln!("Warning: Block {} is unreachable", id);
|
||||
}
|
||||
}
|
||||
|
||||
// Check terminator consistency
|
||||
for block in self.blocks.values() {
|
||||
if !block.is_terminated() && !block.is_empty() {
|
||||
return Err(format!("Block {} is not properly terminated", block.id));
|
||||
}
|
||||
|
||||
// Check successor/predecessor consistency
|
||||
for successor_id in &block.successors {
|
||||
if let Some(successor) = self.blocks.get(successor_id) {
|
||||
if !successor.predecessors.contains(&block.id) {
|
||||
return Err(format!(
|
||||
"Inconsistent CFG: {} -> {} but {} doesn't have {} as predecessor",
|
||||
block.id, successor_id, successor_id, block.id
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Block {} references non-existent successor {}",
|
||||
block.id, successor_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute reachable blocks from entry
|
||||
fn compute_reachable_blocks(&self) -> std::collections::HashSet<BasicBlockId> {
|
||||
let mut reachable = std::collections::HashSet::new();
|
||||
let mut worklist = vec![self.entry_block];
|
||||
|
||||
while let Some(current) = worklist.pop() {
|
||||
if reachable.insert(current) {
|
||||
if let Some(block) = self.blocks.get(¤t) {
|
||||
worklist.extend(block.successors.iter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reachable
|
||||
}
|
||||
|
||||
/// Update predecessor/successor relationships
|
||||
pub fn update_cfg(&mut self) {
|
||||
// Clear all predecessors
|
||||
for block in self.blocks.values_mut() {
|
||||
block.predecessors.clear();
|
||||
}
|
||||
|
||||
// Rebuild predecessors from successors
|
||||
let edges: Vec<(BasicBlockId, BasicBlockId)> = self.blocks.values()
|
||||
.flat_map(|block| {
|
||||
block.successors.iter().map(move |&succ| (block.id, succ))
|
||||
})
|
||||
.collect();
|
||||
|
||||
for (pred, succ) in edges {
|
||||
if let Some(successor_block) = self.blocks.get_mut(&succ) {
|
||||
successor_block.add_predecessor(pred);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark reachable blocks
|
||||
pub fn mark_reachable_blocks(&mut self) {
|
||||
let reachable = self.compute_reachable_blocks();
|
||||
for (id, block) in &mut self.blocks {
|
||||
if reachable.contains(id) {
|
||||
block.mark_reachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get function statistics
|
||||
pub fn stats(&self) -> FunctionStats {
|
||||
let instruction_count = self.blocks.values()
|
||||
.map(|block| block.instructions.len() + if block.terminator.is_some() { 1 } else { 0 })
|
||||
.sum();
|
||||
|
||||
let phi_count = self.blocks.values()
|
||||
.map(|block| block.phi_instructions().count())
|
||||
.sum();
|
||||
|
||||
FunctionStats {
|
||||
block_count: self.blocks.len(),
|
||||
instruction_count,
|
||||
phi_count,
|
||||
value_count: self.next_value_id as usize,
|
||||
is_pure: self.signature.effects.is_pure(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Function statistics for profiling and optimization
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FunctionStats {
|
||||
pub block_count: usize,
|
||||
pub instruction_count: usize,
|
||||
pub phi_count: usize,
|
||||
pub value_count: usize,
|
||||
pub is_pure: bool,
|
||||
}
|
||||
|
||||
/// A MIR module containing multiple functions
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MirModule {
|
||||
/// Module name
|
||||
pub name: String,
|
||||
|
||||
/// Functions in this module
|
||||
pub functions: HashMap<String, MirFunction>,
|
||||
|
||||
/// Global constants/statics
|
||||
pub globals: HashMap<String, super::ConstValue>,
|
||||
|
||||
/// Module metadata
|
||||
pub metadata: ModuleMetadata,
|
||||
}
|
||||
|
||||
/// Metadata for MIR modules
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ModuleMetadata {
|
||||
/// Source file this module was compiled from
|
||||
pub source_file: Option<String>,
|
||||
|
||||
/// Compilation timestamp
|
||||
pub compiled_at: Option<String>,
|
||||
|
||||
/// Compiler version
|
||||
pub compiler_version: Option<String>,
|
||||
|
||||
/// Optimization level used
|
||||
pub optimization_level: u32,
|
||||
}
|
||||
|
||||
impl MirModule {
|
||||
/// Create a new MIR module
|
||||
pub fn new(name: String) -> Self {
|
||||
Self {
|
||||
name,
|
||||
functions: HashMap::new(),
|
||||
globals: HashMap::new(),
|
||||
metadata: ModuleMetadata::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a function to the module
|
||||
pub fn add_function(&mut self, function: MirFunction) {
|
||||
let name = function.signature.name.clone();
|
||||
self.functions.insert(name, function);
|
||||
}
|
||||
|
||||
/// Get a function by name
|
||||
pub fn get_function(&self, name: &str) -> Option<&MirFunction> {
|
||||
self.functions.get(name)
|
||||
}
|
||||
|
||||
/// Get a mutable function by name
|
||||
pub fn get_function_mut(&mut self, name: &str) -> Option<&mut MirFunction> {
|
||||
self.functions.get_mut(name)
|
||||
}
|
||||
|
||||
/// Get all function names
|
||||
pub fn function_names(&self) -> Vec<&String> {
|
||||
self.functions.keys().collect()
|
||||
}
|
||||
|
||||
/// Add a global constant
|
||||
pub fn add_global(&mut self, name: String, value: super::ConstValue) {
|
||||
self.globals.insert(name, value);
|
||||
}
|
||||
|
||||
/// Verify entire module
|
||||
pub fn verify(&self) -> Result<(), Vec<String>> {
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for (name, function) in &self.functions {
|
||||
if let Err(e) = function.verify() {
|
||||
errors.push(format!("Function '{}': {}", name, e));
|
||||
}
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get module statistics
|
||||
pub fn stats(&self) -> ModuleStats {
|
||||
let function_stats: Vec<_> = self.functions.values()
|
||||
.map(|f| f.stats())
|
||||
.collect();
|
||||
|
||||
ModuleStats {
|
||||
function_count: self.functions.len(),
|
||||
global_count: self.globals.len(),
|
||||
total_blocks: function_stats.iter().map(|s| s.block_count).sum(),
|
||||
total_instructions: function_stats.iter().map(|s| s.instruction_count).sum(),
|
||||
total_values: function_stats.iter().map(|s| s.value_count).sum(),
|
||||
pure_functions: function_stats.iter().filter(|s| s.is_pure).count(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Module statistics
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModuleStats {
|
||||
pub function_count: usize,
|
||||
pub global_count: usize,
|
||||
pub total_blocks: usize,
|
||||
pub total_instructions: usize,
|
||||
pub total_values: usize,
|
||||
pub pure_functions: usize,
|
||||
}
|
||||
|
||||
impl fmt::Display for MirFunction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "function {}({}) -> {} {{",
|
||||
self.signature.name,
|
||||
self.signature.params.iter()
|
||||
.enumerate()
|
||||
.map(|(i, ty)| format!("%{}: {:?}", i, ty))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
format!("{:?}", self.signature.return_type))?;
|
||||
|
||||
// Show effects if not pure
|
||||
if !self.signature.effects.is_pure() {
|
||||
writeln!(f, " ; effects: {}", self.signature.effects)?;
|
||||
}
|
||||
|
||||
// Show blocks in order
|
||||
let mut block_ids: Vec<_> = self.blocks.keys().copied().collect();
|
||||
block_ids.sort();
|
||||
|
||||
for block_id in block_ids {
|
||||
if let Some(block) = self.blocks.get(&block_id) {
|
||||
write!(f, "{}", block)?;
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(f, "}}")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MirModule {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "module {} {{", self.name)?;
|
||||
|
||||
// Show globals
|
||||
if !self.globals.is_empty() {
|
||||
writeln!(f, " ; globals:")?;
|
||||
for (name, value) in &self.globals {
|
||||
writeln!(f, " global {} = {}", name, value)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
// Show functions
|
||||
for function in self.functions.values() {
|
||||
writeln!(f, "{}", function)?;
|
||||
}
|
||||
|
||||
writeln!(f, "}}")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::{MirType, EffectMask};
|
||||
|
||||
#[test]
|
||||
fn test_function_creation() {
|
||||
let signature = FunctionSignature {
|
||||
name: "test_func".to_string(),
|
||||
params: vec![MirType::Integer, MirType::Float],
|
||||
return_type: MirType::Integer,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
|
||||
let entry_block = BasicBlockId::new(0);
|
||||
let function = MirFunction::new(signature.clone(), entry_block);
|
||||
|
||||
assert_eq!(function.signature.name, "test_func");
|
||||
assert_eq!(function.entry_block, entry_block);
|
||||
assert!(function.blocks.contains_key(&entry_block));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_module_creation() {
|
||||
let mut module = MirModule::new("test_module".to_string());
|
||||
|
||||
let signature = FunctionSignature {
|
||||
name: "main".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
|
||||
let function = MirFunction::new(signature, BasicBlockId::new(0));
|
||||
module.add_function(function);
|
||||
|
||||
assert_eq!(module.name, "test_module");
|
||||
assert!(module.get_function("main").is_some());
|
||||
assert_eq!(module.function_names().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_id_generation() {
|
||||
let signature = FunctionSignature {
|
||||
name: "test".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
|
||||
let mut function = MirFunction::new(signature, BasicBlockId::new(0));
|
||||
|
||||
let val1 = function.next_value_id();
|
||||
let val2 = function.next_value_id();
|
||||
let val3 = function.next_value_id();
|
||||
|
||||
assert_eq!(val1, ValueId::new(0));
|
||||
assert_eq!(val2, ValueId::new(1));
|
||||
assert_eq!(val3, ValueId::new(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_stats() {
|
||||
let signature = FunctionSignature {
|
||||
name: "test".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
|
||||
let function = MirFunction::new(signature, BasicBlockId::new(0));
|
||||
let stats = function.stats();
|
||||
|
||||
assert_eq!(stats.block_count, 1);
|
||||
assert_eq!(stats.instruction_count, 0);
|
||||
assert_eq!(stats.value_count, 0);
|
||||
assert!(stats.is_pure);
|
||||
}
|
||||
}
|
||||
492
src/mir/instruction.rs
Normal file
492
src/mir/instruction.rs
Normal file
@ -0,0 +1,492 @@
|
||||
/*!
|
||||
* MIR Instruction Set - 20 Core Instructions per ChatGPT5 Design
|
||||
*
|
||||
* SSA-form instructions with effect tracking for optimization
|
||||
*/
|
||||
|
||||
use super::{ValueId, LocalId, EffectMask, Effect};
|
||||
use crate::value::NyashValue;
|
||||
use std::fmt;
|
||||
|
||||
/// MIR instruction types - limited to 20 core instructions
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum MirInstruction {
|
||||
// === Constants and Values ===
|
||||
/// Load a constant value
|
||||
/// `%dst = const value`
|
||||
Const {
|
||||
dst: ValueId,
|
||||
value: ConstValue,
|
||||
},
|
||||
|
||||
// === Arithmetic Operations ===
|
||||
/// Binary arithmetic operation
|
||||
/// `%dst = %lhs op %rhs`
|
||||
BinOp {
|
||||
dst: ValueId,
|
||||
op: BinaryOp,
|
||||
lhs: ValueId,
|
||||
rhs: ValueId,
|
||||
},
|
||||
|
||||
/// Unary operation
|
||||
/// `%dst = op %operand`
|
||||
UnaryOp {
|
||||
dst: ValueId,
|
||||
op: UnaryOp,
|
||||
operand: ValueId,
|
||||
},
|
||||
|
||||
// === Comparison Operations ===
|
||||
/// Compare two values
|
||||
/// `%dst = %lhs cmp %rhs`
|
||||
Compare {
|
||||
dst: ValueId,
|
||||
op: CompareOp,
|
||||
lhs: ValueId,
|
||||
rhs: ValueId,
|
||||
},
|
||||
|
||||
// === Memory Operations ===
|
||||
/// Load from memory/variable
|
||||
/// `%dst = load %ptr`
|
||||
Load {
|
||||
dst: ValueId,
|
||||
ptr: ValueId,
|
||||
},
|
||||
|
||||
/// Store to memory/variable
|
||||
/// `store %value -> %ptr`
|
||||
Store {
|
||||
value: ValueId,
|
||||
ptr: ValueId,
|
||||
},
|
||||
|
||||
// === Function Calls ===
|
||||
/// Call a function
|
||||
/// `%dst = call %func(%args...)`
|
||||
Call {
|
||||
dst: Option<ValueId>,
|
||||
func: ValueId,
|
||||
args: Vec<ValueId>,
|
||||
effects: EffectMask,
|
||||
},
|
||||
|
||||
/// Box method invocation
|
||||
/// `%dst = invoke %box.method(%args...)`
|
||||
BoxCall {
|
||||
dst: Option<ValueId>,
|
||||
box_val: ValueId,
|
||||
method: String,
|
||||
args: Vec<ValueId>,
|
||||
effects: EffectMask,
|
||||
},
|
||||
|
||||
// === Control Flow ===
|
||||
/// Conditional branch
|
||||
/// `br %condition -> %then_bb, %else_bb`
|
||||
Branch {
|
||||
condition: ValueId,
|
||||
then_bb: super::BasicBlockId,
|
||||
else_bb: super::BasicBlockId,
|
||||
},
|
||||
|
||||
/// Unconditional jump
|
||||
/// `jmp %target_bb`
|
||||
Jump {
|
||||
target: super::BasicBlockId,
|
||||
},
|
||||
|
||||
/// Return from function
|
||||
/// `ret %value` or `ret void`
|
||||
Return {
|
||||
value: Option<ValueId>,
|
||||
},
|
||||
|
||||
// === SSA Phi Function ===
|
||||
/// SSA phi function for merging values from different paths
|
||||
/// `%dst = phi [%val1 from %bb1, %val2 from %bb2, ...]`
|
||||
Phi {
|
||||
dst: ValueId,
|
||||
inputs: Vec<(super::BasicBlockId, ValueId)>,
|
||||
},
|
||||
|
||||
// === Box Operations ===
|
||||
/// Create a new Box instance
|
||||
/// `%dst = new_box "BoxType"(%args...)`
|
||||
NewBox {
|
||||
dst: ValueId,
|
||||
box_type: String,
|
||||
args: Vec<ValueId>,
|
||||
},
|
||||
|
||||
/// Check Box type
|
||||
/// `%dst = type_check %box "BoxType"`
|
||||
TypeCheck {
|
||||
dst: ValueId,
|
||||
value: ValueId,
|
||||
expected_type: String,
|
||||
},
|
||||
|
||||
// === Type Conversion ===
|
||||
/// Convert between types
|
||||
/// `%dst = cast %value as Type`
|
||||
Cast {
|
||||
dst: ValueId,
|
||||
value: ValueId,
|
||||
target_type: MirType,
|
||||
},
|
||||
|
||||
// === Array Operations ===
|
||||
/// Get array element
|
||||
/// `%dst = %array[%index]`
|
||||
ArrayGet {
|
||||
dst: ValueId,
|
||||
array: ValueId,
|
||||
index: ValueId,
|
||||
},
|
||||
|
||||
/// Set array element
|
||||
/// `%array[%index] = %value`
|
||||
ArraySet {
|
||||
array: ValueId,
|
||||
index: ValueId,
|
||||
value: ValueId,
|
||||
},
|
||||
|
||||
// === Special Operations ===
|
||||
/// Copy a value (for optimization passes)
|
||||
/// `%dst = copy %src`
|
||||
Copy {
|
||||
dst: ValueId,
|
||||
src: ValueId,
|
||||
},
|
||||
|
||||
/// Debug/introspection instruction
|
||||
/// `debug %value "message"`
|
||||
Debug {
|
||||
value: ValueId,
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// No-op instruction (for optimization placeholders)
|
||||
Nop,
|
||||
}
|
||||
|
||||
/// Constant values in MIR
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ConstValue {
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
String(String),
|
||||
Null,
|
||||
Void,
|
||||
}
|
||||
|
||||
/// Binary operations
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BinaryOp {
|
||||
// Arithmetic
|
||||
Add, Sub, Mul, Div, Mod,
|
||||
|
||||
// Bitwise
|
||||
BitAnd, BitOr, BitXor, Shl, Shr,
|
||||
|
||||
// Logical
|
||||
And, Or,
|
||||
}
|
||||
|
||||
/// Unary operations
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum UnaryOp {
|
||||
// Arithmetic
|
||||
Neg,
|
||||
|
||||
// Logical
|
||||
Not,
|
||||
|
||||
// Bitwise
|
||||
BitNot,
|
||||
}
|
||||
|
||||
/// Comparison operations
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CompareOp {
|
||||
Eq, Ne, Lt, Le, Gt, Ge,
|
||||
}
|
||||
|
||||
/// MIR type system
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MirType {
|
||||
Integer,
|
||||
Float,
|
||||
Bool,
|
||||
String,
|
||||
Box(String), // Box type with name
|
||||
Array(Box<MirType>),
|
||||
Void,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl MirInstruction {
|
||||
/// Get the effect mask for this instruction
|
||||
pub fn effects(&self) -> EffectMask {
|
||||
match self {
|
||||
// Pure operations
|
||||
MirInstruction::Const { .. } |
|
||||
MirInstruction::BinOp { .. } |
|
||||
MirInstruction::UnaryOp { .. } |
|
||||
MirInstruction::Compare { .. } |
|
||||
MirInstruction::Cast { .. } |
|
||||
MirInstruction::Copy { .. } |
|
||||
MirInstruction::Phi { .. } |
|
||||
MirInstruction::TypeCheck { .. } |
|
||||
MirInstruction::Nop => EffectMask::PURE,
|
||||
|
||||
// Memory operations
|
||||
MirInstruction::Load { .. } => EffectMask::READ,
|
||||
MirInstruction::Store { .. } |
|
||||
MirInstruction::ArraySet { .. } => EffectMask::WRITE,
|
||||
MirInstruction::ArrayGet { .. } => EffectMask::READ,
|
||||
|
||||
// Function calls use provided effect mask
|
||||
MirInstruction::Call { effects, .. } |
|
||||
MirInstruction::BoxCall { effects, .. } => *effects,
|
||||
|
||||
// Control flow (pure but affects execution)
|
||||
MirInstruction::Branch { .. } |
|
||||
MirInstruction::Jump { .. } |
|
||||
MirInstruction::Return { .. } => EffectMask::PURE,
|
||||
|
||||
// Box creation may allocate
|
||||
MirInstruction::NewBox { .. } => EffectMask::PURE.add(Effect::Alloc),
|
||||
|
||||
// Debug has debug effect
|
||||
MirInstruction::Debug { .. } => EffectMask::PURE.add(Effect::Debug),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the destination ValueId if this instruction produces a value
|
||||
pub fn dst_value(&self) -> Option<ValueId> {
|
||||
match self {
|
||||
MirInstruction::Const { dst, .. } |
|
||||
MirInstruction::BinOp { dst, .. } |
|
||||
MirInstruction::UnaryOp { dst, .. } |
|
||||
MirInstruction::Compare { dst, .. } |
|
||||
MirInstruction::Load { dst, .. } |
|
||||
MirInstruction::Phi { dst, .. } |
|
||||
MirInstruction::NewBox { dst, .. } |
|
||||
MirInstruction::TypeCheck { dst, .. } |
|
||||
MirInstruction::Cast { dst, .. } |
|
||||
MirInstruction::ArrayGet { dst, .. } |
|
||||
MirInstruction::Copy { dst, .. } => Some(*dst),
|
||||
|
||||
MirInstruction::Call { dst, .. } |
|
||||
MirInstruction::BoxCall { dst, .. } => *dst,
|
||||
|
||||
MirInstruction::Store { .. } |
|
||||
MirInstruction::Branch { .. } |
|
||||
MirInstruction::Jump { .. } |
|
||||
MirInstruction::Return { .. } |
|
||||
MirInstruction::ArraySet { .. } |
|
||||
MirInstruction::Debug { .. } |
|
||||
MirInstruction::Nop => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all ValueIds used by this instruction
|
||||
pub fn used_values(&self) -> Vec<ValueId> {
|
||||
match self {
|
||||
MirInstruction::Const { .. } |
|
||||
MirInstruction::Jump { .. } |
|
||||
MirInstruction::Nop => Vec::new(),
|
||||
|
||||
MirInstruction::UnaryOp { operand, .. } |
|
||||
MirInstruction::Load { ptr: operand, .. } |
|
||||
MirInstruction::TypeCheck { value: operand, .. } |
|
||||
MirInstruction::Cast { value: operand, .. } |
|
||||
MirInstruction::Copy { src: operand, .. } |
|
||||
MirInstruction::Debug { value: operand, .. } => vec![*operand],
|
||||
|
||||
MirInstruction::BinOp { lhs, rhs, .. } |
|
||||
MirInstruction::Compare { lhs, rhs, .. } |
|
||||
MirInstruction::Store { value: lhs, ptr: rhs, .. } => vec![*lhs, *rhs],
|
||||
|
||||
MirInstruction::ArrayGet { array, index, .. } => vec![*array, *index],
|
||||
|
||||
MirInstruction::ArraySet { array, index, value } => vec![*array, *index, *value],
|
||||
|
||||
MirInstruction::Branch { condition, .. } => vec![*condition],
|
||||
|
||||
MirInstruction::Return { value } => {
|
||||
value.map(|v| vec![v]).unwrap_or_default()
|
||||
},
|
||||
|
||||
MirInstruction::Call { func, args, .. } => {
|
||||
let mut used = vec![*func];
|
||||
used.extend(args);
|
||||
used
|
||||
},
|
||||
|
||||
MirInstruction::BoxCall { box_val, args, .. } => {
|
||||
let mut used = vec![*box_val];
|
||||
used.extend(args);
|
||||
used
|
||||
},
|
||||
|
||||
MirInstruction::NewBox { args, .. } => args.clone(),
|
||||
|
||||
MirInstruction::Phi { inputs, .. } => {
|
||||
inputs.iter().map(|(_, value)| *value).collect()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstValue {
|
||||
/// Convert to NyashValue
|
||||
pub fn to_nyash_value(&self) -> NyashValue {
|
||||
match self {
|
||||
ConstValue::Integer(n) => NyashValue::new_integer(*n),
|
||||
ConstValue::Float(f) => NyashValue::new_float(*f),
|
||||
ConstValue::Bool(b) => NyashValue::new_bool(*b),
|
||||
ConstValue::String(s) => NyashValue::new_string(s.clone()),
|
||||
ConstValue::Null => NyashValue::new_null(),
|
||||
ConstValue::Void => NyashValue::new_void(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create from NyashValue
|
||||
pub fn from_nyash_value(value: &NyashValue) -> Option<Self> {
|
||||
match value {
|
||||
NyashValue::Integer(n) => Some(ConstValue::Integer(*n)),
|
||||
NyashValue::Float(f) => Some(ConstValue::Float(*f)),
|
||||
NyashValue::Bool(b) => Some(ConstValue::Bool(*b)),
|
||||
NyashValue::String(s) => Some(ConstValue::String(s.clone())),
|
||||
NyashValue::Null => Some(ConstValue::Null),
|
||||
NyashValue::Void => Some(ConstValue::Void),
|
||||
_ => None, // Collections and Boxes can't be constants
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MirInstruction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
MirInstruction::Const { dst, value } => {
|
||||
write!(f, "{} = const {}", dst, value)
|
||||
},
|
||||
MirInstruction::BinOp { dst, op, lhs, rhs } => {
|
||||
write!(f, "{} = {} {:?} {}", dst, lhs, op, rhs)
|
||||
},
|
||||
MirInstruction::UnaryOp { dst, op, operand } => {
|
||||
write!(f, "{} = {:?} {}", dst, op, operand)
|
||||
},
|
||||
MirInstruction::Compare { dst, op, lhs, rhs } => {
|
||||
write!(f, "{} = {} {:?} {}", dst, lhs, op, rhs)
|
||||
},
|
||||
MirInstruction::Load { dst, ptr } => {
|
||||
write!(f, "{} = load {}", dst, ptr)
|
||||
},
|
||||
MirInstruction::Store { value, ptr } => {
|
||||
write!(f, "store {} -> {}", value, ptr)
|
||||
},
|
||||
MirInstruction::Call { dst, func, args, effects } => {
|
||||
if let Some(dst) = dst {
|
||||
write!(f, "{} = call {}({}); effects: {}", dst, func,
|
||||
args.iter().map(|v| format!("{}", v)).collect::<Vec<_>>().join(", "),
|
||||
effects)
|
||||
} else {
|
||||
write!(f, "call {}({}); effects: {}", func,
|
||||
args.iter().map(|v| format!("{}", v)).collect::<Vec<_>>().join(", "),
|
||||
effects)
|
||||
}
|
||||
},
|
||||
MirInstruction::Return { value } => {
|
||||
if let Some(value) = value {
|
||||
write!(f, "ret {}", value)
|
||||
} else {
|
||||
write!(f, "ret void")
|
||||
}
|
||||
},
|
||||
_ => write!(f, "{:?}", self), // Fallback for other instructions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConstValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ConstValue::Integer(n) => write!(f, "{}", n),
|
||||
ConstValue::Float(fl) => write!(f, "{}", fl),
|
||||
ConstValue::Bool(b) => write!(f, "{}", b),
|
||||
ConstValue::String(s) => write!(f, "\"{}\"", s),
|
||||
ConstValue::Null => write!(f, "null"),
|
||||
ConstValue::Void => write!(f, "void"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_const_instruction() {
|
||||
let dst = ValueId::new(0);
|
||||
let inst = MirInstruction::Const {
|
||||
dst,
|
||||
value: ConstValue::Integer(42),
|
||||
};
|
||||
|
||||
assert_eq!(inst.dst_value(), Some(dst));
|
||||
assert!(inst.used_values().is_empty());
|
||||
assert!(inst.effects().is_pure());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binop_instruction() {
|
||||
let dst = ValueId::new(0);
|
||||
let lhs = ValueId::new(1);
|
||||
let rhs = ValueId::new(2);
|
||||
|
||||
let inst = MirInstruction::BinOp {
|
||||
dst, op: BinaryOp::Add, lhs, rhs
|
||||
};
|
||||
|
||||
assert_eq!(inst.dst_value(), Some(dst));
|
||||
assert_eq!(inst.used_values(), vec![lhs, rhs]);
|
||||
assert!(inst.effects().is_pure());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_instruction() {
|
||||
let dst = ValueId::new(0);
|
||||
let func = ValueId::new(1);
|
||||
let arg1 = ValueId::new(2);
|
||||
let arg2 = ValueId::new(3);
|
||||
|
||||
let inst = MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func,
|
||||
args: vec![arg1, arg2],
|
||||
effects: EffectMask::IO,
|
||||
};
|
||||
|
||||
assert_eq!(inst.dst_value(), Some(dst));
|
||||
assert_eq!(inst.used_values(), vec![func, arg1, arg2]);
|
||||
assert_eq!(inst.effects(), EffectMask::IO);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_const_value_conversion() {
|
||||
let const_val = ConstValue::Integer(42);
|
||||
let nyash_val = const_val.to_nyash_value();
|
||||
|
||||
assert_eq!(nyash_val, NyashValue::new_integer(42));
|
||||
|
||||
let back = ConstValue::from_nyash_value(&nyash_val).unwrap();
|
||||
assert_eq!(back, const_val);
|
||||
}
|
||||
}
|
||||
113
src/mir/mod.rs
Normal file
113
src/mir/mod.rs
Normal file
@ -0,0 +1,113 @@
|
||||
/*!
|
||||
* Nyash MIR (Mid-level Intermediate Representation) - Stage 1 Implementation
|
||||
*
|
||||
* ChatGPT5-designed MIR infrastructure for native compilation support
|
||||
* Based on SSA form with effect tracking and Box-aware optimizations
|
||||
*/
|
||||
|
||||
pub mod instruction;
|
||||
pub mod basic_block;
|
||||
pub mod function;
|
||||
pub mod builder;
|
||||
pub mod verification;
|
||||
pub mod printer;
|
||||
pub mod value_id;
|
||||
pub mod effect;
|
||||
|
||||
// Re-export main types for easy access
|
||||
pub use instruction::{MirInstruction, BinaryOp, CompareOp, UnaryOp, ConstValue, MirType};
|
||||
pub use basic_block::{BasicBlock, BasicBlockId, BasicBlockIdGenerator};
|
||||
pub use function::{MirFunction, MirModule, FunctionSignature};
|
||||
pub use builder::MirBuilder;
|
||||
pub use verification::{MirVerifier, VerificationError};
|
||||
pub use printer::MirPrinter;
|
||||
pub use value_id::{ValueId, LocalId, ValueIdGenerator};
|
||||
pub use effect::{EffectMask, Effect};
|
||||
|
||||
/// MIR compilation result
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MirCompileResult {
|
||||
pub module: MirModule,
|
||||
pub verification_result: Result<(), Vec<VerificationError>>,
|
||||
}
|
||||
|
||||
/// MIR compiler - converts AST to MIR/SSA form
|
||||
pub struct MirCompiler {
|
||||
builder: MirBuilder,
|
||||
verifier: MirVerifier,
|
||||
}
|
||||
|
||||
impl MirCompiler {
|
||||
/// Create a new MIR compiler
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
builder: MirBuilder::new(),
|
||||
verifier: MirVerifier::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile AST to MIR module with verification
|
||||
pub fn compile(&mut self, ast: crate::ast::ASTNode) -> Result<MirCompileResult, String> {
|
||||
// Convert AST to MIR using builder
|
||||
let module = self.builder.build_module(ast)?;
|
||||
|
||||
// Verify the generated MIR
|
||||
let verification_result = self.verifier.verify_module(&module);
|
||||
|
||||
Ok(MirCompileResult {
|
||||
module,
|
||||
verification_result,
|
||||
})
|
||||
}
|
||||
|
||||
/// Dump MIR to string for debugging
|
||||
pub fn dump_mir(&self, module: &MirModule) -> String {
|
||||
MirPrinter::new().print_module(module)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MirCompiler {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{ASTNode, LiteralValue};
|
||||
|
||||
#[test]
|
||||
fn test_basic_mir_compilation() {
|
||||
let mut compiler = MirCompiler::new();
|
||||
|
||||
// Create a simple literal AST node
|
||||
let ast = ASTNode::Literal {
|
||||
value: LiteralValue::Integer(42),
|
||||
span: crate::ast::Span::unknown()
|
||||
};
|
||||
|
||||
// Compile to MIR
|
||||
let result = compiler.compile(ast);
|
||||
assert!(result.is_ok(), "Basic MIR compilation should succeed");
|
||||
|
||||
let compile_result = result.unwrap();
|
||||
assert!(!compile_result.module.functions.is_empty(), "Module should contain at least one function");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mir_dump() {
|
||||
let mut compiler = MirCompiler::new();
|
||||
|
||||
let ast = ASTNode::Literal {
|
||||
value: LiteralValue::Integer(42),
|
||||
span: crate::ast::Span::unknown()
|
||||
};
|
||||
|
||||
let result = compiler.compile(ast).unwrap();
|
||||
let mir_dump = compiler.dump_mir(&result.module);
|
||||
|
||||
assert!(!mir_dump.is_empty(), "MIR dump should not be empty");
|
||||
assert!(mir_dump.contains("function"), "MIR dump should contain function information");
|
||||
}
|
||||
}
|
||||
367
src/mir/printer.rs
Normal file
367
src/mir/printer.rs
Normal file
@ -0,0 +1,367 @@
|
||||
/*!
|
||||
* MIR Printer - Debug output and visualization
|
||||
*
|
||||
* Implements pretty-printing for MIR modules and functions
|
||||
*/
|
||||
|
||||
use super::{MirModule, MirFunction, BasicBlock, MirInstruction};
|
||||
use std::fmt::Write;
|
||||
|
||||
/// MIR printer for debug output and visualization
|
||||
pub struct MirPrinter {
|
||||
/// Indentation level
|
||||
indent_level: usize,
|
||||
|
||||
/// Whether to show detailed information
|
||||
verbose: bool,
|
||||
|
||||
/// Whether to show line numbers
|
||||
show_line_numbers: bool,
|
||||
}
|
||||
|
||||
impl MirPrinter {
|
||||
/// Create a new MIR printer with default settings
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
indent_level: 0,
|
||||
verbose: false,
|
||||
show_line_numbers: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a verbose MIR printer
|
||||
pub fn verbose() -> Self {
|
||||
Self {
|
||||
indent_level: 0,
|
||||
verbose: true,
|
||||
show_line_numbers: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set verbose mode
|
||||
pub fn set_verbose(&mut self, verbose: bool) -> &mut Self {
|
||||
self.verbose = verbose;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set line number display
|
||||
pub fn set_show_line_numbers(&mut self, show: bool) -> &mut Self {
|
||||
self.show_line_numbers = show;
|
||||
self
|
||||
}
|
||||
|
||||
/// Print a complete MIR module
|
||||
pub fn print_module(&self, module: &MirModule) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
// 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();
|
||||
|
||||
// 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();
|
||||
writeln!(output, "; Total Instructions: {}", stats.total_instructions).unwrap();
|
||||
writeln!(output, "; Pure Functions: {}", stats.pure_functions).unwrap();
|
||||
writeln!(output).unwrap();
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Functions
|
||||
for (name, function) in &module.functions {
|
||||
output.push_str(&self.print_function(function));
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Print a single MIR function
|
||||
pub fn print_function(&self, function: &MirFunction) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
// Function signature
|
||||
write!(output, "define {} @{}(",
|
||||
self.format_type(&function.signature.return_type),
|
||||
function.signature.name).unwrap();
|
||||
|
||||
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();
|
||||
|
||||
// Effects
|
||||
if !function.signature.effects.is_pure() {
|
||||
write!(output, " effects({})", function.signature.effects).unwrap();
|
||||
}
|
||||
|
||||
writeln!(output, " {{").unwrap();
|
||||
|
||||
// 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();
|
||||
}
|
||||
writeln!(output).unwrap();
|
||||
}
|
||||
|
||||
// Print blocks in order
|
||||
let mut block_ids: Vec<_> = function.blocks.keys().copied().collect();
|
||||
block_ids.sort();
|
||||
|
||||
for (i, block_id) in block_ids.iter().enumerate() {
|
||||
if let Some(block) = function.blocks.get(block_id) {
|
||||
if i > 0 {
|
||||
writeln!(output).unwrap();
|
||||
}
|
||||
output.push_str(&self.print_basic_block(block));
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(output, "}}").unwrap();
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Print a basic block
|
||||
pub fn print_basic_block(&self, block: &BasicBlock) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
// Block header
|
||||
write!(output, "{}:", block.id).unwrap();
|
||||
|
||||
// Predecessors
|
||||
if !block.predecessors.is_empty() && self.verbose {
|
||||
let preds: Vec<String> = block.predecessors.iter()
|
||||
.map(|p| format!("{}", p))
|
||||
.collect();
|
||||
write!(output, " ; preds({})", preds.join(", ")).unwrap();
|
||||
}
|
||||
|
||||
writeln!(output).unwrap();
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
writeln!(output, "{}", self.format_instruction(instruction)).unwrap();
|
||||
line_num += 1;
|
||||
}
|
||||
|
||||
// Block effects (if verbose and not pure)
|
||||
if self.verbose && !block.effects.is_pure() {
|
||||
writeln!(output, " ; effects: {}", block.effects).unwrap();
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Format a single instruction
|
||||
fn format_instruction(&self, instruction: &MirInstruction) -> String {
|
||||
match instruction {
|
||||
MirInstruction::Const { dst, value } => {
|
||||
format!("{} = const {}", dst, value)
|
||||
},
|
||||
|
||||
MirInstruction::BinOp { dst, op, lhs, rhs } => {
|
||||
format!("{} = {} {:?} {}", dst, lhs, op, rhs)
|
||||
},
|
||||
|
||||
MirInstruction::UnaryOp { dst, op, operand } => {
|
||||
format!("{} = {:?} {}", dst, op, operand)
|
||||
},
|
||||
|
||||
MirInstruction::Compare { dst, op, lhs, rhs } => {
|
||||
format!("{} = icmp {:?} {}, {}", dst, op, lhs, rhs)
|
||||
},
|
||||
|
||||
MirInstruction::Load { dst, ptr } => {
|
||||
format!("{} = load {}", dst, ptr)
|
||||
},
|
||||
|
||||
MirInstruction::Store { value, ptr } => {
|
||||
format!("store {} -> {}", value, ptr)
|
||||
},
|
||||
|
||||
MirInstruction::Call { dst, func, args, effects } => {
|
||||
let args_str = args.iter()
|
||||
.map(|v| format!("{}", v))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
if let Some(dst) = dst {
|
||||
format!("{} = call {}({})", dst, func, args_str)
|
||||
} else {
|
||||
format!("call {}({})", func, args_str)
|
||||
}
|
||||
},
|
||||
|
||||
MirInstruction::BoxCall { dst, box_val, method, args, effects } => {
|
||||
let args_str = args.iter()
|
||||
.map(|v| format!("{}", v))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
if let Some(dst) = dst {
|
||||
format!("{} = call {}.{}({})", dst, box_val, method, args_str)
|
||||
} else {
|
||||
format!("call {}.{}({})", box_val, method, args_str)
|
||||
}
|
||||
},
|
||||
|
||||
MirInstruction::Branch { condition, then_bb, else_bb } => {
|
||||
format!("br {}, label {}, label {}", condition, then_bb, else_bb)
|
||||
},
|
||||
|
||||
MirInstruction::Jump { target } => {
|
||||
format!("br label {}", target)
|
||||
},
|
||||
|
||||
MirInstruction::Return { value } => {
|
||||
if let Some(value) = value {
|
||||
format!("ret {}", value)
|
||||
} else {
|
||||
"ret void".to_string()
|
||||
}
|
||||
},
|
||||
|
||||
MirInstruction::Phi { dst, inputs } => {
|
||||
let inputs_str = inputs.iter()
|
||||
.map(|(bb, val)| format!("[{}, {}]", val, bb))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
format!("{} = phi {}", dst, inputs_str)
|
||||
},
|
||||
|
||||
MirInstruction::NewBox { dst, box_type, args } => {
|
||||
let args_str = args.iter()
|
||||
.map(|v| format!("{}", v))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
format!("{} = new {}({})", dst, box_type, args_str)
|
||||
},
|
||||
|
||||
MirInstruction::TypeCheck { dst, value, expected_type } => {
|
||||
format!("{} = type_check {} is {}", dst, value, expected_type)
|
||||
},
|
||||
|
||||
MirInstruction::Cast { dst, value, target_type } => {
|
||||
format!("{} = cast {} to {:?}", dst, value, target_type)
|
||||
},
|
||||
|
||||
MirInstruction::ArrayGet { dst, array, index } => {
|
||||
format!("{} = {}[{}]", dst, array, index)
|
||||
},
|
||||
|
||||
MirInstruction::ArraySet { array, index, value } => {
|
||||
format!("{}[{}] = {}", array, index, value)
|
||||
},
|
||||
|
||||
MirInstruction::Copy { dst, src } => {
|
||||
format!("{} = copy {}", dst, src)
|
||||
},
|
||||
|
||||
MirInstruction::Debug { value, message } => {
|
||||
format!("debug {} \"{}\"", value, message)
|
||||
},
|
||||
|
||||
MirInstruction::Nop => {
|
||||
"nop".to_string()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)),
|
||||
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::*;
|
||||
use crate::mir::{MirModule, MirFunction, FunctionSignature, MirType, EffectMask, BasicBlockId};
|
||||
|
||||
#[test]
|
||||
fn test_empty_module_printing() {
|
||||
let module = MirModule::new("test".to_string());
|
||||
let printer = MirPrinter::new();
|
||||
|
||||
let output = printer.print_module(&module);
|
||||
|
||||
assert!(output.contains("MIR Module: test"));
|
||||
assert!(!output.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_printing() {
|
||||
let signature = FunctionSignature {
|
||||
name: "test_func".to_string(),
|
||||
params: vec![MirType::Integer],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
|
||||
let function = MirFunction::new(signature, BasicBlockId::new(0));
|
||||
let printer = MirPrinter::new();
|
||||
|
||||
let output = printer.print_function(&function);
|
||||
|
||||
assert!(output.contains("define void @test_func(i64 %0)"));
|
||||
assert!(output.contains("bb0:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verbose_printing() {
|
||||
let module = MirModule::new("test".to_string());
|
||||
let printer = MirPrinter::verbose();
|
||||
|
||||
let output = printer.print_module(&module);
|
||||
|
||||
assert!(output.contains("Module Statistics"));
|
||||
}
|
||||
}
|
||||
197
src/mir/value_id.rs
Normal file
197
src/mir/value_id.rs
Normal file
@ -0,0 +1,197 @@
|
||||
/*!
|
||||
* MIR Value ID System - SSA value tracking
|
||||
*
|
||||
* Implements unique identifiers for SSA values with type safety
|
||||
*/
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Unique identifier for SSA values within a function
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ValueId(pub u32);
|
||||
|
||||
impl ValueId {
|
||||
/// Create a new ValueId
|
||||
pub fn new(id: u32) -> Self {
|
||||
ValueId(id)
|
||||
}
|
||||
|
||||
/// Get the raw ID value
|
||||
pub fn as_u32(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Create ValueId from usize (for array indexing)
|
||||
pub fn from_usize(id: usize) -> Self {
|
||||
ValueId(id as u32)
|
||||
}
|
||||
|
||||
/// Convert to usize (for array indexing)
|
||||
pub fn to_usize(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ValueId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "%{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Local variable identifier (before SSA conversion)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct LocalId(pub u32);
|
||||
|
||||
impl LocalId {
|
||||
/// Create a new LocalId
|
||||
pub fn new(id: u32) -> Self {
|
||||
LocalId(id)
|
||||
}
|
||||
|
||||
/// Get the raw ID value
|
||||
pub fn as_u32(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LocalId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "local_{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Value ID generator for unique SSA value creation
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ValueIdGenerator {
|
||||
next_id: u32,
|
||||
}
|
||||
|
||||
impl ValueIdGenerator {
|
||||
/// Create a new generator starting from 0
|
||||
pub fn new() -> Self {
|
||||
Self { next_id: 0 }
|
||||
}
|
||||
|
||||
/// Generate the next unique ValueId
|
||||
pub fn next(&mut self) -> ValueId {
|
||||
let id = ValueId(self.next_id);
|
||||
self.next_id += 1;
|
||||
id
|
||||
}
|
||||
|
||||
/// Peek at the next ID without consuming it
|
||||
pub fn peek_next(&self) -> ValueId {
|
||||
ValueId(self.next_id)
|
||||
}
|
||||
|
||||
/// Reset the generator (for testing)
|
||||
pub fn reset(&mut self) {
|
||||
self.next_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ValueIdGenerator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Local ID generator for variable naming
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalIdGenerator {
|
||||
next_id: u32,
|
||||
}
|
||||
|
||||
impl LocalIdGenerator {
|
||||
/// Create a new generator starting from 0
|
||||
pub fn new() -> Self {
|
||||
Self { next_id: 0 }
|
||||
}
|
||||
|
||||
/// Generate the next unique LocalId
|
||||
pub fn next(&mut self) -> LocalId {
|
||||
let id = LocalId(self.next_id);
|
||||
self.next_id += 1;
|
||||
id
|
||||
}
|
||||
|
||||
/// Reset the generator (for testing)
|
||||
pub fn reset(&mut self) {
|
||||
self.next_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LocalIdGenerator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_value_id_creation() {
|
||||
let id1 = ValueId::new(0);
|
||||
let id2 = ValueId::new(1);
|
||||
|
||||
assert_eq!(id1.as_u32(), 0);
|
||||
assert_eq!(id2.as_u32(), 1);
|
||||
assert_ne!(id1, id2);
|
||||
|
||||
assert_eq!(format!("{}", id1), "%0");
|
||||
assert_eq!(format!("{}", id2), "%1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_id_generator() {
|
||||
let mut gen = ValueIdGenerator::new();
|
||||
|
||||
let id1 = gen.next();
|
||||
let id2 = gen.next();
|
||||
let id3 = gen.next();
|
||||
|
||||
assert_eq!(id1, ValueId(0));
|
||||
assert_eq!(id2, ValueId(1));
|
||||
assert_eq!(id3, ValueId(2));
|
||||
|
||||
assert_eq!(gen.peek_next(), ValueId(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_id_creation() {
|
||||
let local1 = LocalId::new(0);
|
||||
let local2 = LocalId::new(1);
|
||||
|
||||
assert_eq!(format!("{}", local1), "local_0");
|
||||
assert_eq!(format!("{}", local2), "local_1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_id_generator() {
|
||||
let mut gen = LocalIdGenerator::new();
|
||||
|
||||
let local1 = gen.next();
|
||||
let local2 = gen.next();
|
||||
|
||||
assert_eq!(local1, LocalId(0));
|
||||
assert_eq!(local2, LocalId(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_id_ordering() {
|
||||
let id1 = ValueId(1);
|
||||
let id2 = ValueId(2);
|
||||
let id3 = ValueId(3);
|
||||
|
||||
assert!(id1 < id2);
|
||||
assert!(id2 < id3);
|
||||
assert!(id1 < id3);
|
||||
|
||||
let mut ids = vec![id3, id1, id2];
|
||||
ids.sort();
|
||||
|
||||
assert_eq!(ids, vec![id1, id2, id3]);
|
||||
}
|
||||
}
|
||||
318
src/mir/verification.rs
Normal file
318
src/mir/verification.rs
Normal file
@ -0,0 +1,318 @@
|
||||
/*!
|
||||
* MIR Verification - SSA form and semantic verification
|
||||
*
|
||||
* Implements dominance checking, SSA verification, and semantic analysis
|
||||
*/
|
||||
|
||||
use super::{MirModule, MirFunction, BasicBlockId, ValueId};
|
||||
use std::collections::{HashSet, HashMap};
|
||||
|
||||
/// Verification error types
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum VerificationError {
|
||||
/// Undefined value used
|
||||
UndefinedValue {
|
||||
value: ValueId,
|
||||
block: BasicBlockId,
|
||||
instruction_index: usize,
|
||||
},
|
||||
|
||||
/// Value defined multiple times
|
||||
MultipleDefinition {
|
||||
value: ValueId,
|
||||
first_block: BasicBlockId,
|
||||
second_block: BasicBlockId,
|
||||
},
|
||||
|
||||
/// Invalid phi function
|
||||
InvalidPhi {
|
||||
phi_value: ValueId,
|
||||
block: BasicBlockId,
|
||||
reason: String,
|
||||
},
|
||||
|
||||
/// Unreachable block
|
||||
UnreachableBlock {
|
||||
block: BasicBlockId,
|
||||
},
|
||||
|
||||
/// Control flow error
|
||||
ControlFlowError {
|
||||
block: BasicBlockId,
|
||||
reason: String,
|
||||
},
|
||||
|
||||
/// Dominator violation
|
||||
DominatorViolation {
|
||||
value: ValueId,
|
||||
use_block: BasicBlockId,
|
||||
def_block: BasicBlockId,
|
||||
},
|
||||
}
|
||||
|
||||
/// MIR verifier for SSA form and semantic correctness
|
||||
pub struct MirVerifier {
|
||||
/// Current verification errors
|
||||
errors: Vec<VerificationError>,
|
||||
}
|
||||
|
||||
impl MirVerifier {
|
||||
/// Create a new MIR verifier
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
errors: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify an entire MIR module
|
||||
pub fn verify_module(&mut self, module: &MirModule) -> Result<(), Vec<VerificationError>> {
|
||||
self.errors.clear();
|
||||
|
||||
for (name, function) in &module.functions {
|
||||
if let Err(mut func_errors) = self.verify_function(function) {
|
||||
// Add function context to errors
|
||||
for error in &mut func_errors {
|
||||
// Could add function name to error context here
|
||||
}
|
||||
self.errors.extend(func_errors);
|
||||
}
|
||||
}
|
||||
|
||||
if self.errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.errors.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a single MIR function
|
||||
pub fn verify_function(&mut self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||
let mut local_errors = Vec::new();
|
||||
|
||||
// 1. Check SSA form
|
||||
if let Err(mut ssa_errors) = self.verify_ssa_form(function) {
|
||||
local_errors.append(&mut ssa_errors);
|
||||
}
|
||||
|
||||
// 2. Check dominance relations
|
||||
if let Err(mut dom_errors) = self.verify_dominance(function) {
|
||||
local_errors.append(&mut dom_errors);
|
||||
}
|
||||
|
||||
// 3. Check control flow integrity
|
||||
if let Err(mut cfg_errors) = self.verify_control_flow(function) {
|
||||
local_errors.append(&mut cfg_errors);
|
||||
}
|
||||
|
||||
if local_errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(local_errors)
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify SSA form properties
|
||||
fn verify_ssa_form(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||
let mut errors = Vec::new();
|
||||
let mut definitions = HashMap::new();
|
||||
|
||||
// Check that each value is defined exactly once
|
||||
for (block_id, block) in &function.blocks {
|
||||
for (inst_idx, instruction) in block.all_instructions().enumerate() {
|
||||
if let Some(dst) = instruction.dst_value() {
|
||||
if let Some((first_block, _)) = definitions.insert(dst, (*block_id, inst_idx)) {
|
||||
errors.push(VerificationError::MultipleDefinition {
|
||||
value: dst,
|
||||
first_block,
|
||||
second_block: *block_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all used values are defined
|
||||
for (block_id, block) in &function.blocks {
|
||||
for (inst_idx, instruction) in block.all_instructions().enumerate() {
|
||||
for used_value in instruction.used_values() {
|
||||
if !definitions.contains_key(&used_value) {
|
||||
errors.push(VerificationError::UndefinedValue {
|
||||
value: used_value,
|
||||
block: *block_id,
|
||||
instruction_index: inst_idx,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify dominance relations
|
||||
fn verify_dominance(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||
// This is a simplified dominance check
|
||||
// In a full implementation, we would compute the dominator tree
|
||||
let mut errors = Vec::new();
|
||||
|
||||
// For now, just check that values are defined before use in the same block
|
||||
for (block_id, block) in &function.blocks {
|
||||
let mut defined_in_block = HashSet::new();
|
||||
|
||||
for instruction in block.all_instructions() {
|
||||
// Check uses
|
||||
for used_value in instruction.used_values() {
|
||||
if !defined_in_block.contains(&used_value) {
|
||||
// Value used before definition in this block
|
||||
// This is okay if it's defined in a dominating block
|
||||
// For simplicity, we'll skip this check for now
|
||||
}
|
||||
}
|
||||
|
||||
// Record definition
|
||||
if let Some(dst) = instruction.dst_value() {
|
||||
defined_in_block.insert(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify control flow graph integrity
|
||||
fn verify_control_flow(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||
let mut errors = Vec::new();
|
||||
|
||||
// Check that all referenced blocks exist
|
||||
for (block_id, block) in &function.blocks {
|
||||
for successor in &block.successors {
|
||||
if !function.blocks.contains_key(successor) {
|
||||
errors.push(VerificationError::ControlFlowError {
|
||||
block: *block_id,
|
||||
reason: format!("References non-existent block {}", successor),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all blocks are reachable from entry
|
||||
let reachable = self.compute_reachable_blocks(function);
|
||||
for block_id in function.blocks.keys() {
|
||||
if !reachable.contains(block_id) && *block_id != function.entry_block {
|
||||
errors.push(VerificationError::UnreachableBlock {
|
||||
block: *block_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errors)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute reachable blocks from entry
|
||||
fn compute_reachable_blocks(&self, function: &MirFunction) -> HashSet<BasicBlockId> {
|
||||
let mut reachable = HashSet::new();
|
||||
let mut worklist = vec![function.entry_block];
|
||||
|
||||
while let Some(current) = worklist.pop() {
|
||||
if reachable.insert(current) {
|
||||
if let Some(block) = function.blocks.get(¤t) {
|
||||
for successor in &block.successors {
|
||||
if !reachable.contains(successor) {
|
||||
worklist.push(*successor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reachable
|
||||
}
|
||||
|
||||
/// Get all verification errors from the last run
|
||||
pub fn get_errors(&self) -> &[VerificationError] {
|
||||
&self.errors
|
||||
}
|
||||
|
||||
/// Clear verification errors
|
||||
pub fn clear_errors(&mut self) {
|
||||
self.errors.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MirVerifier {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VerificationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VerificationError::UndefinedValue { value, block, instruction_index } => {
|
||||
write!(f, "Undefined value {} used in block {} at instruction {}",
|
||||
value, block, instruction_index)
|
||||
},
|
||||
VerificationError::MultipleDefinition { value, first_block, second_block } => {
|
||||
write!(f, "Value {} defined multiple times: first in block {}, again in block {}",
|
||||
value, first_block, second_block)
|
||||
},
|
||||
VerificationError::InvalidPhi { phi_value, block, reason } => {
|
||||
write!(f, "Invalid phi function {} in block {}: {}",
|
||||
phi_value, block, reason)
|
||||
},
|
||||
VerificationError::UnreachableBlock { block } => {
|
||||
write!(f, "Unreachable block {}", block)
|
||||
},
|
||||
VerificationError::ControlFlowError { block, reason } => {
|
||||
write!(f, "Control flow error in block {}: {}", block, reason)
|
||||
},
|
||||
VerificationError::DominatorViolation { value, use_block, def_block } => {
|
||||
write!(f, "Value {} used in block {} but defined in non-dominating block {}",
|
||||
value, use_block, def_block)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::{MirFunction, FunctionSignature, MirType, EffectMask, BasicBlock};
|
||||
|
||||
#[test]
|
||||
fn test_valid_function_verification() {
|
||||
let signature = FunctionSignature {
|
||||
name: "test".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
|
||||
let entry_block = BasicBlockId::new(0);
|
||||
let function = MirFunction::new(signature, entry_block);
|
||||
|
||||
let mut verifier = MirVerifier::new();
|
||||
let result = verifier.verify_function(&function);
|
||||
|
||||
assert!(result.is_ok(), "Valid function should pass verification");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_undefined_value_detection() {
|
||||
// This test would create a function with undefined value usage
|
||||
// and verify that the verifier catches it
|
||||
// Implementation details would depend on the specific test case
|
||||
}
|
||||
}
|
||||
6
test_mir_simple.nyash
Normal file
6
test_mir_simple.nyash
Normal file
@ -0,0 +1,6 @@
|
||||
/*!
|
||||
* Simple test for basic MIR functionality
|
||||
*/
|
||||
|
||||
// A simple Nyash program for testing MIR compilation
|
||||
print(42 + 10)
|
||||
Reference in New Issue
Block a user