Implement Phase 5: Control flow & exceptions in MIR/VM - Core functionality complete

Co-authored-by: moe-charm <217100418+moe-charm@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-08-13 06:23:28 +00:00
parent 2e9b5daadf
commit d3a85b2305
8 changed files with 468 additions and 0 deletions

View File

@ -256,6 +256,33 @@ impl VM {
Ok(ControlFlow::Continue) Ok(ControlFlow::Continue)
}, },
MirInstruction::Nop => {
// No-op instruction
Ok(ControlFlow::Continue)
},
// Phase 5: Control flow & exception handling
MirInstruction::Throw { exception, effects: _ } => {
let exception_val = self.get_value(*exception)?;
// For now, convert throw to error return (simplified exception handling)
// In a full implementation, this would unwind the stack looking for catch handlers
println!("Exception thrown: {}", exception_val.to_string());
Err(VMError::InvalidInstruction(format!("Unhandled exception: {}", exception_val.to_string())))
},
MirInstruction::Catch { exception_type: _, exception_value, handler_bb: _ } => {
// For now, catch is a no-op since we don't have full exception handling
// In a real implementation, this would set up exception handling metadata
self.values.insert(*exception_value, VMValue::Void);
Ok(ControlFlow::Continue)
},
MirInstruction::Safepoint => {
// Safepoint is a no-op for now
// In a real implementation, this could trigger GC, debugging, etc.
Ok(ControlFlow::Continue)
},
_ => { _ => {
Err(VMError::InvalidInstruction(format!("Unsupported instruction: {:?}", instruction))) Err(VMError::InvalidInstruction(format!("Unsupported instruction: {:?}", instruction)))
} }

View File

