Files
hakorune/src/mir/mod.rs

429 lines
18 KiB
Rust
Raw Normal View History

/*!
* 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 instruction_v2; // New 25-instruction specification
pub mod instruction_introspection; // Introspection helpers for tests (core instruction names)
pub mod basic_block;
pub mod function;
pub mod builder;
pub mod loop_builder; // SSA loop construction with phi nodes
pub mod loop_api; // Minimal LoopBuilder facade (adapter-ready)
pub mod verification;
pub mod ownership_verifier_simple; // Simple ownership forest verification for current MIR
pub mod printer;
pub mod value_id;
pub mod effect;
pub mod optimizer;
pub mod slot_registry; // Phase 9.79b.1: method slot resolution (IDs)
pub mod passes; // Optimization subpasses (e.g., type_hints)
// Re-export main types for easy access
pub use instruction::{MirInstruction, BinaryOp, CompareOp, UnaryOp, ConstValue, MirType, TypeOpKind, WeakRefOp, BarrierOp};
pub use instruction_v2::{MirInstructionV2, AtomicOrdering}; // New 25-instruction set
pub use basic_block::{BasicBlock, BasicBlockId, BasicBlockIdGenerator};
pub use function::{MirFunction, MirModule, FunctionSignature};
pub use builder::MirBuilder;
pub use verification::{MirVerifier, VerificationError};
pub use ownership_verifier_simple::{OwnershipVerifier, OwnershipError, OwnershipStats}; // Simple ownership forest verification
pub use printer::MirPrinter;
pub use value_id::{ValueId, LocalId, ValueIdGenerator};
pub use effect::{EffectMask, Effect};
pub use optimizer::MirOptimizer;
pub use slot_registry::{BoxTypeId, MethodSlot};
/// 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,
optimize: bool,
}
impl MirCompiler {
/// Create a new MIR compiler
pub fn new() -> Self {
Self {
builder: MirBuilder::new(),
verifier: MirVerifier::new(),
optimize: true,
}
}
/// Create with options
pub fn with_options(optimize: bool) -> Self {
Self {
builder: MirBuilder::new(),
verifier: MirVerifier::new(),
optimize,
}
}
/// 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 mut module = self.builder.build_module(ast)?;
if self.optimize {
let mut optimizer = MirOptimizer::new();
let stats = optimizer.optimize_module(&mut module);
if (crate::config::env::opt_diag_fail()
|| crate::config::env::opt_diag_forbid_legacy())
&& stats.diagnostics_reported > 0 {
return Err(format!("Diagnostic failure: {} issues detected (unlowered/legacy)", stats.diagnostics_reported));
}
}
// Core-13 strict: forbid legacy ops in final MIR when enabled
if crate::config::env::mir_core13() || crate::config::env::opt_diag_forbid_legacy() {
let mut legacy_count = 0usize;
for (_fname, function) in &module.functions {
for (_bb, block) in &function.blocks {
for inst in &block.instructions {
if matches!(inst,
MirInstruction::TypeCheck { .. }
| MirInstruction::Cast { .. }
| MirInstruction::WeakNew { .. }
| MirInstruction::WeakLoad { .. }
| MirInstruction::BarrierRead { .. }
| MirInstruction::BarrierWrite { .. }
| MirInstruction::ArrayGet { .. }
| MirInstruction::ArraySet { .. }
| MirInstruction::RefGet { .. }
| MirInstruction::RefSet { .. }
| MirInstruction::PluginInvoke { .. }
) { legacy_count += 1; }
}
if let Some(term) = &block.terminator {
if matches!(term,
MirInstruction::TypeCheck { .. }
| MirInstruction::Cast { .. }
| MirInstruction::WeakNew { .. }
| MirInstruction::WeakLoad { .. }
| MirInstruction::BarrierRead { .. }
| MirInstruction::BarrierWrite { .. }
| MirInstruction::ArrayGet { .. }
| MirInstruction::ArraySet { .. }
| MirInstruction::RefGet { .. }
| MirInstruction::RefSet { .. }
| MirInstruction::PluginInvoke { .. }
) { legacy_count += 1; }
}
}
}
if legacy_count > 0 {
return Err(format!("Core-13 strict: final MIR contains {} legacy ops", legacy_count));
}
}
// 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("define"), "MIR dump should contain function definition");
}
#[test]
fn test_lowering_is_type_function_call_in_print() {
// Build AST: print(isType(42, "Integer"))
let ast = ASTNode::Print {
expression: Box::new(ASTNode::FunctionCall {
name: "isType".to_string(),
arguments: vec![
ASTNode::Literal { value: LiteralValue::Integer(42), span: crate::ast::Span::unknown() },
ASTNode::Literal { value: LiteralValue::String("Integer".to_string()), span: crate::ast::Span::unknown() },
],
span: crate::ast::Span::unknown(),
}),
span: crate::ast::Span::unknown(),
};
let mut compiler = MirCompiler::new();
let result = compiler.compile(ast).expect("compile should succeed");
// Ensure TypeOp exists in the resulting MIR
let has_typeop = result.module.functions.values().any(|f| {
f.blocks.values().any(|b| b.all_instructions().any(|i| matches!(i, MirInstruction::TypeOp { .. })))
});
assert!(has_typeop, "Expected TypeOp lowering for print(isType(...))");
}
#[test]
fn test_lowering_is_method_call_in_print() {
// Build AST: print( (42).is("Integer") )
let ast = ASTNode::Print {
expression: Box::new(ASTNode::MethodCall {
object: Box::new(ASTNode::Literal { value: LiteralValue::Integer(42), span: crate::ast::Span::unknown() }),
method: "is".to_string(),
arguments: vec![
ASTNode::Literal { value: LiteralValue::String("Integer".to_string()), span: crate::ast::Span::unknown() },
],
span: crate::ast::Span::unknown(),
}),
span: crate::ast::Span::unknown(),
};
let mut compiler = MirCompiler::new();
let result = compiler.compile(ast).expect("compile should succeed");
// Ensure TypeOp exists in the resulting MIR
let has_typeop = result.module.functions.values().any(|f| {
f.blocks.values().any(|b| b.all_instructions().any(|i| matches!(i, MirInstruction::TypeOp { .. })))
});
assert!(has_typeop, "Expected TypeOp lowering for print(obj.is(...))");
}
#[test]
#[ignore = "MIR13 migration: extern console.log expectation pending"]
fn test_lowering_extern_console_log() {
// Build AST: console.log("hi") → ExternCall env.console.log
let ast = ASTNode::MethodCall {
object: Box::new(ASTNode::Variable { name: "console".to_string(), span: crate::ast::Span::unknown() }),
method: "log".to_string(),
arguments: vec![ ASTNode::Literal { value: LiteralValue::String("hi".to_string()), span: crate::ast::Span::unknown() } ],
span: crate::ast::Span::unknown(),
};
let mut compiler = MirCompiler::new();
let result = compiler.compile(ast).expect("compile should succeed");
let dump = MirPrinter::verbose().print_module(&result.module);
assert!(dump.contains("extern_call env.console.log"), "Expected extern_call env.console.log in MIR dump. Got:\n{}", dump);
}
#[test]
fn test_lowering_boxcall_array_push() {
// Build AST: (new ArrayBox()).push(1)
let ast = ASTNode::MethodCall {
object: Box::new(ASTNode::New { class: "ArrayBox".to_string(), arguments: vec![], type_arguments: vec![], span: crate::ast::Span::unknown() }),
method: "push".to_string(),
arguments: vec![ ASTNode::Literal { value: LiteralValue::Integer(1), span: crate::ast::Span::unknown() } ],
span: crate::ast::Span::unknown(),
};
let mut compiler = MirCompiler::new();
let result = compiler.compile(ast).expect("compile should succeed");
let dump = MirPrinter::new().print_module(&result.module);
// Expect a BoxCall to push (printer formats as `call <box>.<method>(...)`)
assert!(dump.contains(".push("), "Expected BoxCall to .push(...). Got:\n{}", dump);
}
#[test]
#[ignore = "MIR13 migration: method id naming in printer pending"]
fn test_boxcall_method_id_on_universal_slot() {
// Build AST: (new ArrayBox()).toString()
let ast = ASTNode::MethodCall {
object: Box::new(ASTNode::New { class: "ArrayBox".to_string(), arguments: vec![], type_arguments: vec![], span: crate::ast::Span::unknown() }),
method: "toString".to_string(),
arguments: vec![],
span: crate::ast::Span::unknown(),
};
let mut compiler = MirCompiler::new();
let result = compiler.compile(ast).expect("compile should succeed");
let dump = MirPrinter::new().print_module(&result.module);
// Expect a BoxCall with numeric method id [#0] for toString universal slot
assert!(dump.contains("toString[#0]"), "Expected method_id #0 for toString. Dump:\n{}", dump);
}
#[test]
fn test_lowering_await_expression() {
// Build AST: await 1 (semantic is nonsensical but should emit Await)
let ast = ASTNode::AwaitExpression { expression: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: crate::ast::Span::unknown() }), span: crate::ast::Span::unknown() };
let mut compiler = MirCompiler::new();
let result = compiler.compile(ast).expect("compile should succeed");
let dump = MirPrinter::new().print_module(&result.module);
assert!(dump.contains("await"), "Expected await in MIR dump. Got:\n{}", dump);
}
#[test]
fn test_await_has_checkpoints() {
use crate::ast::{LiteralValue, Span};
// Build: await 1
let ast = ASTNode::AwaitExpression { expression: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() };
let mut compiler = MirCompiler::new();
let result = compiler.compile(ast).expect("compile");
// Verifier should pass (await flanked by safepoints)
assert!(result.verification_result.is_ok(), "Verifier failed for await checkpoints: {:?}", result.verification_result);
let dump = compiler.dump_mir(&result.module);
// Expect at least two safepoints in the function (before/after await)
let sp_count = dump.matches("safepoint").count();
assert!(sp_count >= 2, "Expected >=2 safepoints around await, got {}. Dump:\n{}", sp_count, dump);
}
#[test]
fn test_rewritten_await_still_checkpoints() {
use crate::ast::{LiteralValue, Span};
// Enable rewrite so Await → ExternCall(env.future.await)
std::env::set_var("NYASH_REWRITE_FUTURE", "1");
let ast = ASTNode::AwaitExpression { expression: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() };
let mut compiler = MirCompiler::new();
let result = compiler.compile(ast).expect("compile");
// Verifier should still pass (checkpoint verification includes ExternCall await)
assert!(result.verification_result.is_ok(), "Verifier failed for rewritten await checkpoints: {:?}", result.verification_result);
let dump = compiler.dump_mir(&result.module);
assert!(dump.contains("env.future.await"), "Expected rewritten await extern call. Dump:\n{}", dump);
let sp_count = dump.matches("safepoint").count();
assert!(sp_count >= 2, "Expected >=2 safepoints around rewritten await, got {}. Dump:\n{}", sp_count, dump);
// Cleanup env
std::env::remove_var("NYASH_REWRITE_FUTURE");
}
#[test]
#[ignore = "MIR13 migration: throw/safepoint expectations pending"]
fn test_throw_compilation() {
let mut compiler = MirCompiler::new();
let throw_ast = ASTNode::Throw {
expression: Box::new(ASTNode::Literal {
value: LiteralValue::String("Test exception".to_string()),
span: crate::ast::Span::unknown(),
}),
span: crate::ast::Span::unknown(),
};
let result = compiler.compile(throw_ast);
assert!(result.is_ok(), "Throw compilation should succeed");
let compile_result = result.unwrap();
let mir_dump = compiler.dump_mir(&compile_result.module);
assert!(mir_dump.contains("throw"), "MIR should contain throw instruction");
assert!(mir_dump.contains("safepoint"), "MIR should contain safepoint instruction");
}
#[test]
#[ignore = "MIR13 migration: loop safepoint expectation pending"]
fn test_loop_compilation() {
let mut compiler = MirCompiler::new();
let loop_ast = ASTNode::Loop {
condition: Box::new(ASTNode::Literal {
value: LiteralValue::Bool(true),
span: crate::ast::Span::unknown(),
}),
body: vec![
ASTNode::Print {
expression: Box::new(ASTNode::Literal {
value: LiteralValue::String("Loop body".to_string()),
span: crate::ast::Span::unknown(),
}),
span: crate::ast::Span::unknown(),
}
],
span: crate::ast::Span::unknown(),
};
let result = compiler.compile(loop_ast);
assert!(result.is_ok(), "Loop compilation should succeed");
let compile_result = result.unwrap();
let mir_dump = compiler.dump_mir(&compile_result.module);
assert!(mir_dump.contains("br"), "MIR should contain branch instructions");
assert!(mir_dump.contains("safepoint"), "MIR should contain safepoint instructions");
}
#[test]
fn test_try_catch_compilation() {
let mut compiler = MirCompiler::new();
let try_catch_ast = ASTNode::TryCatch {
try_body: vec![
ASTNode::Print {
expression: Box::new(ASTNode::Literal {
value: LiteralValue::String("Try block".to_string()),
span: crate::ast::Span::unknown(),
}),
span: crate::ast::Span::unknown(),
}
],
catch_clauses: vec![
crate::ast::CatchClause {
exception_type: Some("Exception".to_string()),
variable_name: Some("e".to_string()),
body: vec![
ASTNode::Print {
expression: Box::new(ASTNode::Literal {
value: LiteralValue::String("Catch block".to_string()),
span: crate::ast::Span::unknown(),
}),
span: crate::ast::Span::unknown(),
}
],
span: crate::ast::Span::unknown(),
}
],
finally_body: None,
span: crate::ast::Span::unknown(),
};
let result = compiler.compile(try_catch_ast);
assert!(result.is_ok(), "TryCatch compilation should succeed");
let compile_result = result.unwrap();
let mir_dump = compiler.dump_mir(&compile_result.module);
assert!(mir_dump.contains("catch"), "MIR should contain catch instruction");
}
}