Files
hakorune/src/mir/builder/control_flow.rs

672 lines
25 KiB
Rust
Raw Normal View History

//! Control-flow entrypoints (if/loop/try/throw) centralized here.
use super::{Effect, EffectMask, MirInstruction, ValueId};
use crate::ast::{ASTNode, Span};
impl super::MirBuilder {
/// Control-flow: block
pub(super) fn cf_block(&mut self, statements: Vec<ASTNode>) -> Result<ValueId, String> {
// identical to build_block; kept here for future policy hooks
self.build_block(statements)
}
/// Control-flow: if
pub(super) fn cf_if(
&mut self,
condition: ASTNode,
then_branch: ASTNode,
else_branch: Option<ASTNode>,
) -> Result<ValueId, String> {
// current impl is a simple forward to canonical lowering
self.lower_if_form(condition, then_branch, else_branch)
}
/// Control-flow: loop
///
/// # Phase 49: JoinIR Frontend Mainline Integration
///
/// This is the unified entry point for all loop lowering. When enabled via
/// `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`, specific functions (starting with
/// `JsonTokenizer.print_tokens/1`) are routed through JoinIR Frontend instead
/// of the traditional LoopBuilder path.
pub(super) fn cf_loop(
&mut self,
condition: ASTNode,
body: Vec<ASTNode>,
) -> Result<ValueId, String> {
// Phase 49: Try JoinIR Frontend route for mainline targets
if let Some(result) = self.try_cf_loop_joinir(&condition, &body)? {
return Ok(result);
}
📦 Hotfix 1 & 2: Parameter ValueId Reservation + Exit PHI Validation (Box-First Theory) **箱理論に基づく根治的修正**: ## 🎯 Hotfix 1: Parameter ValueId Reservation (パラメータ ValueId 予約) ### 根本原因 - MirFunction counter が params.len() を考慮していなかった - local variables が parameter ValueIds を上書き ### 箱理論的解決 1. **LoopFormContext Box** - パラメータ予約を明示的に管理 - 境界をはっきりさせる 2. **MirFunction::new() 改善** - `initial_counter = param_count.max(1)` でパラメータ予約 - Parameters are %0, %1, ..., %N-1 3. **ensure_counter_after() 強化** - パラメータ数 + 既存 ValueIds 両方を考慮 - `min_counter = param_count.max(max_id + 1)` 4. **reserve_parameter_value_ids() 追加** - 明示的な予約メソッド(Box-First) ## 🎯 Hotfix 2: Exit PHI Predecessor Validation (Exit PHI 検証) ### 根本原因 - LoopForm builder が存在しないブロックを PHI predecessor に追加 - 「幽霊ブロック」問題 ### 箱理論的解決 1. **LoopFormOps.block_exists() 追加** - CFG 存在確認メソッド - 境界を明確化 2. **build_exit_phis() 検証** - 非存在ブロックをスキップ - デバッグログ付き ### 実装ファイル - `src/mir/function.rs`: Parameter reservation - `src/mir/phi_core/loopform_builder.rs`: Context + validation - `src/mir/loop_builder.rs`: LoopFormOps impl - `src/mir/builder/stmts.rs`: Local variable allocation ### 業界標準準拠 - ✅ LLVM IR: Parameters are %0, %1, ... - ✅ SSA Form: PHI predecessors must exist in CFG - ✅ Cytron et al. (1991): Parameter reservation principle 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 06:39:45 +09:00
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
eprintln!("[cf_loop] CALLED from somewhere");
eprintln!("[cf_loop] Current stack (simulated): check build_statement vs build_expression_impl");
}
// Delegate to LoopBuilder for consistent handling
let mut loop_builder = crate::mir::loop_builder::LoopBuilder::new(self);
loop_builder.build_loop(condition, body)
}
/// Phase 49: Try JoinIR Frontend for mainline integration
///
/// Returns `Ok(Some(value))` if the current function should use JoinIR Frontend,
/// `Ok(None)` to fall through to the legacy LoopBuilder path.
fn try_cf_loop_joinir(
&mut self,
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<ValueId>, String> {
// Phase 49-2: Check if feature is enabled
if std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN").ok().as_deref() != Some("1") {
return Ok(None);
}
// Get current function name
let func_name = self
.current_function
.as_ref()
.map(|f| f.signature.name.clone())
.unwrap_or_default();
// Phase 49-2: Only handle print_tokens for now
if func_name != "JsonTokenizer.print_tokens/1" {
return Ok(None);
}
// Debug log when routing through JoinIR Frontend
let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok()
|| std::env::var("NYASH_JOINIR_MAINLINE_DEBUG").is_ok();
if debug {
eprintln!(
"[cf_loop/joinir] Routing {} through JoinIR Frontend mainline",
func_name
);
}
// Phase 49-3: Implement JoinIR Frontend integration
self.cf_loop_joinir_impl(condition, body, &func_name, debug)
}
/// Phase 49-3: JoinIR Frontend integration implementation
///
/// # Pipeline
/// 1. Build Loop AST → Program JSON
/// 2. AstToJoinIrLowerer::lower_program_json() → JoinModule
/// 3. convert_join_module_to_mir_with_meta() → MirModule
/// 4. Merge MIR blocks into current_function
fn cf_loop_joinir_impl(
&mut self,
condition: &ASTNode,
body: &[ASTNode],
func_name: &str,
debug: bool,
) -> Result<Option<ValueId>, String> {
use crate::r#macro::ast_json::ast_to_json;
use crate::mir::join_ir::frontend::{AstToJoinIrLowerer, JoinFuncMetaMap};
use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta;
use crate::mir::types::ConstValue;
// Step 1: Build Loop AST wrapped in a minimal function
let loop_ast = ASTNode::Loop {
condition: Box::new(condition.clone()),
body: body.to_vec(),
span: Span::unknown(),
};
// Wrap in a minimal function for JoinIR lowering
// JoinIR Frontend expects a function body, not just a loop
let wrapper_func = ASTNode::Program {
statements: vec![loop_ast],
span: Span::unknown(),
};
// Step 2: Convert to JSON
let program_json = ast_to_json(&wrapper_func);
if debug {
eprintln!(
"[cf_loop/joinir] Generated JSON for {}: {}",
func_name,
serde_json::to_string_pretty(&program_json).unwrap_or_default()
);
}
// Step 3: Lower to JoinIR
let mut lowerer = AstToJoinIrLowerer::new();
let join_module = lowerer.lower_program_json(&program_json);
// Phase 49-3 MVP: Use empty meta map (full if-analysis is Phase 40+ territory)
let join_meta = JoinFuncMetaMap::new();
if debug {
eprintln!(
"[cf_loop/joinir] JoinModule has {} functions, entry={:?}",
join_module.functions.len(),
join_module.entry
);
}
// Step 4: Convert JoinModule to MIR
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &join_meta)
.map_err(|e| format!("JoinIR→MIR conversion failed: {}", e.message))?;
if debug {
eprintln!(
"[cf_loop/joinir] MirModule has {} functions",
mir_module.functions.len()
);
}
// Step 5: Merge MIR blocks into current_function
// For Phase 49-3, we'll use a simplified approach:
// - Add generated blocks to current_function
// - Jump from current_block to the entry of generated loop
// - The loop exit becomes the new current_block
self.merge_joinir_mir_blocks(&mir_module, debug)?;
// Return void for now (loop doesn't have a meaningful return value in this context)
let void_val = self.next_value_id();
self.emit_instruction(MirInstruction::Const {
dst: void_val,
value: ConstValue::Void,
})?;
Ok(Some(void_val))
}
/// Phase 49-3.2: Merge JoinIR-generated MIR blocks into current_function
///
/// This merges JoinIR-generated blocks by:
/// 1. Remapping all block IDs to avoid conflicts
/// 2. Remapping all value IDs to avoid conflicts
/// 3. Adding all blocks to current_function
/// 4. Jumping from current_block to the entry block
/// 5. Converting Return → Jump to exit block
fn merge_joinir_mir_blocks(
&mut self,
mir_module: &crate::mir::MirModule,
debug: bool,
) -> Result<(), String> {
use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId};
use std::collections::HashMap;
if debug {
eprintln!(
"[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions",
mir_module.functions.len()
);
}
// Get the first (and typically only) function from JoinIR output
let join_func = mir_module
.functions
.values()
.next()
.ok_or("JoinIR module has no functions")?;
if debug {
eprintln!(
"[cf_loop/joinir] Merging function with {} blocks, entry={:?}",
join_func.blocks.len(),
join_func.entry_block
);
}
// Phase 49-3.2: Block ID and Value ID remapping
let mut block_map: HashMap<BasicBlockId, BasicBlockId> = HashMap::new();
let mut value_map: HashMap<ValueId, ValueId> = HashMap::new();
// 1. Allocate new block IDs for all JoinIR blocks
for old_block_id in join_func.blocks.keys() {
let new_block_id = self.block_gen.next();
block_map.insert(*old_block_id, new_block_id);
if debug {
eprintln!(
"[cf_loop/joinir] Block remap: {:?} → {:?}",
old_block_id, new_block_id
);
}
}
// 2. Create exit block for Return conversion
let exit_block_id = self.block_gen.next();
if debug {
eprintln!("[cf_loop/joinir] Exit block: {:?}", exit_block_id);
}
// 3. Collect all ValueIds used in JoinIR function
let mut used_values: std::collections::BTreeSet<ValueId> = std::collections::BTreeSet::new();
for block in join_func.blocks.values() {
Self::collect_values_in_block(block, &mut used_values);
}
// Also collect parameter ValueIds
for param in &join_func.params {
used_values.insert(*param);
}
// 4. Allocate new ValueIds
for old_value in used_values {
let new_value = self.next_value_id();
value_map.insert(old_value, new_value);
if debug {
eprintln!(
"[cf_loop/joinir] Value remap: {:?} → {:?}",
old_value, new_value
);
}
}
// 5. Clone and remap all blocks
for (old_block_id, old_block) in &join_func.blocks {
let new_block_id = block_map[old_block_id];
let mut new_block = BasicBlock::new(new_block_id);
// Remap instructions
for inst in &old_block.instructions {
let remapped = Self::remap_instruction(inst, &value_map, &block_map);
new_block.instructions.push(remapped);
}
new_block.instruction_spans = old_block.instruction_spans.clone();
// Remap terminator (convert Return → Jump to exit)
if let Some(ref term) = old_block.terminator {
let remapped_term = match term {
MirInstruction::Return { value } => {
// Convert Return to Jump to exit block
// If there's a return value, we need to store it first
if let Some(ret_val) = value {
let remapped_val = value_map.get(ret_val).copied().unwrap_or(*ret_val);
// Store the return value for later use
// For now, just jump to exit (value handling in Phase 49-4)
if debug {
eprintln!(
"[cf_loop/joinir] Return({:?}) → Jump to exit",
remapped_val
);
}
}
MirInstruction::Jump { target: exit_block_id }
}
_ => Self::remap_instruction(term, &value_map, &block_map),
};
new_block.terminator = Some(remapped_term);
}
// Add block to current function
if let Some(ref mut func) = self.current_function {
func.add_block(new_block);
}
}
// 6. Create exit block (empty for now, will be populated after loop)
if let Some(ref mut func) = self.current_function {
let exit_block = BasicBlock::new(exit_block_id);
func.add_block(exit_block);
}
// 7. Jump from current block to JoinIR entry
let entry_block = block_map[&join_func.entry_block];
crate::mir::builder::emission::branch::emit_jump(self, entry_block)?;
// 8. Switch to exit block for subsequent code
self.start_new_block(exit_block_id)?;
if debug {
eprintln!(
"[cf_loop/joinir] Merge complete: {} blocks added, continuing from {:?}",
join_func.blocks.len(),
exit_block_id
);
}
Ok(())
}
/// Collect all ValueIds used in a block
fn collect_values_in_block(
block: &crate::mir::BasicBlock,
values: &mut std::collections::BTreeSet<super::ValueId>,
) {
for inst in &block.instructions {
Self::collect_values_in_instruction(inst, values);
}
if let Some(ref term) = block.terminator {
Self::collect_values_in_instruction(term, values);
}
}
/// Collect all ValueIds used in an instruction
fn collect_values_in_instruction(
inst: &crate::mir::MirInstruction,
values: &mut std::collections::BTreeSet<super::ValueId>,
) {
use crate::mir::MirInstruction;
match inst {
MirInstruction::Const { dst, .. } => {
values.insert(*dst);
}
MirInstruction::BinOp { dst, lhs, rhs, .. } => {
values.insert(*dst);
values.insert(*lhs);
values.insert(*rhs);
}
MirInstruction::UnaryOp { dst, operand, .. } => {
values.insert(*dst);
values.insert(*operand);
}
MirInstruction::Compare { dst, lhs, rhs, .. } => {
values.insert(*dst);
values.insert(*lhs);
values.insert(*rhs);
}
MirInstruction::Load { dst, ptr } => {
values.insert(*dst);
values.insert(*ptr);
}
MirInstruction::Store { value, ptr } => {
values.insert(*value);
values.insert(*ptr);
}
MirInstruction::Call { dst, func, args, .. } => {
if let Some(d) = dst {
values.insert(*d);
}
values.insert(*func);
for arg in args {
values.insert(*arg);
}
}
MirInstruction::BoxCall { dst, box_val, args, .. } => {
if let Some(d) = dst {
values.insert(*d);
}
values.insert(*box_val);
for arg in args {
values.insert(*arg);
}
}
MirInstruction::Branch { condition, .. } => {
values.insert(*condition);
}
MirInstruction::Return { value } => {
if let Some(v) = value {
values.insert(*v);
}
}
MirInstruction::Phi { dst, inputs } => {
values.insert(*dst);
for (_, val) in inputs {
values.insert(*val);
}
}
MirInstruction::Copy { dst, src } => {
values.insert(*dst);
values.insert(*src);
}
MirInstruction::NewBox { dst, args, .. } => {
values.insert(*dst);
for arg in args {
values.insert(*arg);
}
}
MirInstruction::Print { value, .. } => {
values.insert(*value);
}
_ => {
// Other instructions: skip for now
}
}
}
/// Remap an instruction's ValueIds and BlockIds
fn remap_instruction(
inst: &crate::mir::MirInstruction,
value_map: &std::collections::HashMap<super::ValueId, super::ValueId>,
block_map: &std::collections::HashMap<crate::mir::BasicBlockId, crate::mir::BasicBlockId>,
) -> crate::mir::MirInstruction {
use crate::mir::MirInstruction;
let remap_value = |v: super::ValueId| value_map.get(&v).copied().unwrap_or(v);
let remap_block = |b: crate::mir::BasicBlockId| block_map.get(&b).copied().unwrap_or(b);
match inst {
MirInstruction::Const { dst, value } => MirInstruction::Const {
dst: remap_value(*dst),
value: value.clone(),
},
MirInstruction::BinOp { dst, op, lhs, rhs } => MirInstruction::BinOp {
dst: remap_value(*dst),
op: *op,
lhs: remap_value(*lhs),
rhs: remap_value(*rhs),
},
MirInstruction::UnaryOp { dst, op, operand } => MirInstruction::UnaryOp {
dst: remap_value(*dst),
op: *op,
operand: remap_value(*operand),
},
MirInstruction::Compare { dst, op, lhs, rhs } => MirInstruction::Compare {
dst: remap_value(*dst),
op: *op,
lhs: remap_value(*lhs),
rhs: remap_value(*rhs),
},
MirInstruction::Load { dst, ptr } => MirInstruction::Load {
dst: remap_value(*dst),
ptr: remap_value(*ptr),
},
MirInstruction::Store { value, ptr } => MirInstruction::Store {
value: remap_value(*value),
ptr: remap_value(*ptr),
},
MirInstruction::Call { dst, func, callee, args, effects } => MirInstruction::Call {
dst: dst.map(remap_value),
func: remap_value(*func),
callee: callee.clone(),
args: args.iter().map(|a| remap_value(*a)).collect(),
effects: *effects,
},
MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects } => {
MirInstruction::BoxCall {
dst: dst.map(remap_value),
box_val: remap_value(*box_val),
method: method.clone(),
method_id: *method_id,
args: args.iter().map(|a| remap_value(*a)).collect(),
effects: *effects,
}
}
MirInstruction::Branch { condition, then_bb, else_bb } => MirInstruction::Branch {
condition: remap_value(*condition),
then_bb: remap_block(*then_bb),
else_bb: remap_block(*else_bb),
},
MirInstruction::Jump { target } => MirInstruction::Jump {
target: remap_block(*target),
},
MirInstruction::Return { value } => MirInstruction::Return {
value: value.map(remap_value),
},
MirInstruction::Phi { dst, inputs } => MirInstruction::Phi {
dst: remap_value(*dst),
inputs: inputs
.iter()
.map(|(bb, val)| (remap_block(*bb), remap_value(*val)))
.collect(),
},
MirInstruction::Copy { dst, src } => MirInstruction::Copy {
dst: remap_value(*dst),
src: remap_value(*src),
},
MirInstruction::NewBox { dst, box_type, args } => MirInstruction::NewBox {
dst: remap_value(*dst),
box_type: box_type.clone(),
args: args.iter().map(|a| remap_value(*a)).collect(),
},
MirInstruction::Print { value, effects } => MirInstruction::Print {
value: remap_value(*value),
effects: *effects,
},
// Pass through other instructions unchanged
other => other.clone(),
}
}
/// Control-flow: try/catch/finally
pub(super) fn cf_try_catch(
&mut self,
try_body: Vec<ASTNode>,
catch_clauses: Vec<crate::ast::CatchClause>,
finally_body: Option<Vec<ASTNode>>,
) -> Result<ValueId, String> {
if std::env::var("NYASH_BUILDER_DISABLE_TRYCATCH")
.ok()
.as_deref()
== Some("1")
{
let try_ast = ASTNode::Program {
statements: try_body,
span: crate::ast::Span::unknown(),
};
let result = self.build_expression(try_ast)?;
return Ok(result);
}
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();
// Snapshot deferred-return state
let saved_defer_active = self.return_defer_active;
let saved_defer_slot = self.return_defer_slot;
let saved_defer_target = self.return_defer_target;
let saved_deferred_flag = self.return_deferred_emitted;
let saved_in_cleanup = self.in_cleanup_block;
let saved_allow_ret = self.cleanup_allow_return;
let saved_allow_throw = self.cleanup_allow_throw;
let ret_slot = self.next_value_id();
self.return_defer_active = true;
self.return_defer_slot = Some(ret_slot);
self.return_deferred_emitted = false;
self.return_defer_target = Some(finally_block.unwrap_or(exit_block));
if let Some(catch_clause) = catch_clauses.first() {
if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") {
eprintln!(
"[BUILDER] Emitting catch handler for {:?}",
catch_clause.exception_type
);
}
let exception_value = self.next_value_id();
self.emit_instruction(MirInstruction::Catch {
exception_type: catch_clause.exception_type.clone(),
exception_value,
handler_bb: catch_block,
})?;
}
// Enter try block
crate::mir::builder::emission::branch::emit_jump(self, 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)?;
if !self.is_current_block_terminated() {
let next_target = finally_block.unwrap_or(exit_block);
crate::mir::builder::emission::branch::emit_jump(self, next_target)?;
}
// Catch block
self.start_new_block(catch_block)?;
if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") {
eprintln!("[BUILDER] Enter catch block {:?}", catch_block);
}
if let Some(catch_clause) = catch_clauses.first() {
let catch_ast = ASTNode::Program {
statements: catch_clause.body.clone(),
span: crate::ast::Span::unknown(),
};
self.build_expression(catch_ast)?;
}
if !self.is_current_block_terminated() {
let next_target = finally_block.unwrap_or(exit_block);
crate::mir::builder::emission::branch::emit_jump(self, next_target)?;
}
// Finally
let mut cleanup_terminated = false;
if let (Some(finally_block_id), Some(finally_statements)) = (finally_block, finally_body) {
self.start_new_block(finally_block_id)?;
self.in_cleanup_block = true;
self.cleanup_allow_return = crate::config::env::cleanup_allow_return();
self.cleanup_allow_throw = crate::config::env::cleanup_allow_throw();
self.return_defer_active = false; // do not defer inside cleanup
let finally_ast = ASTNode::Program {
statements: finally_statements,
span: crate::ast::Span::unknown(),
};
self.build_expression(finally_ast)?;
cleanup_terminated = self.is_current_block_terminated();
if !cleanup_terminated {
crate::mir::builder::emission::branch::emit_jump(self, exit_block)?;
}
self.in_cleanup_block = false;
}
// Exit block
self.start_new_block(exit_block)?;
let result = if self.return_deferred_emitted && !cleanup_terminated {
self.emit_instruction(MirInstruction::Return {
value: Some(ret_slot),
})?;
crate::mir::builder::emission::constant::emit_void(self)
} else {
crate::mir::builder::emission::constant::emit_void(self)
};
// Restore context
self.return_defer_active = saved_defer_active;
self.return_defer_slot = saved_defer_slot;
self.return_defer_target = saved_defer_target;
self.return_deferred_emitted = saved_deferred_flag;
self.in_cleanup_block = saved_in_cleanup;
self.cleanup_allow_return = saved_allow_ret;
self.cleanup_allow_throw = saved_allow_throw;
Ok(result)
}
/// Control-flow: throw
pub(super) fn cf_throw(&mut self, expression: ASTNode) -> Result<ValueId, String> {
if self.in_cleanup_block && !self.cleanup_allow_throw {
return Err("throw is not allowed inside cleanup block (enable NYASH_CLEANUP_ALLOW_THROW=1 to permit)".to_string());
}
if std::env::var("NYASH_BUILDER_DISABLE_THROW").ok().as_deref() == Some("1") {
let v = self.build_expression(expression)?;
self.emit_instruction(MirInstruction::ExternCall {
dst: None,
iface_name: "env.debug".to_string(),
method_name: "trace".to_string(),
args: vec![v],
effects: EffectMask::PURE.add(Effect::Debug),
})?;
return Ok(v);
}
let exception_value = self.build_expression(expression)?;
self.emit_instruction(MirInstruction::Throw {
exception: exception_value,
effects: EffectMask::PANIC,
})?;
Ok(exception_value)
}
}