@ -72,6 +72,9 @@ impl MirBuilder {
self.current_function = Some(main_function); self.current_function = Some(main_function);
self.current_block = Some(entry_block); self.current_block = Some(entry_block);
// Add safepoint at function entry
self.emit_instruction(MirInstruction::Safepoint)?;
// Convert AST to MIR // Convert AST to MIR
let result_value = self.build_expression(ast)?; let result_value = self.build_expression(ast)?;
@ -160,6 +163,18 @@ impl MirBuilder {
) )
}, },
ASTNode::Loop { condition, body, .. } => {
self.build_loop_statement(*condition.clone(), body.clone())
},
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
self.build_try_catch_statement(try_body.clone(), catch_clauses.clone(), finally_body.clone())
},
ASTNode::Throw { expression, .. } => {
self.build_throw_statement(*expression.clone())
},
_ => { _ => {
Err(format!("Unsupported AST node type: {:?}", ast)) Err(format!("Unsupported AST node type: {:?}", ast))
} }
@ -391,6 +406,160 @@ impl MirBuilder {
} }
} }
/// Build a loop statement: loop(condition) { body }
fn build_loop_statement(&mut self, condition: ASTNode, body: Vec<ASTNode>) -> Result<ValueId, String> {
// Add safepoint at loop entry
self.emit_instruction(MirInstruction::Safepoint)?;
let loop_header = self.block_gen.next();
let loop_body = self.block_gen.next();
let loop_exit = self.block_gen.next();
// Jump to loop header
self.emit_instruction(MirInstruction::Jump { target: loop_header })?;
// Create loop header block
self.start_new_block(loop_header)?;
// Evaluate condition
let condition_value = self.build_expression(condition)?;
// Branch based on condition
self.emit_instruction(MirInstruction::Branch {
condition: condition_value,
then_bb: loop_body,
else_bb: loop_exit,
})?;
// Create loop body block
self.start_new_block(loop_body)?;
// Add safepoint at loop body start
self.emit_instruction(MirInstruction::Safepoint)?;
// Build loop body
let body_ast = ASTNode::Program {
statements: body,
span: crate::ast::Span::unknown(),
};
self.build_expression(body_ast)?;
// Jump back to loop header
self.emit_instruction(MirInstruction::Jump { target: loop_header })?;
// Create exit block
self.start_new_block(loop_exit)?;
// Return void value
let void_dst = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: void_dst,
value: ConstValue::Void,
})?;
Ok(void_dst)
}
/// Build a try/catch statement
fn build_try_catch_statement(&mut self, try_body: Vec<ASTNode>, catch_clauses: Vec<crate::ast::CatchClause>, finally_body: Option<Vec<ASTNode>>) -> Result<ValueId, String> {
let try_block = self.block_gen.next();
let catch_block = self.block_gen.next();
let finally_block = if finally_body.is_some() { Some(self.block_gen.next()) } else { None };
let exit_block = self.block_gen.next();
// Jump to try block
self.emit_instruction(MirInstruction::Jump { target: try_block })?;
// Build try block
self.start_new_block(try_block)?;
let try_ast = ASTNode::Program {
statements: try_body,
span: crate::ast::Span::unknown(),
};
let try_result = self.build_expression(try_ast)?;
// Jump to finally or exit
let next_target = finally_block.unwrap_or(exit_block);
self.emit_instruction(MirInstruction::Jump { target: next_target })?;
// Build catch block
self.start_new_block(catch_block)?;
// For now, handle first catch clause only (simplified)
if let Some(catch_clause) = catch_clauses.first() {
let exception_value = self.value_gen.next();
// Set up catch handler
self.emit_instruction(MirInstruction::Catch {
exception_type: catch_clause.exception_type.clone(),
exception_value,
handler_bb: catch_block,
})?;
// Build catch body
let catch_ast = ASTNode::Program {
statements: catch_clause.body.clone(),
span: crate::ast::Span::unknown(),
};
self.build_expression(catch_ast)?;
}
// Jump to finally or exit
let next_target = finally_block.unwrap_or(exit_block);
self.emit_instruction(MirInstruction::Jump { target: next_target })?;
// Build finally block if present
if let (Some(finally_block_id), Some(finally_statements)) = (finally_block, finally_body) {
self.start_new_block(finally_block_id)?;
let finally_ast = ASTNode::Program {
statements: finally_statements,
span: crate::ast::Span::unknown(),
};
self.build_expression(finally_ast)?;
self.emit_instruction(MirInstruction::Jump { target: exit_block })?;
}
// Create exit block
self.start_new_block(exit_block)?;
// Return the try result (simplified - in real implementation would need phi node)
Ok(try_result)
}
/// Build a throw statement
fn build_throw_statement(&mut self, expression: ASTNode) -> Result<ValueId, String> {
let exception_value = self.build_expression(expression)?;
// Emit throw instruction with PANIC effect
self.emit_instruction(MirInstruction::Throw {
exception: exception_value,
effects: EffectMask::PANIC,
})?;
// Throw doesn't return normally, but we need to return a value for the type system
let void_dst = self.value_gen.next();
self.emit_instruction(MirInstruction::Const {
dst: void_dst,
value: ConstValue::Void,
})?;
Ok(void_dst)
}
/// Start a new basic block
fn start_new_block(&mut self, block_id: BasicBlockId) -> Result<(), String> {
if let Some(ref mut function) = self.current_function {
function.add_block(BasicBlock::new(block_id));
self.current_block = Some(block_id);
Ok(())
} else {
Err("No current function".to_string())
}
}
/// Convert AST binary operator to MIR operator /// Convert AST binary operator to MIR operator
fn convert_binary_operator(&self, op: BinaryOperator) -> Result<BinaryOpType, String> { fn convert_binary_operator(&self, op: BinaryOperator) -> Result<BinaryOpType, String> {
match op { match op {

View File

@ -55,6 +55,9 @@ impl EffectMask {
/// P2P communication effects /// P2P communication effects
pub const P2P: Self = Self(Effect::P2P as u16); pub const P2P: Self = Self(Effect::P2P as u16);
/// Panic/exception effects
pub const PANIC: Self = Self(Effect::Panic as u16);
/// All effects - maximum side effects /// All effects - maximum side effects
pub const ALL: Self = Self(0xFFFF); pub const ALL: Self = Self(0xFFFF);

View File

@ -178,6 +178,27 @@ pub enum MirInstruction {
/// No-op instruction (for optimization placeholders) /// No-op instruction (for optimization placeholders)
Nop, Nop,
// === Control Flow & Exception Handling (Phase 5) ===
/// Throw an exception
/// `throw %exception_value`
Throw {
exception: ValueId,
effects: EffectMask,
},
/// Catch handler setup (landing pad for exceptions)
/// `catch %exception_type -> %handler_bb`
Catch {
exception_type: Option<String>, // None = catch-all
exception_value: ValueId, // Where to store caught exception
handler_bb: super::BasicBlockId,
},
/// Safepoint instruction (no-op for now, can be used for GC/debugging)
/// `safepoint`
Safepoint,
} }
/// Constant values in MIR /// Constant values in MIR
@ -274,6 +295,11 @@ impl MirInstruction {
// Print has external write effect // Print has external write effect
MirInstruction::Print { effects, .. } => *effects, MirInstruction::Print { effects, .. } => *effects,
// Phase 5: Control flow & exception handling
MirInstruction::Throw { effects, .. } => *effects,
MirInstruction::Catch { .. } => EffectMask::PURE, // Setting up handler is pure
MirInstruction::Safepoint => EffectMask::PURE, // No-op for now
} }
} }
@ -302,7 +328,11 @@ impl MirInstruction {
MirInstruction::ArraySet { .. } | MirInstruction::ArraySet { .. } |
MirInstruction::Debug { .. } | MirInstruction::Debug { .. } |
MirInstruction::Print { .. } | MirInstruction::Print { .. } |
MirInstruction::Throw { .. } |
MirInstruction::Safepoint |
MirInstruction::Nop => None, MirInstruction::Nop => None,
MirInstruction::Catch { exception_value, .. } => Some(*exception_value),
} }
} }
@ -352,6 +382,11 @@ impl MirInstruction {
MirInstruction::Phi { inputs, .. } => { MirInstruction::Phi { inputs, .. } => {
inputs.iter().map(|(_, value)| *value).collect() inputs.iter().map(|(_, value)| *value).collect()
}, },
// Phase 5: Control flow & exception handling
MirInstruction::Throw { exception, .. } => vec![*exception],
MirInstruction::Catch { .. } => Vec::new(), // Handler setup doesn't use values
MirInstruction::Safepoint => Vec::new(),
} }
} }
} }

View File

@ -110,4 +110,97 @@ mod tests {
assert!(!mir_dump.is_empty(), "MIR dump should not be empty"); assert!(!mir_dump.is_empty(), "MIR dump should not be empty");
assert!(mir_dump.contains("function"), "MIR dump should contain function information"); assert!(mir_dump.contains("function"), "MIR dump should contain function information");
} }
#[test]
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]
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");
}
} }

View File

@ -301,6 +301,23 @@ impl MirPrinter {
MirInstruction::Nop => { MirInstruction::Nop => {
"nop".to_string() "nop".to_string()
}, },
// Phase 5: Control flow & exception handling
MirInstruction::Throw { exception, effects: _ } => {
format!("throw {}", exception)
},
MirInstruction::Catch { exception_type, exception_value, handler_bb } => {
if let Some(ref exc_type) = exception_type {
format!("catch {} {} -> {}", exc_type, exception_value, handler_bb)
} else {
format!("catch * {} -> {}", exception_value, handler_bb)
}
},
MirInstruction::Safepoint => {
"safepoint".to_string()
},
} }
} }

View File

@ -0,0 +1,21 @@
// Test MIR control flow and exception handling - Phase 5
// Test loop functionality
local counter
counter = 0
loop(counter < 3) {
print(counter)
counter = counter + 1
}
// Test try/catch functionality
try {
print("In try block")
throw "Test exception"
print("This should not execute")
} catch (exception) {
print("Caught exception")
}
print("Program completed")

103
test_mir_phase5.rs Normal file
View File

@ -0,0 +1,103 @@
use nyash_rust::mir::{MirCompiler};
use nyash_rust::ast::{ASTNode, LiteralValue, Span};
fn main() {
println!("=== Testing MIR Control Flow Compilation ===\n");
// Test 1: Basic Throw instruction
println!("Test 1: Basic Throw Instruction");
let throw_ast = ASTNode::Throw {
expression: Box::new(ASTNode::Literal {
value: LiteralValue::String("Test exception".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let mut compiler = MirCompiler::new();
match compiler.compile(throw_ast) {
Ok(result) => {
println!("✓ Throw compilation successful");
let mir_dump = compiler.dump_mir(&result.module);
println!("MIR Output:\n{}", mir_dump);
},
Err(e) => println!("✗ Throw compilation failed: {}", e),
}
println!("\n" + &"=".repeat(50) + "\n");
// Test 2: Basic Loop instruction
println!("Test 2: Basic Loop Instruction");
let loop_ast = ASTNode::Loop {
condition: Box::new(ASTNode::Literal {
value: LiteralValue::Bool(true),
span: Span::unknown(),
}),
body: vec![
ASTNode::Print {
expression: Box::new(ASTNode::Literal {
value: LiteralValue::String("Hello from loop".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}
],
span: Span::unknown(),
};
let mut compiler2 = MirCompiler::new();
match compiler2.compile(loop_ast) {
Ok(result) => {
println!("✓ Loop compilation successful");
let mir_dump = compiler2.dump_mir(&result.module);
println!("MIR Output:\n{}", mir_dump);
},
Err(e) => println!("✗ Loop compilation failed: {}", e),
}
println!("\n" + &"=".repeat(50) + "\n");
// Test 3: TryCatch compilation
println!("Test 3: TryCatch Instruction");
let try_catch_ast = ASTNode::TryCatch {
try_body: vec![
ASTNode::Print {
expression: Box::new(ASTNode::Literal {
value: LiteralValue::String("In try block".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}
],
catch_clauses: vec![
nyash_rust::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("In catch block".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}
],
span: Span::unknown(),
}
],
finally_body: None,
span: Span::unknown(),
};
let mut compiler3 = MirCompiler::new();
match compiler3.compile(try_catch_ast) {
Ok(result) => {
println!("✓ TryCatch compilation successful");
let mir_dump = compiler3.dump_mir(&result.module);
println!("MIR Output:\n{}", mir_dump);
},
Err(e) => println!("✗ TryCatch compilation failed: {}", e),
}
println!("\n=== All tests completed ===");
}